|
1 | 1 | /* eslint react/no-is-mounted:0,react/sort-comp:0,react/prop-types:0 */
|
2 |
| -import React, { Component, ReactElement } from 'react'; |
| 2 | +import type { ReactElement } from 'react'; |
| 3 | +import React, { Component } from 'react'; |
3 | 4 | import classNames from 'classnames';
|
4 | 5 | import pickAttrs from 'rc-util/lib/pickAttrs';
|
5 | 6 | import defaultRequest from './request';
|
6 | 7 | import getUid from './uid';
|
7 | 8 | import attrAccept from './attr-accept';
|
8 | 9 | import traverseFileTree from './traverseFileTree';
|
9 |
| -import { UploadProps, UploadProgressEvent, UploadRequestError, RcFile, Action } from './interface'; |
| 10 | +import type { UploadProps, UploadProgressEvent, UploadRequestError, RcFile } from './interface'; |
| 11 | + |
| 12 | +interface ParsedFileInfo { |
| 13 | + origin: RcFile; |
| 14 | + action: string; |
| 15 | + data: object; |
| 16 | + parsedFile: File | Blob | null; |
| 17 | +} |
10 | 18 |
|
11 | 19 | class AjaxUploader extends Component<UploadProps> {
|
12 | 20 | state = { uid: getUid() };
|
@@ -84,106 +92,105 @@ class AjaxUploader extends Component<UploadProps> {
|
84 | 92 | }
|
85 | 93 |
|
86 | 94 | uploadFiles = (files: FileList) => {
|
87 |
| - const postFiles: Array<RcFile> = Array.prototype.slice.call(files); |
88 |
| - postFiles |
89 |
| - .map((file: RcFile & { uid?: string }) => { |
90 |
| - // eslint-disable-next-line no-param-reassign |
91 |
| - file.uid = getUid(); |
92 |
| - return file; |
93 |
| - }) |
94 |
| - .forEach(file => { |
95 |
| - this.upload(file, postFiles); |
96 |
| - }); |
97 |
| - }; |
| 95 | + const originFiles = [...files] as RcFile[]; |
| 96 | + const postFiles = originFiles.map((file: RcFile & { uid?: string }) => { |
| 97 | + // eslint-disable-next-line no-param-reassign |
| 98 | + file.uid = getUid(); |
| 99 | + return this.processFile(file, originFiles); |
| 100 | + }); |
98 | 101 |
|
99 |
| - upload(file: RcFile, fileList: Array<RcFile>) { |
100 |
| - const { props } = this; |
101 |
| - if (!props.beforeUpload) { |
102 |
| - // always async in case use react state to keep fileList |
103 |
| - Promise.resolve().then(() => { |
| 102 | + // Batch upload files |
| 103 | + Promise.all(postFiles).then(fileList => { |
| 104 | + const { onBatchStart } = this.props; |
| 105 | + const enabledFiles = fileList.filter(file => file); |
| 106 | + |
| 107 | + onBatchStart?.(enabledFiles.map(file => file.origin)); |
| 108 | + |
| 109 | + enabledFiles.forEach(file => { |
104 | 110 | this.post(file);
|
105 | 111 | });
|
106 |
| - return; |
| 112 | + }); |
| 113 | + }; |
| 114 | + |
| 115 | + /** |
| 116 | + * Process file before upload. When all the file is ready, we start upload. |
| 117 | + */ |
| 118 | + processFile = async (file: RcFile, fileList: RcFile[]): Promise<ParsedFileInfo> => { |
| 119 | + const { beforeUpload, action, data } = this.props; |
| 120 | + |
| 121 | + let transformedFile: boolean | File | Blob | void = file; |
| 122 | + if (beforeUpload) { |
| 123 | + transformedFile = await beforeUpload(file, fileList); |
| 124 | + if (transformedFile === false) { |
| 125 | + return null; |
| 126 | + } |
107 | 127 | }
|
108 | 128 |
|
109 |
| - const before = props.beforeUpload(file, fileList); |
110 |
| - if (before && typeof before !== 'boolean' && before.then) { |
111 |
| - before |
112 |
| - .then(processedFile => { |
113 |
| - const processedFileType = Object.prototype.toString.call(processedFile); |
114 |
| - if (processedFileType === '[object File]' || processedFileType === '[object Blob]') { |
115 |
| - this.post(processedFile as RcFile); |
116 |
| - return; |
117 |
| - } |
118 |
| - this.post(file); |
119 |
| - }) |
120 |
| - .catch(e => { |
121 |
| - // eslint-disable-next-line no-console |
122 |
| - console.log(e); |
123 |
| - }); |
124 |
| - } else if (before !== false) { |
125 |
| - Promise.resolve().then(() => { |
126 |
| - this.post(file); |
127 |
| - }); |
| 129 | + let mergedAction: string; |
| 130 | + if (typeof action === 'function') { |
| 131 | + mergedAction = await action(file); |
| 132 | + } else { |
| 133 | + mergedAction = action; |
128 | 134 | }
|
129 |
| - } |
130 | 135 |
|
131 |
| - post(file: RcFile) { |
| 136 | + let mergedData: object; |
| 137 | + if (typeof data === 'function') { |
| 138 | + mergedData = await data(file); |
| 139 | + } else { |
| 140 | + mergedData = data; |
| 141 | + } |
| 142 | + |
| 143 | + const parsedFile = |
| 144 | + typeof transformedFile === 'object' && transformedFile ? transformedFile : file; |
| 145 | + |
| 146 | + // Used for `request.ts` get form data name |
| 147 | + if (!(parsedFile as any).name) { |
| 148 | + (parsedFile as any).name = file.name; |
| 149 | + } |
| 150 | + |
| 151 | + return { |
| 152 | + origin: file, |
| 153 | + data: mergedData, |
| 154 | + parsedFile, |
| 155 | + action: mergedAction, |
| 156 | + }; |
| 157 | + }; |
| 158 | + |
| 159 | + post({ data, origin, action, parsedFile }: ParsedFileInfo) { |
132 | 160 | if (!this._isMounted) {
|
133 | 161 | return;
|
134 | 162 | }
|
135 | 163 | const { props } = this;
|
136 |
| - const { onStart, onProgress, transformFile = originFile => originFile } = props; |
| 164 | + const { onStart, onProgress } = props; |
137 | 165 |
|
138 |
| - new Promise(resolve => { |
139 |
| - let actionRet: Action | PromiseLike<string> = props.action; |
140 |
| - if (typeof actionRet === 'function') { |
141 |
| - actionRet = actionRet(file); |
142 |
| - } |
143 |
| - return resolve(actionRet); |
144 |
| - }).then((action: string) => { |
145 |
| - const { uid } = file; |
146 |
| - const request = props.customRequest || defaultRequest; |
147 |
| - const transform = Promise.resolve(transformFile(file)) |
148 |
| - .then(transformedFile => { |
149 |
| - let { data } = props; |
150 |
| - if (typeof data === 'function') { |
151 |
| - data = data(transformedFile); |
| 166 | + const { uid } = origin; |
| 167 | + const request = props.customRequest || defaultRequest; |
| 168 | + |
| 169 | + const requestOption = { |
| 170 | + action, |
| 171 | + filename: props.name, |
| 172 | + data, |
| 173 | + file: parsedFile, |
| 174 | + headers: props.headers, |
| 175 | + withCredentials: props.withCredentials, |
| 176 | + method: props.method || 'post', |
| 177 | + onProgress: onProgress |
| 178 | + ? (e: UploadProgressEvent) => { |
| 179 | + onProgress(e, origin); |
152 | 180 | }
|
153 |
| - return Promise.all([transformedFile, data]); |
154 |
| - }) |
155 |
| - .catch(e => { |
156 |
| - console.error(e); // eslint-disable-line no-console |
157 |
| - }); |
158 |
| - |
159 |
| - transform.then(([transformedFile, data]: [RcFile, object]) => { |
160 |
| - const requestOption = { |
161 |
| - action, |
162 |
| - filename: props.name, |
163 |
| - data, |
164 |
| - file: transformedFile, |
165 |
| - headers: props.headers, |
166 |
| - withCredentials: props.withCredentials, |
167 |
| - method: props.method || 'post', |
168 |
| - onProgress: onProgress |
169 |
| - ? (e: UploadProgressEvent) => { |
170 |
| - onProgress(e, file); |
171 |
| - } |
172 |
| - : null, |
173 |
| - onSuccess: (ret: any, xhr: XMLHttpRequest) => { |
174 |
| - delete this.reqs[uid]; |
175 |
| - props.onSuccess(ret, file, xhr); |
176 |
| - }, |
177 |
| - onError: (err: UploadRequestError, ret: any) => { |
178 |
| - delete this.reqs[uid]; |
179 |
| - props.onError(err, ret, file); |
180 |
| - }, |
181 |
| - }; |
| 181 | + : null, |
| 182 | + onSuccess: (ret: any, xhr: XMLHttpRequest) => { |
| 183 | + delete this.reqs[uid]; |
| 184 | + props.onSuccess(ret, origin, xhr); |
| 185 | + }, |
| 186 | + onError: (err: UploadRequestError, ret: any) => { |
| 187 | + delete this.reqs[uid]; |
| 188 | + props.onError(err, ret, origin); |
| 189 | + }, |
| 190 | + }; |
182 | 191 |
|
183 |
| - onStart(file); |
184 |
| - this.reqs[uid] = request(requestOption); |
185 |
| - }); |
186 |
| - }); |
| 192 | + onStart(origin); |
| 193 | + this.reqs[uid] = request(requestOption); |
187 | 194 | }
|
188 | 195 |
|
189 | 196 | reset() {
|
|
0 commit comments