UNPKG

antd-mobile

Version:
250 lines 8.62 kB
import { __awaiter } from "tslib"; import React, { forwardRef, useRef, useState, useImperativeHandle } from 'react'; import { AddOutline, CloseOutline } from 'antd-mobile-icons'; import { mergeProps } from '../../utils/with-default-props'; import ImageViewer from '../image-viewer'; import PreviewItem from './preview-item'; import { usePropsValue } from '../../utils/use-props-value'; import { useIsomorphicLayoutEffect, useUnmount, useSize } from 'ahooks'; import Space from '../space'; import { withNativeProps } from '../../utils/native-props'; import { measureCSSLength } from '../../utils/measure-css-length'; import { useConfig } from '../config-provider'; import Grid from '../grid'; const classPrefix = `adm-image-uploader`; const defaultProps = { disableUpload: false, deletable: true, deleteIcon: React.createElement(CloseOutline, { className: `${classPrefix}-cell-delete-icon` }), showUpload: true, multiple: false, maxCount: 0, defaultValue: [], accept: 'image/*', preview: true, showFailed: true, imageFit: 'cover' }; export const ImageUploader = forwardRef((p, ref) => { const { locale } = useConfig(); const props = mergeProps(defaultProps, p); const { columns } = props; const [value, setValue] = usePropsValue(props); const [tasks, setTasks] = useState([]); const containerRef = useRef(null); const containerSize = useSize(containerRef); const gapMeasureRef = useRef(null); const [cellSize, setCellSize] = useState(80); const inputRef = useRef(null); useIsomorphicLayoutEffect(() => { const gapMeasure = gapMeasureRef.current; if (columns && containerSize && gapMeasure) { const width = containerSize.width; const gap = measureCSSLength(window.getComputedStyle(gapMeasure).getPropertyValue('height')); setCellSize((width - gap * (columns - 1)) / columns); } }, [containerSize === null || containerSize === void 0 ? void 0 : containerSize.width]); const style = { '--cell-size': cellSize + 'px' }; useIsomorphicLayoutEffect(() => { setTasks(prev => prev.filter(task => { if (task.url === undefined) return true; return !value.some(fileItem => fileItem.url === task.url); })); }, [value]); useIsomorphicLayoutEffect(() => { var _a; (_a = props.onUploadQueueChange) === null || _a === void 0 ? void 0 : _a.call(props, tasks.map(item => ({ id: item.id, status: item.status }))); }, [tasks]); const idCountRef = useRef(0); const { maxCount, onPreview, renderItem } = props; function processFile(file, fileList) { return __awaiter(this, void 0, void 0, function* () { const { beforeUpload } = props; let transformedFile = file; transformedFile = yield beforeUpload === null || beforeUpload === void 0 ? void 0 : beforeUpload(file, fileList); return transformedFile; }); } function getFinalTasks(tasks) { return props.showFailed ? tasks : tasks.filter(task => task.status !== 'fail'); } function onChange(e) { var _a; return __awaiter(this, void 0, void 0, function* () { e.persist(); const { files: rawFiles } = e.target; if (!rawFiles) return; let files = [].slice.call(rawFiles); e.target.value = ''; // HACK: fix the same file doesn't trigger onChange if (props.beforeUpload) { const postFiles = files.map(file => processFile(file, files)); yield Promise.all(postFiles).then(filesList => { files = filesList.filter(Boolean); }); } if (files.length === 0) { return; } if (maxCount > 0) { const exceed = value.length + files.length - maxCount; if (exceed > 0) { files = files.slice(0, files.length - exceed); (_a = props.onCountExceed) === null || _a === void 0 ? void 0 : _a.call(props, exceed); } } const newTasks = files.map(file => ({ id: idCountRef.current++, status: 'pending', file })); setTasks(prev => [...getFinalTasks(prev), ...newTasks]); const newVal = []; yield Promise.all(newTasks.map((currentTask, index) => __awaiter(this, void 0, void 0, function* () { try { const result = yield props.upload(currentTask.file); newVal[index] = result; setTasks(prev => { return prev.map(task => { if (task.id === currentTask.id) { return Object.assign(Object.assign({}, task), { status: 'success', url: result.url }); } return task; }); }); } catch (e) { setTasks(prev => { return prev.map(task => { if (task.id === currentTask.id) { return Object.assign(Object.assign({}, task), { status: 'fail' }); } return task; }); }); throw e; } }))).catch(error => console.error(error)); setValue(prev => prev.concat(newVal)); }); } const imageViewerHandlerRef = useRef(null); function previewImage(index) { imageViewerHandlerRef.current = ImageViewer.Multi.show({ images: value.map(fileItem => fileItem.url), defaultIndex: index, onClose: () => { imageViewerHandlerRef.current = null; } }); } useUnmount(() => { var _a; (_a = imageViewerHandlerRef.current) === null || _a === void 0 ? void 0 : _a.close(); }); const finalTasks = getFinalTasks(tasks); const showUpload = props.showUpload && (maxCount === 0 || value.length + finalTasks.length < maxCount); const renderImages = () => { return value.map((fileItem, index) => { var _a, _b; const originNode = React.createElement(PreviewItem, { key: (_a = fileItem.key) !== null && _a !== void 0 ? _a : index, url: (_b = fileItem.thumbnailUrl) !== null && _b !== void 0 ? _b : fileItem.url, deletable: props.deletable, deleteIcon: props.deleteIcon, imageFit: props.imageFit, onClick: () => { if (props.preview) { previewImage(index); } onPreview && onPreview(index, fileItem); }, onDelete: () => __awaiter(void 0, void 0, void 0, function* () { var _c; const canDelete = yield (_c = props.onDelete) === null || _c === void 0 ? void 0 : _c.call(props, fileItem); if (canDelete === false) return; setValue(value.filter((x, i) => i !== index)); }) }); return renderItem ? renderItem(originNode, fileItem, value) : originNode; }); }; const contentNode = React.createElement(React.Fragment, null, renderImages(), tasks.map(task => { if (!props.showFailed && task.status === 'fail') { return null; } return React.createElement(PreviewItem, { key: task.id, file: task.file, deletable: task.status !== 'pending', deleteIcon: props.deleteIcon, status: task.status, imageFit: props.imageFit, onDelete: () => { setTasks(tasks.filter(x => x.id !== task.id)); } }); }), React.createElement("div", { className: `${classPrefix}-upload-button-wrap`, style: showUpload ? undefined : { display: 'none' } }, props.children || React.createElement("span", { className: `${classPrefix}-cell ${classPrefix}-upload-button`, role: 'button', "aria-label": locale.ImageUploader.upload }, React.createElement("span", { className: `${classPrefix}-upload-button-icon` }, React.createElement(AddOutline, null))), !props.disableUpload && React.createElement("input", { ref: inputRef, capture: props.capture, accept: props.accept, multiple: props.multiple, type: 'file', className: `${classPrefix}-input`, onChange: onChange, "aria-hidden": true }))); useImperativeHandle(ref, () => ({ get nativeElement() { return inputRef.current; } })); return withNativeProps(props, React.createElement("div", { className: classPrefix, ref: containerRef }, columns ? React.createElement(Grid, { className: `${classPrefix}-grid`, columns: columns, style: style }, React.createElement("div", { className: `${classPrefix}-gap-measure`, ref: gapMeasureRef }), contentNode.props.children) : React.createElement(Space, { className: `${classPrefix}-space`, wrap: true, block: true }, contentNode.props.children))); });