import * as React from 'react'; import RcUpload from 'rc-upload'; import classNames from 'classnames'; import Dragger from './Dragger'; import UploadList from './UploadList'; import { RcFile, ShowUploadListInterface, UploadProps, UploadFile, UploadLocale, UploadChangeParam, UploadType, UploadListType, } from './interface'; import { T, fileToObject, getFileItem, removeFileItem } from './utils'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; import defaultLocale from '../locale/default'; import { ConfigContext } from '../config-provider'; import devWarning from '../_util/devWarning'; import useForceUpdate from '../_util/hooks/useForceUpdate'; import useFreshState from './useFreshState'; export { UploadProps }; const InternalUpload: React.ForwardRefRenderFunction = (props, ref) => { const { fileList: fileListProp, defaultFileList, onRemove, showUploadList, listType, onPreview, onDownload, onChange, previewFile, disabled, locale: propLocale, iconRender, isImageUrl, progress, prefixCls: customizePrefixCls, className, type, children, style, itemRender, maxCount, } = props; const [dragState, setDragState] = React.useState('drop'); const forceUpdate = useForceUpdate(); // Refresh always use fresh data const [getFileList, setFileList] = useFreshState[]>( fileListProp || defaultFileList || [], fileListProp, ); const upload = React.useRef(); React.useEffect(() => { devWarning( 'fileList' in props || !('value' in props), 'Upload', '`value` is not a valid prop, do you mean `fileList`?', ); devWarning( !('transformFile' in props), 'Upload', '`transformFile` is deprecated. Please use `beforeUpload` directly.', ); }, []); // Control mode will auto fill file uid if not provided React.useEffect(() => { const timestamp = Date.now(); (fileListProp || []).forEach((file, index) => { file.uid = file.uid ?? `__AUTO__${timestamp}_${index}__`; }); }, [fileListProp]); const onInternalChange = (info: UploadChangeParam) => { let cloneList = [...info.fileList]; // Cut to match count if (maxCount === 1) { cloneList = cloneList.slice(-1); } else if (maxCount) { cloneList = cloneList.slice(0, maxCount); } setFileList(cloneList); onChange?.({ ...info, fileList: cloneList, }); }; const onStart = (file: RcFile) => { const targetItem = fileToObject(file); targetItem.status = 'uploading'; const nextFileList = getFileList().concat(); const fileIndex = nextFileList.findIndex(({ uid }: UploadFile) => uid === targetItem.uid); if (fileIndex === -1) { nextFileList.push(targetItem); } else { nextFileList[fileIndex] = targetItem; } onInternalChange({ file: targetItem, fileList: nextFileList, }); }; const onSuccess = (response: any, file: UploadFile, xhr: any) => { try { if (typeof response === 'string') { response = JSON.parse(response); } } catch (e) { /* do nothing */ } const targetItem = getFileItem(file, getFileList()); // removed if (!targetItem) { return; } targetItem.status = 'done'; targetItem.response = response; targetItem.xhr = xhr; onInternalChange({ file: { ...targetItem }, fileList: getFileList().concat(), }); }; const onProgress = (e: { percent: number }, file: UploadFile) => { const targetItem = getFileItem(file, getFileList()); // removed if (!targetItem) { return; } targetItem.percent = e.percent; onInternalChange({ event: e, file: { ...targetItem }, fileList: getFileList().concat(), }); }; const onError = (error: Error, response: any, file: UploadFile) => { const targetItem = getFileItem(file, getFileList()); // removed if (!targetItem) { return; } targetItem.error = error; targetItem.response = response; targetItem.status = 'error'; onInternalChange({ file: { ...targetItem }, fileList: getFileList().concat(), }); }; const handleRemove = (file: UploadFile) => { let currentFile: UploadFile; Promise.resolve(typeof onRemove === 'function' ? onRemove(file) : onRemove).then(ret => { // Prevent removing file if (ret === false) { return; } const fileList = getFileList(); const removedFileList = removeFileItem(file, fileList); if (removedFileList) { currentFile = { ...file, status: 'removed' }; fileList?.forEach(item => { const matchKey = currentFile.uid !== undefined ? 'uid' : 'name'; if (item[matchKey] === currentFile[matchKey]) { item.status = 'removed'; } }); upload.current?.abort(currentFile); onInternalChange({ file: currentFile, fileList: removedFileList, }); } }); }; const onFileDrop = (e: React.DragEvent) => { setDragState(e.type); }; const beforeUpload = (file: RcFile, fileListArgs: RcFile[]) => { const { beforeUpload: beforeUploadProp } = props; if (!beforeUploadProp) { return true; } const result = beforeUploadProp(file, fileListArgs); if (result === false) { // Get unique file list const uniqueList: UploadFile[] = []; getFileList() .concat(fileListArgs.map(fileToObject)) .forEach(f => { if (uniqueList.every(uf => uf.uid !== f.uid)) { uniqueList.push(f); } }); onInternalChange({ file, fileList: uniqueList, }); return false; } if (result && (result as PromiseLike).then) { return result; } return true; }; // Test needs React.useImperativeHandle(ref, () => ({ onStart, onSuccess, onProgress, onError, fileList: getFileList(), upload: upload.current, forceUpdate, })); const { getPrefixCls, direction } = React.useContext(ConfigContext); const prefixCls = getPrefixCls('upload', customizePrefixCls); const rcUploadProps = { onStart, onError, onProgress, onSuccess, ...props, prefixCls, beforeUpload, 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 || disabled) { delete rcUploadProps.id; } const renderUploadList = (button?: React.ReactNode) => showUploadList ? ( {(locale: UploadLocale) => { const { showRemoveIcon, showPreviewIcon, showDownloadIcon, removeIcon, downloadIcon } = typeof showUploadList === 'boolean' ? ({} as ShowUploadListInterface) : showUploadList; return ( ); }} ) : ( button ); if (type === 'drag') { const dragCls = classNames( prefixCls, { [`${prefixCls}-drag`]: true, [`${prefixCls}-drag-uploading`]: getFileList().some(file => file.status === 'uploading'), [`${prefixCls}-drag-hover`]: dragState === 'dragover', [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-rtl`]: direction === 'rtl', }, className, ); return (
{children}
{renderUploadList()}
); } const uploadButtonCls = classNames(prefixCls, { [`${prefixCls}-select`]: true, [`${prefixCls}-select-${listType}`]: true, [`${prefixCls}-disabled`]: disabled, [`${prefixCls}-rtl`]: direction === 'rtl', }); const uploadButton = (
); if (listType === 'picture-card') { return ( {renderUploadList(uploadButton)} ); } return ( {uploadButton} {renderUploadList()} ); }; interface CompoundedComponent extends React.ForwardRefExoticComponent< React.PropsWithChildren & React.RefAttributes > { Dragger: typeof Dragger; } const Upload = React.forwardRef(InternalUpload) as CompoundedComponent; Upload.Dragger = Dragger; Upload.displayName = 'Upload'; Upload.defaultProps = { type: 'select' as UploadType, multiple: false, action: '', data: {}, accept: '', beforeUpload: T, showUploadList: true, listType: 'text' as UploadListType, // or picture className: '', disabled: false, supportServerRender: true, }; export default Upload;