"use client"; import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray"; var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import * as React from 'react'; import { flushSync } from 'react-dom'; import classNames from 'classnames'; import RcUpload from 'rc-upload'; import useMergedState from "rc-util/es/hooks/useMergedState"; import { devUseWarning } from '../_util/warning'; import { ConfigContext } from '../config-provider'; import DisabledContext from '../config-provider/DisabledContext'; import { useLocale } from '../locale'; import defaultLocale from '../locale/en_US'; import useStyle from './style'; import UploadList from './UploadList'; import { file2Obj, getFileItem, removeFileItem, updateFileList } from './utils'; export const LIST_IGNORE = `__LIST_IGNORE_${Date.now()}__`; const InternalUpload = (props, ref) => { const { fileList, defaultFileList, onRemove, showUploadList = true, listType = 'text', onPreview, onDownload, onChange, onDrop, previewFile, disabled: customDisabled, locale: propLocale, iconRender, isImageUrl, progress, prefixCls: customizePrefixCls, className, type = 'select', children, style, itemRender, maxCount, data = {}, multiple = false, action = '', accept = '', supportServerRender = true, rootClassName } = props; // ===================== Disabled ===================== const disabled = React.useContext(DisabledContext); const mergedDisabled = customDisabled !== null && customDisabled !== void 0 ? customDisabled : disabled; const [mergedFileList, setMergedFileList] = useMergedState(defaultFileList || [], { value: fileList, postState: list => list !== null && list !== void 0 ? list : [] }); const [dragState, setDragState] = React.useState('drop'); const upload = React.useRef(null); if (process.env.NODE_ENV !== 'production') { const warning = devUseWarning('Upload'); process.env.NODE_ENV !== "production" ? warning('fileList' in props || !('value' in props), 'usage', '`value` is not a valid prop, do you mean `fileList`?') : void 0; warning.deprecated(!('transformFile' in props), 'transformFile', 'beforeUpload'); } // Control mode will auto fill file uid if not provided React.useMemo(() => { const timestamp = Date.now(); (fileList || []).forEach((file, index) => { if (!file.uid && !Object.isFrozen(file)) { file.uid = `__AUTO__${timestamp}_${index}__`; } }); }, [fileList]); const onInternalChange = (file, changedFileList, event) => { let cloneList = _toConsumableArray(changedFileList); let exceedMaxCount = false; // Cut to match count if (maxCount === 1) { cloneList = cloneList.slice(-1); } else if (maxCount) { exceedMaxCount = cloneList.length > maxCount; cloneList = cloneList.slice(0, maxCount); } // Prevent React18 auto batch since input[upload] trigger process at same time // which makes fileList closure problem flushSync(() => { setMergedFileList(cloneList); }); const changeInfo = { file: file, fileList: cloneList }; if (event) { changeInfo.event = event; } if (!exceedMaxCount || // We should ignore event if current file is exceed `maxCount` cloneList.some(f => f.uid === file.uid)) { flushSync(() => { onChange === null || onChange === void 0 ? void 0 : onChange(changeInfo); }); } }; const mergedBeforeUpload = (file, fileListArgs) => __awaiter(void 0, void 0, void 0, function* () { const { beforeUpload, transformFile } = props; let parsedFile = file; if (beforeUpload) { const result = yield beforeUpload(file, fileListArgs); if (result === false) { return false; } // Hack for LIST_IGNORE, we add additional info to remove from the list delete file[LIST_IGNORE]; if (result === LIST_IGNORE) { Object.defineProperty(file, LIST_IGNORE, { value: true, configurable: true }); return false; } if (typeof result === 'object' && result) { parsedFile = result; } } if (transformFile) { parsedFile = yield transformFile(parsedFile); } return parsedFile; }); const onBatchStart = batchFileInfoList => { // Skip file which marked as `LIST_IGNORE`, these file will not add to file list const filteredFileInfoList = batchFileInfoList.filter(info => !info.file[LIST_IGNORE]); // Nothing to do since no file need upload if (!filteredFileInfoList.length) { return; } const objectFileList = filteredFileInfoList.map(info => file2Obj(info.file)); // Concat new files with prev files let newFileList = _toConsumableArray(mergedFileList); objectFileList.forEach(fileObj => { // Replace file if exist newFileList = updateFileList(fileObj, newFileList); }); objectFileList.forEach((fileObj, index) => { // Repeat trigger `onChange` event for compatible let triggerFileObj = fileObj; if (!filteredFileInfoList[index].parsedFile) { // `beforeUpload` return false const { originFileObj } = fileObj; let clone; try { clone = new File([originFileObj], originFileObj.name, { type: originFileObj.type }); } catch (e) { clone = new Blob([originFileObj], { type: originFileObj.type }); clone.name = originFileObj.name; clone.lastModifiedDate = new Date(); clone.lastModified = new Date().getTime(); } clone.uid = fileObj.uid; triggerFileObj = clone; } else { // Inject `uploading` status fileObj.status = 'uploading'; } onInternalChange(triggerFileObj, newFileList); }); }; const onSuccess = (response, file, xhr) => { try { if (typeof response === 'string') { response = JSON.parse(response); } } catch (e) { /* do nothing */ } // removed if (!getFileItem(file, mergedFileList)) { return; } const targetItem = file2Obj(file); targetItem.status = 'done'; targetItem.percent = 100; targetItem.response = response; targetItem.xhr = xhr; const nextFileList = updateFileList(targetItem, mergedFileList); onInternalChange(targetItem, nextFileList); }; const onProgress = (e, file) => { // removed if (!getFileItem(file, mergedFileList)) { return; } const targetItem = file2Obj(file); targetItem.status = 'uploading'; targetItem.percent = e.percent; const nextFileList = updateFileList(targetItem, mergedFileList); onInternalChange(targetItem, nextFileList, e); }; const onError = (error, response, file) => { // removed if (!getFileItem(file, mergedFileList)) { return; } const targetItem = file2Obj(file); targetItem.error = error; targetItem.response = response; targetItem.status = 'error'; const nextFileList = updateFileList(targetItem, mergedFileList); onInternalChange(targetItem, nextFileList); }; const handleRemove = file => { let currentFile; Promise.resolve(typeof onRemove === 'function' ? onRemove(file) : onRemove).then(ret => { var _a; // Prevent removing file if (ret === false) { return; } const removedFileList = removeFileItem(file, mergedFileList); if (removedFileList) { currentFile = Object.assign(Object.assign({}, file), { status: 'removed' }); mergedFileList === null || mergedFileList === void 0 ? void 0 : mergedFileList.forEach(item => { const matchKey = currentFile.uid !== undefined ? 'uid' : 'name'; if (item[matchKey] === currentFile[matchKey] && !Object.isFrozen(item)) { item.status = 'removed'; } }); (_a = upload.current) === null || _a === void 0 ? void 0 : _a.abort(currentFile); onInternalChange(currentFile, removedFileList); } }); }; const onFileDrop = e => { setDragState(e.type); if (e.type === 'drop') { onDrop === null || onDrop === void 0 ? void 0 : onDrop(e); } }; // Test needs React.useImperativeHandle(ref, () => ({ onBatchStart, onSuccess, onProgress, onError, fileList: mergedFileList, upload: upload.current })); const { getPrefixCls, direction, upload: ctxUpload } = React.useContext(ConfigContext); const prefixCls = getPrefixCls('upload', customizePrefixCls); const rcUploadProps = Object.assign(Object.assign({ onBatchStart, onError, onProgress, onSuccess }, props), { data, multiple, action, accept, supportServerRender, prefixCls, disabled: mergedDisabled, beforeUpload: mergedBeforeUpload, onChange: undefined }); delete rcUploadProps.className; delete rcUploadProps.style; // Remove id to avoid open by label when trigger is hidden // !children: https://github.com/ant-design/ant-design/issues/14298 // disabled: https://github.com/ant-design/ant-design/issues/16478 // https://github.com/ant-design/ant-design/issues/24197 if (!children || mergedDisabled) { delete rcUploadProps.id; } const [wrapSSR, hashId] = useStyle(prefixCls); const [contextLocale] = useLocale('Upload', defaultLocale.Upload); const { showRemoveIcon, showPreviewIcon, showDownloadIcon, removeIcon, previewIcon, downloadIcon } = typeof showUploadList === 'boolean' ? {} : showUploadList; const renderUploadList = (button, buttonVisible) => { if (!showUploadList) { return button; } return /*#__PURE__*/React.createElement(UploadList, { prefixCls: prefixCls, listType: listType, items: mergedFileList, previewFile: previewFile, onPreview: onPreview, onDownload: onDownload, onRemove: handleRemove, showRemoveIcon: !mergedDisabled && showRemoveIcon, showPreviewIcon: showPreviewIcon, showDownloadIcon: showDownloadIcon, removeIcon: removeIcon, previewIcon: previewIcon, downloadIcon: downloadIcon, iconRender: iconRender, locale: Object.assign(Object.assign({}, contextLocale), propLocale), isImageUrl: isImageUrl, progress: progress, appendAction: button, appendActionVisible: buttonVisible, itemRender: itemRender, disabled: mergedDisabled }); }; const wrapperCls = classNames(`${prefixCls}-wrapper`, className, rootClassName, hashId, ctxUpload === null || ctxUpload === void 0 ? void 0 : ctxUpload.className, { [`${prefixCls}-rtl`]: direction === 'rtl', [`${prefixCls}-picture-card-wrapper`]: listType === 'picture-card', [`${prefixCls}-picture-circle-wrapper`]: listType === 'picture-circle' }); const mergedStyle = Object.assign(Object.assign({}, ctxUpload === null || ctxUpload === void 0 ? void 0 : ctxUpload.style), style); if (type === 'drag') { const dragCls = classNames(hashId, prefixCls, `${prefixCls}-drag`, { [`${prefixCls}-drag-uploading`]: mergedFileList.some(file => file.status === 'uploading'), [`${prefixCls}-drag-hover`]: dragState === 'dragover', [`${prefixCls}-disabled`]: mergedDisabled, [`${prefixCls}-rtl`]: direction === 'rtl' }); return wrapSSR( /*#__PURE__*/React.createElement("span", { className: wrapperCls }, /*#__PURE__*/React.createElement("div", { className: dragCls, style: mergedStyle, onDrop: onFileDrop, onDragOver: onFileDrop, onDragLeave: onFileDrop }, /*#__PURE__*/React.createElement(RcUpload, Object.assign({}, rcUploadProps, { ref: upload, className: `${prefixCls}-btn` }), /*#__PURE__*/React.createElement("div", { className: `${prefixCls}-drag-container` }, children))), renderUploadList())); } const uploadButtonCls = classNames(prefixCls, `${prefixCls}-select`, { [`${prefixCls}-disabled`]: mergedDisabled }); const renderUploadButton = uploadButtonStyle => /*#__PURE__*/React.createElement("div", { className: uploadButtonCls, style: uploadButtonStyle }, /*#__PURE__*/React.createElement(RcUpload, Object.assign({}, rcUploadProps, { ref: upload }))); const uploadButton = renderUploadButton(children ? undefined : { display: 'none' }); if (listType === 'picture-card' || listType === 'picture-circle') { return wrapSSR( /*#__PURE__*/React.createElement("span", { className: wrapperCls }, renderUploadList(uploadButton, !!children))); } return wrapSSR( /*#__PURE__*/React.createElement("span", { className: wrapperCls }, uploadButton, renderUploadList())); }; const Upload = /*#__PURE__*/React.forwardRef(InternalUpload); if (process.env.NODE_ENV !== 'production') { Upload.displayName = 'Upload'; } export default Upload;