Upload.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. "use client";
  2. import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
  3. var __awaiter = this && this.__awaiter || function (thisArg, _arguments, P, generator) {
  4. function adopt(value) {
  5. return value instanceof P ? value : new P(function (resolve) {
  6. resolve(value);
  7. });
  8. }
  9. return new (P || (P = Promise))(function (resolve, reject) {
  10. function fulfilled(value) {
  11. try {
  12. step(generator.next(value));
  13. } catch (e) {
  14. reject(e);
  15. }
  16. }
  17. function rejected(value) {
  18. try {
  19. step(generator["throw"](value));
  20. } catch (e) {
  21. reject(e);
  22. }
  23. }
  24. function step(result) {
  25. result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
  26. }
  27. step((generator = generator.apply(thisArg, _arguments || [])).next());
  28. });
  29. };
  30. import * as React from 'react';
  31. import { flushSync } from 'react-dom';
  32. import classNames from 'classnames';
  33. import RcUpload from 'rc-upload';
  34. import useMergedState from "rc-util/es/hooks/useMergedState";
  35. import { devUseWarning } from '../_util/warning';
  36. import { ConfigContext } from '../config-provider';
  37. import DisabledContext from '../config-provider/DisabledContext';
  38. import { useLocale } from '../locale';
  39. import defaultLocale from '../locale/en_US';
  40. import useStyle from './style';
  41. import UploadList from './UploadList';
  42. import { file2Obj, getFileItem, removeFileItem, updateFileList } from './utils';
  43. import { useComponentConfig } from '../config-provider/context';
  44. export const LIST_IGNORE = `__LIST_IGNORE_${Date.now()}__`;
  45. const InternalUpload = (props, ref) => {
  46. const config = useComponentConfig('upload');
  47. const {
  48. fileList,
  49. defaultFileList,
  50. onRemove,
  51. showUploadList = true,
  52. listType = 'text',
  53. onPreview,
  54. onDownload,
  55. onChange,
  56. onDrop,
  57. previewFile,
  58. disabled: customDisabled,
  59. locale: propLocale,
  60. iconRender,
  61. isImageUrl,
  62. progress,
  63. prefixCls: customizePrefixCls,
  64. className,
  65. type = 'select',
  66. children,
  67. style,
  68. itemRender,
  69. maxCount,
  70. data = {},
  71. multiple = false,
  72. hasControlInside = true,
  73. action = '',
  74. accept = '',
  75. supportServerRender = true,
  76. rootClassName
  77. } = props;
  78. // ===================== Disabled =====================
  79. const disabled = React.useContext(DisabledContext);
  80. const mergedDisabled = customDisabled !== null && customDisabled !== void 0 ? customDisabled : disabled;
  81. const customRequest = props.customRequest || config.customRequest;
  82. const [mergedFileList, setMergedFileList] = useMergedState(defaultFileList || [], {
  83. value: fileList,
  84. postState: list => list !== null && list !== void 0 ? list : []
  85. });
  86. const [dragState, setDragState] = React.useState('drop');
  87. const upload = React.useRef(null);
  88. const wrapRef = React.useRef(null);
  89. if (process.env.NODE_ENV !== 'production') {
  90. const warning = devUseWarning('Upload');
  91. 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;
  92. warning.deprecated(!('transformFile' in props), 'transformFile', 'beforeUpload');
  93. }
  94. // Control mode will auto fill file uid if not provided
  95. React.useMemo(() => {
  96. const timestamp = Date.now();
  97. (fileList || []).forEach((file, index) => {
  98. if (!file.uid && !Object.isFrozen(file)) {
  99. file.uid = `__AUTO__${timestamp}_${index}__`;
  100. }
  101. });
  102. }, [fileList]);
  103. const onInternalChange = (file, changedFileList, event) => {
  104. let cloneList = _toConsumableArray(changedFileList);
  105. let exceedMaxCount = false;
  106. // Cut to match count
  107. if (maxCount === 1) {
  108. cloneList = cloneList.slice(-1);
  109. } else if (maxCount) {
  110. exceedMaxCount = cloneList.length > maxCount;
  111. cloneList = cloneList.slice(0, maxCount);
  112. }
  113. // Prevent React18 auto batch since input[upload] trigger process at same time
  114. // which makes fileList closure problem
  115. // eslint-disable-next-line react-dom/no-flush-sync
  116. flushSync(() => {
  117. setMergedFileList(cloneList);
  118. });
  119. const changeInfo = {
  120. file: file,
  121. fileList: cloneList
  122. };
  123. if (event) {
  124. changeInfo.event = event;
  125. }
  126. if (!exceedMaxCount || file.status === 'removed' ||
  127. // We should ignore event if current file is exceed `maxCount`
  128. cloneList.some(f => f.uid === file.uid)) {
  129. // eslint-disable-next-line react-dom/no-flush-sync
  130. flushSync(() => {
  131. onChange === null || onChange === void 0 ? void 0 : onChange(changeInfo);
  132. });
  133. }
  134. };
  135. const mergedBeforeUpload = (file, fileListArgs) => __awaiter(void 0, void 0, void 0, function* () {
  136. const {
  137. beforeUpload,
  138. transformFile
  139. } = props;
  140. let parsedFile = file;
  141. if (beforeUpload) {
  142. const result = yield beforeUpload(file, fileListArgs);
  143. if (result === false) {
  144. return false;
  145. }
  146. // Hack for LIST_IGNORE, we add additional info to remove from the list
  147. delete file[LIST_IGNORE];
  148. if (result === LIST_IGNORE) {
  149. Object.defineProperty(file, LIST_IGNORE, {
  150. value: true,
  151. configurable: true
  152. });
  153. return false;
  154. }
  155. if (typeof result === 'object' && result) {
  156. parsedFile = result;
  157. }
  158. }
  159. if (transformFile) {
  160. parsedFile = yield transformFile(parsedFile);
  161. }
  162. return parsedFile;
  163. });
  164. const onBatchStart = batchFileInfoList => {
  165. // Skip file which marked as `LIST_IGNORE`, these file will not add to file list
  166. const filteredFileInfoList = batchFileInfoList.filter(info => !info.file[LIST_IGNORE]);
  167. // Nothing to do since no file need upload
  168. if (!filteredFileInfoList.length) {
  169. return;
  170. }
  171. const objectFileList = filteredFileInfoList.map(info => file2Obj(info.file));
  172. // Concat new files with prev files
  173. let newFileList = _toConsumableArray(mergedFileList);
  174. objectFileList.forEach(fileObj => {
  175. // Replace file if exist
  176. newFileList = updateFileList(fileObj, newFileList);
  177. });
  178. objectFileList.forEach((fileObj, index) => {
  179. // Repeat trigger `onChange` event for compatible
  180. let triggerFileObj = fileObj;
  181. if (!filteredFileInfoList[index].parsedFile) {
  182. // `beforeUpload` return false
  183. const {
  184. originFileObj
  185. } = fileObj;
  186. let clone;
  187. try {
  188. clone = new File([originFileObj], originFileObj.name, {
  189. type: originFileObj.type
  190. });
  191. } catch (_a) {
  192. clone = new Blob([originFileObj], {
  193. type: originFileObj.type
  194. });
  195. clone.name = originFileObj.name;
  196. clone.lastModifiedDate = new Date();
  197. clone.lastModified = new Date().getTime();
  198. }
  199. clone.uid = fileObj.uid;
  200. triggerFileObj = clone;
  201. } else {
  202. // Inject `uploading` status
  203. fileObj.status = 'uploading';
  204. }
  205. onInternalChange(triggerFileObj, newFileList);
  206. });
  207. };
  208. const onSuccess = (response, file, xhr) => {
  209. try {
  210. if (typeof response === 'string') {
  211. response = JSON.parse(response);
  212. }
  213. } catch (_a) {
  214. /* do nothing */
  215. }
  216. // removed
  217. if (!getFileItem(file, mergedFileList)) {
  218. return;
  219. }
  220. const targetItem = file2Obj(file);
  221. targetItem.status = 'done';
  222. targetItem.percent = 100;
  223. targetItem.response = response;
  224. targetItem.xhr = xhr;
  225. const nextFileList = updateFileList(targetItem, mergedFileList);
  226. onInternalChange(targetItem, nextFileList);
  227. };
  228. const onProgress = (e, file) => {
  229. // removed
  230. if (!getFileItem(file, mergedFileList)) {
  231. return;
  232. }
  233. const targetItem = file2Obj(file);
  234. targetItem.status = 'uploading';
  235. targetItem.percent = e.percent;
  236. const nextFileList = updateFileList(targetItem, mergedFileList);
  237. onInternalChange(targetItem, nextFileList, e);
  238. };
  239. const onError = (error, response, file) => {
  240. // removed
  241. if (!getFileItem(file, mergedFileList)) {
  242. return;
  243. }
  244. const targetItem = file2Obj(file);
  245. targetItem.error = error;
  246. targetItem.response = response;
  247. targetItem.status = 'error';
  248. const nextFileList = updateFileList(targetItem, mergedFileList);
  249. onInternalChange(targetItem, nextFileList);
  250. };
  251. const handleRemove = file => {
  252. let currentFile;
  253. Promise.resolve(typeof onRemove === 'function' ? onRemove(file) : onRemove).then(ret => {
  254. var _a;
  255. // Prevent removing file
  256. if (ret === false) {
  257. return;
  258. }
  259. const removedFileList = removeFileItem(file, mergedFileList);
  260. if (removedFileList) {
  261. currentFile = Object.assign(Object.assign({}, file), {
  262. status: 'removed'
  263. });
  264. mergedFileList === null || mergedFileList === void 0 ? void 0 : mergedFileList.forEach(item => {
  265. const matchKey = currentFile.uid !== undefined ? 'uid' : 'name';
  266. if (item[matchKey] === currentFile[matchKey] && !Object.isFrozen(item)) {
  267. item.status = 'removed';
  268. }
  269. });
  270. (_a = upload.current) === null || _a === void 0 ? void 0 : _a.abort(currentFile);
  271. onInternalChange(currentFile, removedFileList);
  272. }
  273. });
  274. };
  275. const onFileDrop = e => {
  276. setDragState(e.type);
  277. if (e.type === 'drop') {
  278. onDrop === null || onDrop === void 0 ? void 0 : onDrop(e);
  279. }
  280. };
  281. // Test needs
  282. React.useImperativeHandle(ref, () => ({
  283. onBatchStart,
  284. onSuccess,
  285. onProgress,
  286. onError,
  287. fileList: mergedFileList,
  288. upload: upload.current,
  289. nativeElement: wrapRef.current
  290. }));
  291. const {
  292. getPrefixCls,
  293. direction,
  294. upload: ctxUpload
  295. } = React.useContext(ConfigContext);
  296. const prefixCls = getPrefixCls('upload', customizePrefixCls);
  297. const rcUploadProps = Object.assign(Object.assign({
  298. onBatchStart,
  299. onError,
  300. onProgress,
  301. onSuccess
  302. }, props), {
  303. customRequest,
  304. data,
  305. multiple,
  306. action,
  307. accept,
  308. supportServerRender,
  309. prefixCls,
  310. disabled: mergedDisabled,
  311. beforeUpload: mergedBeforeUpload,
  312. onChange: undefined,
  313. hasControlInside
  314. });
  315. delete rcUploadProps.className;
  316. delete rcUploadProps.style;
  317. // Remove id to avoid open by label when trigger is hidden
  318. // !children: https://github.com/ant-design/ant-design/issues/14298
  319. // disabled: https://github.com/ant-design/ant-design/issues/16478
  320. // https://github.com/ant-design/ant-design/issues/24197
  321. if (!children || mergedDisabled) {
  322. delete rcUploadProps.id;
  323. }
  324. const wrapperCls = `${prefixCls}-wrapper`;
  325. const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, wrapperCls);
  326. const [contextLocale] = useLocale('Upload', defaultLocale.Upload);
  327. const {
  328. showRemoveIcon,
  329. showPreviewIcon,
  330. showDownloadIcon,
  331. removeIcon,
  332. previewIcon,
  333. downloadIcon,
  334. extra
  335. } = typeof showUploadList === 'boolean' ? {} : showUploadList;
  336. // use showRemoveIcon if it is specified explicitly
  337. const realShowRemoveIcon = typeof showRemoveIcon === 'undefined' ? !mergedDisabled : showRemoveIcon;
  338. const renderUploadList = (button, buttonVisible) => {
  339. if (!showUploadList) {
  340. return button;
  341. }
  342. return /*#__PURE__*/React.createElement(UploadList, {
  343. prefixCls: prefixCls,
  344. listType: listType,
  345. items: mergedFileList,
  346. previewFile: previewFile,
  347. onPreview: onPreview,
  348. onDownload: onDownload,
  349. onRemove: handleRemove,
  350. showRemoveIcon: realShowRemoveIcon,
  351. showPreviewIcon: showPreviewIcon,
  352. showDownloadIcon: showDownloadIcon,
  353. removeIcon: removeIcon,
  354. previewIcon: previewIcon,
  355. downloadIcon: downloadIcon,
  356. iconRender: iconRender,
  357. extra: extra,
  358. locale: Object.assign(Object.assign({}, contextLocale), propLocale),
  359. isImageUrl: isImageUrl,
  360. progress: progress,
  361. appendAction: button,
  362. appendActionVisible: buttonVisible,
  363. itemRender: itemRender,
  364. disabled: mergedDisabled
  365. });
  366. };
  367. const mergedCls = classNames(wrapperCls, className, rootClassName, hashId, cssVarCls, ctxUpload === null || ctxUpload === void 0 ? void 0 : ctxUpload.className, {
  368. [`${prefixCls}-rtl`]: direction === 'rtl',
  369. [`${prefixCls}-picture-card-wrapper`]: listType === 'picture-card',
  370. [`${prefixCls}-picture-circle-wrapper`]: listType === 'picture-circle'
  371. });
  372. const mergedStyle = Object.assign(Object.assign({}, ctxUpload === null || ctxUpload === void 0 ? void 0 : ctxUpload.style), style);
  373. // ======================== Render ========================
  374. if (type === 'drag') {
  375. const dragCls = classNames(hashId, prefixCls, `${prefixCls}-drag`, {
  376. [`${prefixCls}-drag-uploading`]: mergedFileList.some(file => file.status === 'uploading'),
  377. [`${prefixCls}-drag-hover`]: dragState === 'dragover',
  378. [`${prefixCls}-disabled`]: mergedDisabled,
  379. [`${prefixCls}-rtl`]: direction === 'rtl'
  380. });
  381. return wrapCSSVar(/*#__PURE__*/React.createElement("span", {
  382. className: mergedCls,
  383. ref: wrapRef
  384. }, /*#__PURE__*/React.createElement("div", {
  385. className: dragCls,
  386. style: mergedStyle,
  387. onDrop: onFileDrop,
  388. onDragOver: onFileDrop,
  389. onDragLeave: onFileDrop
  390. }, /*#__PURE__*/React.createElement(RcUpload, Object.assign({}, rcUploadProps, {
  391. ref: upload,
  392. className: `${prefixCls}-btn`
  393. }), /*#__PURE__*/React.createElement("div", {
  394. className: `${prefixCls}-drag-container`
  395. }, children))), renderUploadList()));
  396. }
  397. const uploadBtnCls = classNames(prefixCls, `${prefixCls}-select`, {
  398. [`${prefixCls}-disabled`]: mergedDisabled,
  399. [`${prefixCls}-hidden`]: !children
  400. });
  401. const uploadButton = /*#__PURE__*/React.createElement("div", {
  402. className: uploadBtnCls,
  403. style: mergedStyle
  404. }, /*#__PURE__*/React.createElement(RcUpload, Object.assign({}, rcUploadProps, {
  405. ref: upload
  406. })));
  407. if (listType === 'picture-card' || listType === 'picture-circle') {
  408. return wrapCSSVar(/*#__PURE__*/React.createElement("span", {
  409. className: mergedCls,
  410. ref: wrapRef
  411. }, renderUploadList(uploadButton, !!children)));
  412. }
  413. return wrapCSSVar(/*#__PURE__*/React.createElement("span", {
  414. className: mergedCls,
  415. ref: wrapRef
  416. }, uploadButton, renderUploadList()));
  417. };
  418. const Upload = /*#__PURE__*/React.forwardRef(InternalUpload);
  419. if (process.env.NODE_ENV !== 'production') {
  420. Upload.displayName = 'Upload';
  421. }
  422. export default Upload;