UNPKG

8.62 kBJavaScriptView Raw
1import { __awaiter } from "tslib";
2import React, { forwardRef, useRef, useState, useImperativeHandle } from 'react';
3import { AddOutline, CloseOutline } from 'antd-mobile-icons';
4import { mergeProps } from '../../utils/with-default-props';
5import ImageViewer from '../image-viewer';
6import PreviewItem from './preview-item';
7import { usePropsValue } from '../../utils/use-props-value';
8import { useIsomorphicLayoutEffect, useUnmount, useSize } from 'ahooks';
9import Space from '../space';
10import { withNativeProps } from '../../utils/native-props';
11import { measureCSSLength } from '../../utils/measure-css-length';
12import { useConfig } from '../config-provider';
13import Grid from '../grid';
14const classPrefix = `adm-image-uploader`;
15const defaultProps = {
16 disableUpload: false,
17 deletable: true,
18 deleteIcon: React.createElement(CloseOutline, {
19 className: `${classPrefix}-cell-delete-icon`
20 }),
21 showUpload: true,
22 multiple: false,
23 maxCount: 0,
24 defaultValue: [],
25 accept: 'image/*',
26 preview: true,
27 showFailed: true,
28 imageFit: 'cover'
29};
30export const ImageUploader = forwardRef((p, ref) => {
31 const {
32 locale
33 } = useConfig();
34 const props = mergeProps(defaultProps, p);
35 const {
36 columns
37 } = props;
38 const [value, setValue] = usePropsValue(props);
39 const [tasks, setTasks] = useState([]);
40 const containerRef = useRef(null);
41 const containerSize = useSize(containerRef);
42 const gapMeasureRef = useRef(null);
43 const [cellSize, setCellSize] = useState(80);
44 const inputRef = useRef(null);
45 useIsomorphicLayoutEffect(() => {
46 const gapMeasure = gapMeasureRef.current;
47 if (columns && containerSize && gapMeasure) {
48 const width = containerSize.width;
49 const gap = measureCSSLength(window.getComputedStyle(gapMeasure).getPropertyValue('height'));
50 setCellSize((width - gap * (columns - 1)) / columns);
51 }
52 }, [containerSize === null || containerSize === void 0 ? void 0 : containerSize.width]);
53 const style = {
54 '--cell-size': cellSize + 'px'
55 };
56 useIsomorphicLayoutEffect(() => {
57 setTasks(prev => prev.filter(task => {
58 if (task.url === undefined) return true;
59 return !value.some(fileItem => fileItem.url === task.url);
60 }));
61 }, [value]);
62 useIsomorphicLayoutEffect(() => {
63 var _a;
64 (_a = props.onUploadQueueChange) === null || _a === void 0 ? void 0 : _a.call(props, tasks.map(item => ({
65 id: item.id,
66 status: item.status
67 })));
68 }, [tasks]);
69 const idCountRef = useRef(0);
70 const {
71 maxCount,
72 onPreview,
73 renderItem
74 } = props;
75 function processFile(file, fileList) {
76 return __awaiter(this, void 0, void 0, function* () {
77 const {
78 beforeUpload
79 } = props;
80 let transformedFile = file;
81 transformedFile = yield beforeUpload === null || beforeUpload === void 0 ? void 0 : beforeUpload(file, fileList);
82 return transformedFile;
83 });
84 }
85 function getFinalTasks(tasks) {
86 return props.showFailed ? tasks : tasks.filter(task => task.status !== 'fail');
87 }
88 function onChange(e) {
89 var _a;
90 return __awaiter(this, void 0, void 0, function* () {
91 e.persist();
92 const {
93 files: rawFiles
94 } = e.target;
95 if (!rawFiles) return;
96 let files = [].slice.call(rawFiles);
97 e.target.value = ''; // HACK: fix the same file doesn't trigger onChange
98 if (props.beforeUpload) {
99 const postFiles = files.map(file => processFile(file, files));
100 yield Promise.all(postFiles).then(filesList => {
101 files = filesList.filter(Boolean);
102 });
103 }
104 if (files.length === 0) {
105 return;
106 }
107 if (maxCount > 0) {
108 const exceed = value.length + files.length - maxCount;
109 if (exceed > 0) {
110 files = files.slice(0, files.length - exceed);
111 (_a = props.onCountExceed) === null || _a === void 0 ? void 0 : _a.call(props, exceed);
112 }
113 }
114 const newTasks = files.map(file => ({
115 id: idCountRef.current++,
116 status: 'pending',
117 file
118 }));
119 setTasks(prev => [...getFinalTasks(prev), ...newTasks]);
120 const newVal = [];
121 yield Promise.all(newTasks.map((currentTask, index) => __awaiter(this, void 0, void 0, function* () {
122 try {
123 const result = yield props.upload(currentTask.file);
124 newVal[index] = result;
125 setTasks(prev => {
126 return prev.map(task => {
127 if (task.id === currentTask.id) {
128 return Object.assign(Object.assign({}, task), {
129 status: 'success',
130 url: result.url
131 });
132 }
133 return task;
134 });
135 });
136 } catch (e) {
137 setTasks(prev => {
138 return prev.map(task => {
139 if (task.id === currentTask.id) {
140 return Object.assign(Object.assign({}, task), {
141 status: 'fail'
142 });
143 }
144 return task;
145 });
146 });
147 throw e;
148 }
149 }))).catch(error => console.error(error));
150 setValue(prev => prev.concat(newVal));
151 });
152 }
153 const imageViewerHandlerRef = useRef(null);
154 function previewImage(index) {
155 imageViewerHandlerRef.current = ImageViewer.Multi.show({
156 images: value.map(fileItem => fileItem.url),
157 defaultIndex: index,
158 onClose: () => {
159 imageViewerHandlerRef.current = null;
160 }
161 });
162 }
163 useUnmount(() => {
164 var _a;
165 (_a = imageViewerHandlerRef.current) === null || _a === void 0 ? void 0 : _a.close();
166 });
167 const finalTasks = getFinalTasks(tasks);
168 const showUpload = props.showUpload && (maxCount === 0 || value.length + finalTasks.length < maxCount);
169 const renderImages = () => {
170 return value.map((fileItem, index) => {
171 var _a, _b;
172 const originNode = React.createElement(PreviewItem, {
173 key: (_a = fileItem.key) !== null && _a !== void 0 ? _a : index,
174 url: (_b = fileItem.thumbnailUrl) !== null && _b !== void 0 ? _b : fileItem.url,
175 deletable: props.deletable,
176 deleteIcon: props.deleteIcon,
177 imageFit: props.imageFit,
178 onClick: () => {
179 if (props.preview) {
180 previewImage(index);
181 }
182 onPreview && onPreview(index, fileItem);
183 },
184 onDelete: () => __awaiter(void 0, void 0, void 0, function* () {
185 var _c;
186 const canDelete = yield (_c = props.onDelete) === null || _c === void 0 ? void 0 : _c.call(props, fileItem);
187 if (canDelete === false) return;
188 setValue(value.filter((x, i) => i !== index));
189 })
190 });
191 return renderItem ? renderItem(originNode, fileItem, value) : originNode;
192 });
193 };
194 const contentNode = React.createElement(React.Fragment, null, renderImages(), tasks.map(task => {
195 if (!props.showFailed && task.status === 'fail') {
196 return null;
197 }
198 return React.createElement(PreviewItem, {
199 key: task.id,
200 file: task.file,
201 deletable: task.status !== 'pending',
202 deleteIcon: props.deleteIcon,
203 status: task.status,
204 imageFit: props.imageFit,
205 onDelete: () => {
206 setTasks(tasks.filter(x => x.id !== task.id));
207 }
208 });
209 }), React.createElement("div", {
210 className: `${classPrefix}-upload-button-wrap`,
211 style: showUpload ? undefined : {
212 display: 'none'
213 }
214 }, props.children || React.createElement("span", {
215 className: `${classPrefix}-cell ${classPrefix}-upload-button`,
216 role: 'button',
217 "aria-label": locale.ImageUploader.upload
218 }, React.createElement("span", {
219 className: `${classPrefix}-upload-button-icon`
220 }, React.createElement(AddOutline, null))), !props.disableUpload && React.createElement("input", {
221 ref: inputRef,
222 capture: props.capture,
223 accept: props.accept,
224 multiple: props.multiple,
225 type: 'file',
226 className: `${classPrefix}-input`,
227 onChange: onChange,
228 "aria-hidden": true
229 })));
230 useImperativeHandle(ref, () => ({
231 get nativeElement() {
232 return inputRef.current;
233 }
234 }));
235 return withNativeProps(props, React.createElement("div", {
236 className: classPrefix,
237 ref: containerRef
238 }, columns ? React.createElement(Grid, {
239 className: `${classPrefix}-grid`,
240 columns: columns,
241 style: style
242 }, React.createElement("div", {
243 className: `${classPrefix}-gap-measure`,
244 ref: gapMeasureRef
245 }), contentNode.props.children) : React.createElement(Space, {
246 className: `${classPrefix}-space`,
247 wrap: true,
248 block: true
249 }, contentNode.props.children)));
250});
\No newline at end of file