box-ui-elements 23.4.0-beta.13 → 23.4.0-beta.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/api/Metadata.js +0 -1
- package/es/api/Metadata.js.flow +1 -1
- package/es/api/Metadata.js.map +1 -1
- package/es/elements/common/upload-dialog/UploadDialog.js +1 -0
- package/es/elements/common/upload-dialog/UploadDialog.js.flow +1 -0
- package/es/elements/common/upload-dialog/UploadDialog.js.map +1 -1
- package/es/elements/index.js +1 -0
- package/es/elements/index.js.flow +1 -0
- package/es/elements/index.js.map +1 -1
- package/es/elements/wrappers/ContentUploader.js +2 -0
- package/es/elements/wrappers/ContentUploader.js.flow +2 -0
- package/es/elements/wrappers/ContentUploader.js.map +1 -1
- package/i18n/ja-JP.js +2 -2
- package/i18n/ja-JP.properties +2 -2
- package/package.json +1 -1
- package/src/api/Metadata.js +1 -1
- package/src/elements/common/upload-dialog/UploadDialog.js +1 -0
- package/src/elements/index.js +1 -0
- package/src/elements/wrappers/ContentUploader.js +2 -0
- package/es/elements/content-uploader/ContentUploader.js.flow +0 -1322
- package/es/elements/content-uploader/ContentUploaderPopup.js.flow +0 -11
- package/es/elements/content-uploader/DroppableContent.js.flow +0 -82
- package/es/elements/content-uploader/Footer.js.flow +0 -63
- package/es/elements/content-uploader/IconName.js.flow +0 -45
- package/es/elements/content-uploader/ItemAction.js.flow +0 -128
- package/es/elements/content-uploader/ItemList.js.flow +0 -79
- package/es/elements/content-uploader/ItemRemove.js.flow +0 -51
- package/es/elements/content-uploader/OverallUploadsProgressBar.js.flow +0 -111
- package/es/elements/content-uploader/ProgressBar.js.flow +0 -65
- package/es/elements/content-uploader/UploadInput.js.flow +0 -50
- package/es/elements/content-uploader/UploadState.js.flow +0 -114
- package/es/elements/content-uploader/UploadStateContent.js.flow +0 -71
- package/es/elements/content-uploader/UploadsManager.js.flow +0 -111
- package/es/elements/content-uploader/UploadsManagerAction.js.flow +0 -36
- package/es/elements/content-uploader/actionCellRenderer.js.flow +0 -18
- package/es/elements/content-uploader/index.js.flow +0 -4
- package/es/elements/content-uploader/nameCellRenderer.js.flow +0 -13
- package/es/elements/content-uploader/progressCellRenderer.js.flow +0 -81
- package/es/elements/content-uploader/removeCellRenderer.js.flow +0 -16
- package/src/elements/content-uploader/ContentUploader.js.flow +0 -1322
- package/src/elements/content-uploader/ContentUploaderPopup.js.flow +0 -11
- package/src/elements/content-uploader/DroppableContent.js.flow +0 -82
- package/src/elements/content-uploader/Footer.js.flow +0 -63
- package/src/elements/content-uploader/IconName.js.flow +0 -45
- package/src/elements/content-uploader/ItemAction.js.flow +0 -128
- package/src/elements/content-uploader/ItemList.js.flow +0 -79
- package/src/elements/content-uploader/ItemRemove.js.flow +0 -51
- package/src/elements/content-uploader/OverallUploadsProgressBar.js.flow +0 -111
- package/src/elements/content-uploader/ProgressBar.js.flow +0 -65
- package/src/elements/content-uploader/UploadInput.js.flow +0 -50
- package/src/elements/content-uploader/UploadState.js.flow +0 -114
- package/src/elements/content-uploader/UploadStateContent.js.flow +0 -71
- package/src/elements/content-uploader/UploadsManager.js.flow +0 -111
- package/src/elements/content-uploader/UploadsManagerAction.js.flow +0 -36
- package/src/elements/content-uploader/actionCellRenderer.js.flow +0 -18
- package/src/elements/content-uploader/index.js.flow +0 -4
- package/src/elements/content-uploader/nameCellRenderer.js.flow +0 -13
- package/src/elements/content-uploader/progressCellRenderer.js.flow +0 -81
- package/src/elements/content-uploader/removeCellRenderer.js.flow +0 -16
|
@@ -1,1322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @flow
|
|
3
|
-
* @file Content Uploader component
|
|
4
|
-
* @author Box
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import 'regenerator-runtime/runtime';
|
|
8
|
-
import React, { Component } from 'react';
|
|
9
|
-
import classNames from 'classnames';
|
|
10
|
-
import getProp from 'lodash/get';
|
|
11
|
-
import noop from 'lodash/noop';
|
|
12
|
-
import uniqueid from 'lodash/uniqueId';
|
|
13
|
-
import cloneDeep from 'lodash/cloneDeep';
|
|
14
|
-
import { TooltipProvider } from '@box/blueprint-web';
|
|
15
|
-
import { getTypedFileId, getTypedFolderId } from '../../utils/file';
|
|
16
|
-
import Browser from '../../utils/Browser';
|
|
17
|
-
|
|
18
|
-
import makeResponsive from '../common/makeResponsive';
|
|
19
|
-
import Internationalize from '../common/Internationalize';
|
|
20
|
-
import FolderUpload from '../../api/uploads/FolderUpload';
|
|
21
|
-
import API from '../../api';
|
|
22
|
-
import {
|
|
23
|
-
getDataTransferItemId,
|
|
24
|
-
getFileId,
|
|
25
|
-
getFileFromDataTransferItem,
|
|
26
|
-
getPackageFileFromDataTransferItem,
|
|
27
|
-
getFile,
|
|
28
|
-
getFileAPIOptions,
|
|
29
|
-
getDataTransferItemAPIOptions,
|
|
30
|
-
isDataTransferItemAFolder,
|
|
31
|
-
isDataTransferItemAPackage,
|
|
32
|
-
isMultiputSupported,
|
|
33
|
-
} from '../../utils/uploads';
|
|
34
|
-
import DroppableContent from './DroppableContent';
|
|
35
|
-
import UploadsManager from './UploadsManager';
|
|
36
|
-
import Footer from './Footer';
|
|
37
|
-
import {
|
|
38
|
-
DEFAULT_ROOT,
|
|
39
|
-
CLIENT_NAME_CONTENT_UPLOADER,
|
|
40
|
-
DEFAULT_HOSTNAME_UPLOAD,
|
|
41
|
-
DEFAULT_HOSTNAME_API,
|
|
42
|
-
VIEW_ERROR,
|
|
43
|
-
VIEW_UPLOAD_EMPTY,
|
|
44
|
-
VIEW_UPLOAD_IN_PROGRESS,
|
|
45
|
-
VIEW_UPLOAD_SUCCESS,
|
|
46
|
-
STATUS_PENDING,
|
|
47
|
-
STATUS_IN_PROGRESS,
|
|
48
|
-
STATUS_STAGED,
|
|
49
|
-
STATUS_COMPLETE,
|
|
50
|
-
STATUS_ERROR,
|
|
51
|
-
ERROR_CODE_UPLOAD_FILE_LIMIT,
|
|
52
|
-
} from '../../constants';
|
|
53
|
-
import type {
|
|
54
|
-
UploadItem,
|
|
55
|
-
UploadDataTransferItemWithAPIOptions,
|
|
56
|
-
UploadFileWithAPIOptions,
|
|
57
|
-
UploadFile,
|
|
58
|
-
UploadItemAPIOptions,
|
|
59
|
-
UploadStatus,
|
|
60
|
-
} from '../../common/types/upload';
|
|
61
|
-
import type { StringMap, Token, View, BoxItem } from '../../common/types/core';
|
|
62
|
-
import '../common/fonts.scss';
|
|
63
|
-
import '../common/base.scss';
|
|
64
|
-
|
|
65
|
-
type Props = {
|
|
66
|
-
apiHost: string,
|
|
67
|
-
chunked: boolean,
|
|
68
|
-
className: string,
|
|
69
|
-
clientName: string,
|
|
70
|
-
dataTransferItems: Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>,
|
|
71
|
-
fileLimit: number,
|
|
72
|
-
files?: Array<UploadFileWithAPIOptions | File>,
|
|
73
|
-
isDraggingItemsToUploadsManager?: boolean,
|
|
74
|
-
isFolderUploadEnabled: boolean,
|
|
75
|
-
isLarge: boolean,
|
|
76
|
-
isPartialUploadEnabled: boolean,
|
|
77
|
-
isPrepopulateFilesEnabled?: boolean,
|
|
78
|
-
isResumableUploadsEnabled: boolean,
|
|
79
|
-
isSmall: boolean,
|
|
80
|
-
isTouch: boolean,
|
|
81
|
-
isUploadFallbackLogicEnabled: boolean,
|
|
82
|
-
language?: string,
|
|
83
|
-
measureRef: Function,
|
|
84
|
-
messages?: StringMap,
|
|
85
|
-
onBeforeUpload: (file: Array<UploadFileWithAPIOptions | File>) => void,
|
|
86
|
-
onCancel: Function,
|
|
87
|
-
onClickCancel: UploadItem => void,
|
|
88
|
-
onClickResume: UploadItem => void,
|
|
89
|
-
onClickRetry: UploadItem => void,
|
|
90
|
-
onClose: Function,
|
|
91
|
-
onComplete: Function,
|
|
92
|
-
onError: Function,
|
|
93
|
-
onMinimize?: Function,
|
|
94
|
-
onProgress: Function,
|
|
95
|
-
onResume: Function,
|
|
96
|
-
onUpgradeCTAClick?: Function,
|
|
97
|
-
onUpload: Function,
|
|
98
|
-
overwrite: boolean,
|
|
99
|
-
requestInterceptor?: Function,
|
|
100
|
-
responseInterceptor?: Function,
|
|
101
|
-
rootFolderId: string,
|
|
102
|
-
sharedLink?: string,
|
|
103
|
-
sharedLinkPassword?: string,
|
|
104
|
-
token?: Token,
|
|
105
|
-
uploadHost: string,
|
|
106
|
-
useUploadsManager?: boolean,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
type State = {
|
|
110
|
-
errorCode?: string,
|
|
111
|
-
isUploadsManagerExpanded: boolean,
|
|
112
|
-
itemIds: Object,
|
|
113
|
-
items: UploadItem[],
|
|
114
|
-
view: View,
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const CHUNKED_UPLOAD_MIN_SIZE_BYTES = 104857600; // 100MB
|
|
118
|
-
const FILE_LIMIT_DEFAULT = 100; // Upload at most 100 files at once by default
|
|
119
|
-
const HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT = 8000;
|
|
120
|
-
const EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD = 5;
|
|
121
|
-
const UPLOAD_CONCURRENCY = 6;
|
|
122
|
-
|
|
123
|
-
class ContentUploader extends Component<Props, State> {
|
|
124
|
-
id: string;
|
|
125
|
-
|
|
126
|
-
state: State;
|
|
127
|
-
|
|
128
|
-
props: Props;
|
|
129
|
-
|
|
130
|
-
rootElement: HTMLElement;
|
|
131
|
-
|
|
132
|
-
appElement: HTMLElement;
|
|
133
|
-
|
|
134
|
-
resetItemsTimeout: TimeoutID;
|
|
135
|
-
|
|
136
|
-
isAutoExpanded: boolean = false;
|
|
137
|
-
|
|
138
|
-
static defaultProps = {
|
|
139
|
-
rootFolderId: DEFAULT_ROOT,
|
|
140
|
-
apiHost: DEFAULT_HOSTNAME_API,
|
|
141
|
-
chunked: true,
|
|
142
|
-
className: '',
|
|
143
|
-
clientName: CLIENT_NAME_CONTENT_UPLOADER,
|
|
144
|
-
fileLimit: FILE_LIMIT_DEFAULT,
|
|
145
|
-
uploadHost: DEFAULT_HOSTNAME_UPLOAD,
|
|
146
|
-
onBeforeUpload: noop,
|
|
147
|
-
onClickCancel: noop,
|
|
148
|
-
onClickResume: noop,
|
|
149
|
-
onClickRetry: noop,
|
|
150
|
-
onClose: noop,
|
|
151
|
-
onComplete: noop,
|
|
152
|
-
onError: noop,
|
|
153
|
-
onResume: noop,
|
|
154
|
-
onUpload: noop,
|
|
155
|
-
onProgress: noop,
|
|
156
|
-
overwrite: true,
|
|
157
|
-
useUploadsManager: false,
|
|
158
|
-
files: [],
|
|
159
|
-
onMinimize: noop,
|
|
160
|
-
onCancel: noop,
|
|
161
|
-
isFolderUploadEnabled: false,
|
|
162
|
-
isResumableUploadsEnabled: false,
|
|
163
|
-
isUploadFallbackLogicEnabled: false,
|
|
164
|
-
dataTransferItems: [],
|
|
165
|
-
isDraggingItemsToUploadsManager: false,
|
|
166
|
-
isPartialUploadEnabled: false,
|
|
167
|
-
isPrepopulateFilesEnabled: false,
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* [constructor]
|
|
172
|
-
*
|
|
173
|
-
* @return {ContentUploader}
|
|
174
|
-
*/
|
|
175
|
-
constructor(props: Props) {
|
|
176
|
-
super(props);
|
|
177
|
-
|
|
178
|
-
const { rootFolderId, token, useUploadsManager } = props;
|
|
179
|
-
this.state = {
|
|
180
|
-
view: (rootFolderId && token) || useUploadsManager ? VIEW_UPLOAD_EMPTY : VIEW_ERROR,
|
|
181
|
-
items: [],
|
|
182
|
-
errorCode: '',
|
|
183
|
-
itemIds: {},
|
|
184
|
-
isUploadsManagerExpanded: false,
|
|
185
|
-
};
|
|
186
|
-
this.id = uniqueid('bcu_');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* Fetches the root folder on load
|
|
191
|
-
*
|
|
192
|
-
* @private
|
|
193
|
-
* @inheritdoc
|
|
194
|
-
* @return {void}
|
|
195
|
-
*/
|
|
196
|
-
componentDidMount() {
|
|
197
|
-
this.rootElement = ((document.getElementById(this.id): any): HTMLElement);
|
|
198
|
-
this.appElement = this.rootElement;
|
|
199
|
-
const { files, isPrepopulateFilesEnabled } = this.props;
|
|
200
|
-
// isPrepopulateFilesEnabled is a prop used to pre-populate files without clicking upload button.
|
|
201
|
-
if (isPrepopulateFilesEnabled && files && files.length > 0) {
|
|
202
|
-
this.addFilesToUploadQueue(files, this.upload);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Cancels pending uploads
|
|
208
|
-
*
|
|
209
|
-
* @private
|
|
210
|
-
* @inheritdoc
|
|
211
|
-
* @return {void}
|
|
212
|
-
*/
|
|
213
|
-
componentWillUnmount() {
|
|
214
|
-
this.cancel();
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Adds new items to the queue when files prop gets updated in window view
|
|
219
|
-
*
|
|
220
|
-
* @return {void}
|
|
221
|
-
*/
|
|
222
|
-
componentDidUpdate(): void {
|
|
223
|
-
const { files, dataTransferItems, useUploadsManager } = this.props;
|
|
224
|
-
|
|
225
|
-
const hasFiles = Array.isArray(files) && files.length > 0;
|
|
226
|
-
const hasItems = Array.isArray(dataTransferItems) && dataTransferItems.length > 0;
|
|
227
|
-
const hasUploads = hasFiles || hasItems;
|
|
228
|
-
|
|
229
|
-
if (!useUploadsManager || !hasUploads) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// TODO: this gets called unnecessarily (upon each render regardless of the queue not changing)
|
|
234
|
-
this.addFilesWithOptionsToUploadQueueAndStartUpload(files, dataTransferItems);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Create and return new instance of API creator
|
|
239
|
-
*
|
|
240
|
-
* @param {UploadItemAPIOptions} [uploadAPIOptions]
|
|
241
|
-
* @return {API}
|
|
242
|
-
*/
|
|
243
|
-
createAPIFactory(uploadAPIOptions?: UploadItemAPIOptions): API {
|
|
244
|
-
const { rootFolderId } = this.props;
|
|
245
|
-
const folderId = getProp(uploadAPIOptions, 'folderId') || rootFolderId;
|
|
246
|
-
const fileId = getProp(uploadAPIOptions, 'fileId');
|
|
247
|
-
const itemFolderId = getTypedFolderId(folderId);
|
|
248
|
-
const itemFileId = fileId ? getTypedFileId(fileId) : null;
|
|
249
|
-
|
|
250
|
-
return new API({
|
|
251
|
-
...this.getBaseAPIOptions(),
|
|
252
|
-
id: itemFileId || itemFolderId,
|
|
253
|
-
...uploadAPIOptions,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Return base API options from props
|
|
259
|
-
*
|
|
260
|
-
* @private
|
|
261
|
-
* @returns {Object}
|
|
262
|
-
*/
|
|
263
|
-
getBaseAPIOptions = (): Object => {
|
|
264
|
-
const {
|
|
265
|
-
token,
|
|
266
|
-
sharedLink,
|
|
267
|
-
sharedLinkPassword,
|
|
268
|
-
apiHost,
|
|
269
|
-
uploadHost,
|
|
270
|
-
clientName,
|
|
271
|
-
requestInterceptor,
|
|
272
|
-
responseInterceptor,
|
|
273
|
-
} = this.props;
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
token,
|
|
277
|
-
sharedLink,
|
|
278
|
-
sharedLinkPassword,
|
|
279
|
-
apiHost,
|
|
280
|
-
uploadHost,
|
|
281
|
-
clientName,
|
|
282
|
-
requestInterceptor,
|
|
283
|
-
responseInterceptor,
|
|
284
|
-
};
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Given an array of files, return the files that are new to the Content Uploader
|
|
289
|
-
*
|
|
290
|
-
* @param {Array<UploadFileWithAPIOptions | File>} files
|
|
291
|
-
*/
|
|
292
|
-
getNewFiles = (files: Array<UploadFileWithAPIOptions | File>): Array<UploadFileWithAPIOptions | File> => {
|
|
293
|
-
const { rootFolderId } = this.props;
|
|
294
|
-
const { itemIds } = this.state;
|
|
295
|
-
|
|
296
|
-
return Array.from(files).filter(file => !itemIds[getFileId(file, rootFolderId)]);
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Given an array of files, return the files that are new to the Content Uploader
|
|
301
|
-
*
|
|
302
|
-
* @param {Array<UploadFileWithAPIOptions | File>} files
|
|
303
|
-
*/
|
|
304
|
-
getNewDataTransferItems = (
|
|
305
|
-
items: Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>,
|
|
306
|
-
): Array<DataTransferItem | UploadDataTransferItemWithAPIOptions> => {
|
|
307
|
-
const { rootFolderId } = this.props;
|
|
308
|
-
const { itemIds } = this.state;
|
|
309
|
-
|
|
310
|
-
return Array.from(items).filter(item => !itemIds[getDataTransferItemId(item, rootFolderId)]);
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Converts File API to upload items and adds to upload queue.
|
|
315
|
-
*
|
|
316
|
-
* @private
|
|
317
|
-
* @param {Array<UploadFileWithAPIOptions | UploadFile>} files - Files to be added to upload queue
|
|
318
|
-
* @param {Function} itemUpdateCallback - function to be invoked after items status are updated
|
|
319
|
-
* @param {boolean} [isRelativePathIgnored] - if true webkitRelativePath property is ignored
|
|
320
|
-
* @return {void}
|
|
321
|
-
*/
|
|
322
|
-
addFilesToUploadQueue = (
|
|
323
|
-
files?: Array<UploadFileWithAPIOptions | UploadFile>,
|
|
324
|
-
itemUpdateCallback: Function,
|
|
325
|
-
isRelativePathIgnored?: boolean = false,
|
|
326
|
-
) => {
|
|
327
|
-
const { onBeforeUpload, rootFolderId, isPrepopulateFilesEnabled } = this.props;
|
|
328
|
-
if (!files || files.length === 0) {
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const newFiles = this.getNewFiles(files);
|
|
333
|
-
|
|
334
|
-
if (newFiles.length === 0) {
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const newItemIds = {};
|
|
339
|
-
|
|
340
|
-
newFiles.forEach(file => {
|
|
341
|
-
newItemIds[getFileId(file, rootFolderId)] = true;
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
clearTimeout(this.resetItemsTimeout);
|
|
345
|
-
|
|
346
|
-
const firstFile = getFile(newFiles[0]);
|
|
347
|
-
|
|
348
|
-
this.setState(
|
|
349
|
-
state => ({
|
|
350
|
-
itemIds: {
|
|
351
|
-
...state.itemIds,
|
|
352
|
-
...newItemIds,
|
|
353
|
-
},
|
|
354
|
-
}),
|
|
355
|
-
() => {
|
|
356
|
-
onBeforeUpload(newFiles);
|
|
357
|
-
if (firstFile.webkitRelativePath && !isRelativePathIgnored) {
|
|
358
|
-
// webkitRelativePath should be ignored when the upload destination folder is known
|
|
359
|
-
this.addFilesWithRelativePathToQueue(newFiles, itemUpdateCallback);
|
|
360
|
-
} else {
|
|
361
|
-
this.addFilesWithoutRelativePathToQueue(
|
|
362
|
-
newFiles,
|
|
363
|
-
isPrepopulateFilesEnabled ? this.upload : itemUpdateCallback,
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
},
|
|
367
|
-
);
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Add dropped items to the upload queue
|
|
372
|
-
*
|
|
373
|
-
* @private
|
|
374
|
-
* @param {DataTransfer} droppedItems
|
|
375
|
-
* @param {Function} itemUpdateCallback
|
|
376
|
-
* @returns {Promise<any>}
|
|
377
|
-
*/
|
|
378
|
-
addDroppedItemsToUploadQueue = (droppedItems: DataTransfer, itemUpdateCallback: Function): void => {
|
|
379
|
-
if (droppedItems.items) {
|
|
380
|
-
this.addDataTransferItemsToUploadQueue(droppedItems.items, itemUpdateCallback);
|
|
381
|
-
} else {
|
|
382
|
-
this.addFilesToUploadQueue(Array.from(droppedItems.files), itemUpdateCallback);
|
|
383
|
-
}
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Add dataTransferItems to the upload queue
|
|
388
|
-
*
|
|
389
|
-
* @private
|
|
390
|
-
* @param {DataTransferItemList} dataTransferItems
|
|
391
|
-
* @param {Function} itemUpdateCallback
|
|
392
|
-
* @returns {Promise<any>}
|
|
393
|
-
*/
|
|
394
|
-
addDataTransferItemsToUploadQueue = (
|
|
395
|
-
dataTransferItems: DataTransferItemList | Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>,
|
|
396
|
-
itemUpdateCallback: Function,
|
|
397
|
-
): void => {
|
|
398
|
-
const { isFolderUploadEnabled } = this.props;
|
|
399
|
-
if (!dataTransferItems || dataTransferItems.length === 0) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const folderItems = [];
|
|
404
|
-
const fileItems = [];
|
|
405
|
-
const packageItems = [];
|
|
406
|
-
Array.from(dataTransferItems).forEach(item => {
|
|
407
|
-
const isDirectory = isDataTransferItemAFolder(item);
|
|
408
|
-
if (Browser.isSafari() && isDataTransferItemAPackage(item)) {
|
|
409
|
-
packageItems.push(item);
|
|
410
|
-
} else if (isDirectory && isFolderUploadEnabled) {
|
|
411
|
-
folderItems.push(item);
|
|
412
|
-
} else if (!isDirectory) {
|
|
413
|
-
fileItems.push(item);
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
this.addFileDataTransferItemsToUploadQueue(fileItems, itemUpdateCallback);
|
|
418
|
-
this.addPackageDataTransferItemsToUploadQueue(packageItems, itemUpdateCallback);
|
|
419
|
-
this.addFolderDataTransferItemsToUploadQueue(folderItems, itemUpdateCallback);
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Add dataTransferItem of file type to the upload queue
|
|
424
|
-
*
|
|
425
|
-
* @private
|
|
426
|
-
* @param {Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>} dataTransferItems
|
|
427
|
-
* @param {Function} itemUpdateCallback
|
|
428
|
-
* @returns {Promise<any>}
|
|
429
|
-
*/
|
|
430
|
-
addFileDataTransferItemsToUploadQueue = async (
|
|
431
|
-
dataTransferItems: Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>,
|
|
432
|
-
itemUpdateCallback: Function,
|
|
433
|
-
): Promise<any> => {
|
|
434
|
-
const files = await Promise.all(dataTransferItems.map(async item => getFileFromDataTransferItem(item)));
|
|
435
|
-
const filesArray = [];
|
|
436
|
-
files.forEach(file => {
|
|
437
|
-
if (file) {
|
|
438
|
-
filesArray.push(file);
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
this.addFilesToUploadQueue(filesArray, itemUpdateCallback);
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
/**
|
|
445
|
-
* Add dataTransferItem of package type to the upload queue
|
|
446
|
-
*
|
|
447
|
-
* @private
|
|
448
|
-
* @param {Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>} dataTransferItems
|
|
449
|
-
* @param {Function} itemUpdateCallback
|
|
450
|
-
* @returns {Promise<any>}
|
|
451
|
-
*/
|
|
452
|
-
addPackageDataTransferItemsToUploadQueue = async (
|
|
453
|
-
dataTransferItems: Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>,
|
|
454
|
-
itemUpdateCallback: Function,
|
|
455
|
-
): Promise<any> => {
|
|
456
|
-
const packageFiles = await Promise.all(
|
|
457
|
-
dataTransferItems.map(async item => getPackageFileFromDataTransferItem(item)),
|
|
458
|
-
);
|
|
459
|
-
const packageFilesArray = [];
|
|
460
|
-
packageFiles.forEach(packageFile => {
|
|
461
|
-
if (packageFile) {
|
|
462
|
-
packageFilesArray.push(packageFile);
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
this.addFilesToUploadQueue(packageFilesArray, itemUpdateCallback);
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Add dataTransferItem of folder type to the upload queue
|
|
470
|
-
*
|
|
471
|
-
* @private
|
|
472
|
-
* @param {Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>} dataTransferItems
|
|
473
|
-
* @param {Function} itemUpdateCallback
|
|
474
|
-
* @returns {Promise<any>}
|
|
475
|
-
*/
|
|
476
|
-
addFolderDataTransferItemsToUploadQueue = async (
|
|
477
|
-
dataTransferItems: Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>,
|
|
478
|
-
itemUpdateCallback: Function,
|
|
479
|
-
): Promise<any> => {
|
|
480
|
-
const { rootFolderId } = this.props;
|
|
481
|
-
const { itemIds } = this.state;
|
|
482
|
-
if (dataTransferItems.length === 0) {
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const newItems = this.getNewDataTransferItems(dataTransferItems);
|
|
487
|
-
newItems.forEach(item => {
|
|
488
|
-
itemIds[getDataTransferItemId(item, rootFolderId)] = true;
|
|
489
|
-
});
|
|
490
|
-
|
|
491
|
-
if (newItems.length === 0) {
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
const fileAPIOptions: Object = getDataTransferItemAPIOptions(newItems[0]);
|
|
496
|
-
const { folderId = rootFolderId } = fileAPIOptions;
|
|
497
|
-
const folderUploads = await Promise.all(
|
|
498
|
-
newItems.map(async item => {
|
|
499
|
-
const folderUpload = this.getFolderUploadAPI(folderId);
|
|
500
|
-
await folderUpload.buildFolderTreeFromDataTransferItem(item);
|
|
501
|
-
return folderUpload;
|
|
502
|
-
}),
|
|
503
|
-
);
|
|
504
|
-
const folderUploadsArray = [];
|
|
505
|
-
folderUploads.forEach(folderUpload => {
|
|
506
|
-
// $FlowFixMe no file property
|
|
507
|
-
folderUploadsArray.push({
|
|
508
|
-
api: folderUpload,
|
|
509
|
-
extension: '',
|
|
510
|
-
isFolder: true,
|
|
511
|
-
name: folderUpload.folder.name,
|
|
512
|
-
options: fileAPIOptions,
|
|
513
|
-
progress: 0,
|
|
514
|
-
size: 1,
|
|
515
|
-
status: STATUS_PENDING,
|
|
516
|
-
});
|
|
517
|
-
});
|
|
518
|
-
this.addToQueue(folderUploadsArray, itemUpdateCallback);
|
|
519
|
-
};
|
|
520
|
-
|
|
521
|
-
/**
|
|
522
|
-
* Converts File API to upload items and adds to upload queue for files with webkitRelativePath.
|
|
523
|
-
*
|
|
524
|
-
* @private
|
|
525
|
-
* @param {Array<UploadFileWithAPIOptions | File>} files - Files to be added to upload queue
|
|
526
|
-
* @param {Function} itemUpdateCallback - function to be invoked after items status are updated
|
|
527
|
-
* @return {void}
|
|
528
|
-
*/
|
|
529
|
-
addFilesWithRelativePathToQueue(files: Array<UploadFileWithAPIOptions | File>, itemUpdateCallback: Function) {
|
|
530
|
-
if (files.length === 0) {
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
const { rootFolderId } = this.props;
|
|
535
|
-
const fileAPIOptions: Object = getFileAPIOptions(files[0]);
|
|
536
|
-
const { folderId = rootFolderId } = fileAPIOptions;
|
|
537
|
-
const folderUpload = this.getFolderUploadAPI(folderId);
|
|
538
|
-
|
|
539
|
-
// Only 1 folder tree can be built with files having webkitRelativePath properties
|
|
540
|
-
folderUpload.buildFolderTreeFromWebkitRelativePath(files);
|
|
541
|
-
|
|
542
|
-
this.addFolderToUploadQueue(folderUpload, itemUpdateCallback, fileAPIOptions);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
/**
|
|
546
|
-
* Get folder upload API instance
|
|
547
|
-
*
|
|
548
|
-
* @private
|
|
549
|
-
* @param {string} folderId
|
|
550
|
-
* @return {FolderUpload}
|
|
551
|
-
*/
|
|
552
|
-
getFolderUploadAPI = (folderId: string): FolderUpload => {
|
|
553
|
-
const uploadBaseAPIOptions = this.getBaseAPIOptions();
|
|
554
|
-
|
|
555
|
-
return new FolderUpload(this.addFilesToUploadQueue, folderId, this.addToQueue, uploadBaseAPIOptions);
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Add folder to upload queue
|
|
560
|
-
*
|
|
561
|
-
* @private
|
|
562
|
-
* @param {FolderUpload} folderUpload
|
|
563
|
-
* @param {Function} itemUpdateCallback
|
|
564
|
-
* @param {Object} apiOptions
|
|
565
|
-
* @return {void}
|
|
566
|
-
*/
|
|
567
|
-
addFolderToUploadQueue = (folderUpload: FolderUpload, itemUpdateCallback: Function, apiOptions: Object): void => {
|
|
568
|
-
this.addToQueue(
|
|
569
|
-
[
|
|
570
|
-
// $FlowFixMe no file property
|
|
571
|
-
{
|
|
572
|
-
api: folderUpload,
|
|
573
|
-
extension: '',
|
|
574
|
-
isFolder: true,
|
|
575
|
-
name: folderUpload.folder.name,
|
|
576
|
-
options: apiOptions,
|
|
577
|
-
progress: 0,
|
|
578
|
-
size: 1,
|
|
579
|
-
status: STATUS_PENDING,
|
|
580
|
-
},
|
|
581
|
-
],
|
|
582
|
-
itemUpdateCallback,
|
|
583
|
-
);
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
* Converts File API to upload items and adds to upload queue for files with webkitRelativePath missing or ignored.
|
|
588
|
-
*
|
|
589
|
-
* @private
|
|
590
|
-
* @param {Array<UploadFileWithAPIOptions | File>} files - Files to be added to upload queue
|
|
591
|
-
* @param {Function} itemUpdateCallback - function to be invoked after items status are updated
|
|
592
|
-
* @return {void}
|
|
593
|
-
*/
|
|
594
|
-
addFilesWithoutRelativePathToQueue = (
|
|
595
|
-
files: Array<UploadFileWithAPIOptions | File>,
|
|
596
|
-
itemUpdateCallback: Function,
|
|
597
|
-
) => {
|
|
598
|
-
const { itemIds } = this.state;
|
|
599
|
-
const { rootFolderId } = this.props;
|
|
600
|
-
|
|
601
|
-
// Convert files from the file API to upload items
|
|
602
|
-
const newItems = files.map(file => {
|
|
603
|
-
const uploadFile = getFile(file);
|
|
604
|
-
const uploadAPIOptions = getFileAPIOptions(file);
|
|
605
|
-
const { name, size } = uploadFile;
|
|
606
|
-
|
|
607
|
-
// Extract extension or use empty string if file has no extension
|
|
608
|
-
let extension = name.substr(name.lastIndexOf('.') + 1);
|
|
609
|
-
if (extension.length === name.length) {
|
|
610
|
-
extension = '';
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
const api = this.getUploadAPI(uploadFile, uploadAPIOptions);
|
|
614
|
-
const uploadItem: Object = {
|
|
615
|
-
api,
|
|
616
|
-
extension,
|
|
617
|
-
file: uploadFile,
|
|
618
|
-
name,
|
|
619
|
-
progress: 0,
|
|
620
|
-
size,
|
|
621
|
-
status: STATUS_PENDING,
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
if (uploadAPIOptions) {
|
|
625
|
-
uploadItem.options = uploadAPIOptions;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
itemIds[getFileId(uploadItem, rootFolderId)] = true;
|
|
629
|
-
|
|
630
|
-
return uploadItem;
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
if (newItems.length === 0) {
|
|
634
|
-
return;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
this.setState({
|
|
638
|
-
itemIds,
|
|
639
|
-
});
|
|
640
|
-
this.addToQueue(newItems, itemUpdateCallback);
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Add new items to the upload queue
|
|
645
|
-
*
|
|
646
|
-
* @private
|
|
647
|
-
* @param {Array<UploadFileWithAPIOptions | File>} newItems - Files to be added to upload queue
|
|
648
|
-
* @param {Function} itemUpdateCallback - function to be invoked after items status are updated
|
|
649
|
-
* @return {void}
|
|
650
|
-
*/
|
|
651
|
-
addToQueue = (newItems: UploadItem[], itemUpdateCallback: Function) => {
|
|
652
|
-
const { fileLimit, useUploadsManager } = this.props;
|
|
653
|
-
const { items, isUploadsManagerExpanded } = this.state;
|
|
654
|
-
|
|
655
|
-
let updatedItems = [];
|
|
656
|
-
const prevItemsNum = items.length;
|
|
657
|
-
const totalNumOfItems = prevItemsNum + newItems.length;
|
|
658
|
-
|
|
659
|
-
// Don't add more than fileLimit # of items
|
|
660
|
-
if (totalNumOfItems > fileLimit) {
|
|
661
|
-
updatedItems = items.concat(newItems.slice(0, fileLimit - items.length));
|
|
662
|
-
this.setState({
|
|
663
|
-
errorCode: ERROR_CODE_UPLOAD_FILE_LIMIT,
|
|
664
|
-
});
|
|
665
|
-
} else {
|
|
666
|
-
updatedItems = items.concat(newItems);
|
|
667
|
-
this.setState({ errorCode: '' });
|
|
668
|
-
|
|
669
|
-
// If the number of items being uploaded passes the threshold, expand the upload manager
|
|
670
|
-
if (
|
|
671
|
-
prevItemsNum < EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD &&
|
|
672
|
-
totalNumOfItems >= EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD &&
|
|
673
|
-
useUploadsManager &&
|
|
674
|
-
!isUploadsManagerExpanded
|
|
675
|
-
) {
|
|
676
|
-
this.isAutoExpanded = true;
|
|
677
|
-
this.expandUploadsManager();
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
this.updateViewAndCollection(updatedItems, () => {
|
|
682
|
-
if (itemUpdateCallback) {
|
|
683
|
-
itemUpdateCallback();
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
const { view } = this.state;
|
|
687
|
-
// Automatically start upload if other files are being uploaded
|
|
688
|
-
if (view === VIEW_UPLOAD_IN_PROGRESS) {
|
|
689
|
-
this.upload();
|
|
690
|
-
}
|
|
691
|
-
});
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
/**
|
|
695
|
-
* Returns a new API instance for the given file.
|
|
696
|
-
*
|
|
697
|
-
* @private
|
|
698
|
-
* @param {File} file - File to get a new API instance for
|
|
699
|
-
* @param {UploadItemAPIOptions} [uploadAPIOptions]
|
|
700
|
-
* @return {UploadAPI} - Instance of Upload API
|
|
701
|
-
*/
|
|
702
|
-
getUploadAPI(file: File, uploadAPIOptions?: UploadItemAPIOptions) {
|
|
703
|
-
const { chunked, isResumableUploadsEnabled, isUploadFallbackLogicEnabled } = this.props;
|
|
704
|
-
const { size } = file;
|
|
705
|
-
const factory = this.createAPIFactory(uploadAPIOptions);
|
|
706
|
-
|
|
707
|
-
if (chunked && size > CHUNKED_UPLOAD_MIN_SIZE_BYTES) {
|
|
708
|
-
if (isMultiputSupported()) {
|
|
709
|
-
const chunkedUploadAPI = factory.getChunkedUploadAPI();
|
|
710
|
-
if (isResumableUploadsEnabled) {
|
|
711
|
-
chunkedUploadAPI.isResumableUploadsEnabled = true;
|
|
712
|
-
}
|
|
713
|
-
if (isUploadFallbackLogicEnabled) {
|
|
714
|
-
chunkedUploadAPI.isUploadFallbackLogicEnabled = true;
|
|
715
|
-
}
|
|
716
|
-
return chunkedUploadAPI;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
/* eslint-disable no-console */
|
|
720
|
-
console.warn(
|
|
721
|
-
'Chunked uploading is enabled, but not supported by your browser. You may need to enable HTTPS.',
|
|
722
|
-
);
|
|
723
|
-
/* eslint-enable no-console */
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const plainUploadAPI = factory.getPlainUploadAPI();
|
|
727
|
-
if (isUploadFallbackLogicEnabled) {
|
|
728
|
-
plainUploadAPI.isUploadFallbackLogicEnabled = true;
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
return plainUploadAPI;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/**
|
|
735
|
-
* Removes an item from the upload queue. Cancels upload if in progress.
|
|
736
|
-
*
|
|
737
|
-
* @param {UploadItem} item - Item to remove
|
|
738
|
-
* @return {void}
|
|
739
|
-
*/
|
|
740
|
-
removeFileFromUploadQueue = (item: UploadItem) => {
|
|
741
|
-
const { onCancel, useUploadsManager } = this.props;
|
|
742
|
-
const { items } = this.state;
|
|
743
|
-
// Clear any error errorCode in footer
|
|
744
|
-
this.setState({ errorCode: '' });
|
|
745
|
-
|
|
746
|
-
const { api } = item;
|
|
747
|
-
api.cancel();
|
|
748
|
-
|
|
749
|
-
items.splice(items.indexOf(item), 1);
|
|
750
|
-
|
|
751
|
-
onCancel([item]);
|
|
752
|
-
this.updateViewAndCollection(items, () => {
|
|
753
|
-
// Minimize uploads manager if there are no more items
|
|
754
|
-
if (useUploadsManager && !items.length) {
|
|
755
|
-
this.minimizeUploadsManager();
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
const { view } = this.state;
|
|
759
|
-
if (view === VIEW_UPLOAD_IN_PROGRESS) {
|
|
760
|
-
this.upload();
|
|
761
|
-
}
|
|
762
|
-
});
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
/**
|
|
766
|
-
* Aborts uploads in progress and clears upload list.
|
|
767
|
-
*
|
|
768
|
-
* @private
|
|
769
|
-
* @return {void}
|
|
770
|
-
*/
|
|
771
|
-
cancel = () => {
|
|
772
|
-
const { items } = this.state;
|
|
773
|
-
items.forEach(uploadItem => {
|
|
774
|
-
const { api, status } = uploadItem;
|
|
775
|
-
if (status === STATUS_IN_PROGRESS) {
|
|
776
|
-
api.cancel();
|
|
777
|
-
}
|
|
778
|
-
});
|
|
779
|
-
|
|
780
|
-
// Reset upload collection
|
|
781
|
-
this.updateViewAndCollection([]);
|
|
782
|
-
};
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* Uploads all items in the upload collection.
|
|
786
|
-
*
|
|
787
|
-
* @private
|
|
788
|
-
* @return {void}
|
|
789
|
-
*/
|
|
790
|
-
upload = () => {
|
|
791
|
-
const { items } = this.state;
|
|
792
|
-
items.forEach(uploadItem => {
|
|
793
|
-
if (uploadItem.status === STATUS_PENDING) {
|
|
794
|
-
this.uploadFile(uploadItem);
|
|
795
|
-
}
|
|
796
|
-
});
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* Helper to upload a single file.
|
|
801
|
-
*
|
|
802
|
-
* @param {UploadItem} item - Upload item object
|
|
803
|
-
* @return {void}
|
|
804
|
-
*/
|
|
805
|
-
uploadFile(item: UploadItem) {
|
|
806
|
-
const { overwrite, rootFolderId } = this.props;
|
|
807
|
-
const { api, file, options } = item;
|
|
808
|
-
const { items } = this.state;
|
|
809
|
-
|
|
810
|
-
const numItemsUploading = items.filter(item_t => item_t.status === STATUS_IN_PROGRESS).length;
|
|
811
|
-
|
|
812
|
-
if (numItemsUploading >= UPLOAD_CONCURRENCY) {
|
|
813
|
-
return;
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
const uploadOptions: Object = {
|
|
817
|
-
file,
|
|
818
|
-
folderId: options && options.folderId ? options.folderId : rootFolderId,
|
|
819
|
-
errorCallback: error => this.handleUploadError(item, error),
|
|
820
|
-
progressCallback: event => this.handleUploadProgress(item, event),
|
|
821
|
-
successCallback: entries => this.handleUploadSuccess(item, entries),
|
|
822
|
-
overwrite,
|
|
823
|
-
fileId: options && options.fileId ? options.fileId : null,
|
|
824
|
-
};
|
|
825
|
-
|
|
826
|
-
item.status = STATUS_IN_PROGRESS;
|
|
827
|
-
items[items.indexOf(item)] = item;
|
|
828
|
-
|
|
829
|
-
api.upload(uploadOptions);
|
|
830
|
-
|
|
831
|
-
this.updateViewAndCollection(items);
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
/**
|
|
835
|
-
* Helper to resume uploading a single file.
|
|
836
|
-
*
|
|
837
|
-
* @param {UploadItem} item - Upload item object
|
|
838
|
-
* @return {void}
|
|
839
|
-
*/
|
|
840
|
-
resumeFile(item: UploadItem) {
|
|
841
|
-
const { overwrite, rootFolderId, onResume } = this.props;
|
|
842
|
-
const { api, file, options } = item;
|
|
843
|
-
const { items } = this.state;
|
|
844
|
-
|
|
845
|
-
const numItemsUploading = items.filter(item_t => item_t.status === STATUS_IN_PROGRESS).length;
|
|
846
|
-
|
|
847
|
-
if (numItemsUploading >= UPLOAD_CONCURRENCY) {
|
|
848
|
-
return;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
const resumeOptions: Object = {
|
|
852
|
-
file,
|
|
853
|
-
folderId: options && options.folderId ? options.folderId : rootFolderId,
|
|
854
|
-
errorCallback: error => this.handleUploadError(item, error),
|
|
855
|
-
progressCallback: event => this.handleUploadProgress(item, event),
|
|
856
|
-
successCallback: entries => this.handleUploadSuccess(item, entries),
|
|
857
|
-
overwrite,
|
|
858
|
-
sessionId: api && api.sessionId ? api.sessionId : null,
|
|
859
|
-
fileId: options && options.fileId ? options.fileId : null,
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
item.status = STATUS_IN_PROGRESS;
|
|
863
|
-
delete item.error;
|
|
864
|
-
items[items.indexOf(item)] = item;
|
|
865
|
-
|
|
866
|
-
onResume(item);
|
|
867
|
-
api.resume(resumeOptions);
|
|
868
|
-
|
|
869
|
-
this.updateViewAndCollection(items);
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* Helper to reset a file. Cancels any current upload and resets progress.
|
|
874
|
-
*
|
|
875
|
-
* @param {UploadItem} item - Upload item to reset
|
|
876
|
-
* @return {void}
|
|
877
|
-
*/
|
|
878
|
-
resetFile(item: UploadItem) {
|
|
879
|
-
const { api, file, options } = item;
|
|
880
|
-
if (api && typeof api.cancel === 'function') {
|
|
881
|
-
api.cancel();
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
// Reset API, progress & status
|
|
885
|
-
item.api = this.getUploadAPI(file, options);
|
|
886
|
-
item.progress = 0;
|
|
887
|
-
item.status = STATUS_PENDING;
|
|
888
|
-
delete item.error;
|
|
889
|
-
|
|
890
|
-
const { items } = this.state;
|
|
891
|
-
items[items.indexOf(item)] = item;
|
|
892
|
-
|
|
893
|
-
this.updateViewAndCollection(items);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
/**
|
|
897
|
-
* Handles a successful upload.
|
|
898
|
-
*
|
|
899
|
-
* @private
|
|
900
|
-
* @param {UploadItem} item - Upload item corresponding to success event
|
|
901
|
-
* @param {BoxItem[]} entries - Successfully uploaded Box File objects
|
|
902
|
-
* @return {void}
|
|
903
|
-
*/
|
|
904
|
-
handleUploadSuccess = (item: UploadItem, entries?: BoxItem[]) => {
|
|
905
|
-
const { onUpload, useUploadsManager } = this.props;
|
|
906
|
-
|
|
907
|
-
item.progress = 100;
|
|
908
|
-
if (!item.error) {
|
|
909
|
-
item.status = STATUS_COMPLETE;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
// Cache Box File object of successfully uploaded item
|
|
913
|
-
if (entries && entries.length === 1) {
|
|
914
|
-
const [boxFile] = entries;
|
|
915
|
-
item.boxFile = boxFile;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
const { items } = this.state;
|
|
919
|
-
items[items.indexOf(item)] = item;
|
|
920
|
-
|
|
921
|
-
// Broadcast that a file has been uploaded
|
|
922
|
-
if (useUploadsManager) {
|
|
923
|
-
onUpload(item);
|
|
924
|
-
this.checkClearUploadItems();
|
|
925
|
-
} else {
|
|
926
|
-
onUpload(item.boxFile);
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
this.updateViewAndCollection(items, () => {
|
|
930
|
-
const { view } = this.state;
|
|
931
|
-
if (view === VIEW_UPLOAD_IN_PROGRESS) {
|
|
932
|
-
this.upload();
|
|
933
|
-
}
|
|
934
|
-
});
|
|
935
|
-
};
|
|
936
|
-
|
|
937
|
-
resetUploadManagerExpandState = () => {
|
|
938
|
-
this.isAutoExpanded = false;
|
|
939
|
-
this.setState({
|
|
940
|
-
isUploadsManagerExpanded: false,
|
|
941
|
-
});
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
/**
|
|
945
|
-
* Updates view and internal upload collection with provided items.
|
|
946
|
-
*
|
|
947
|
-
* @private
|
|
948
|
-
* @param {UploadItem[]} item - Items to update collection with
|
|
949
|
-
* @param {Function} callback
|
|
950
|
-
* @return {void}
|
|
951
|
-
*/
|
|
952
|
-
updateViewAndCollection(items: UploadItem[], callback?: Function) {
|
|
953
|
-
const { onComplete, useUploadsManager, isResumableUploadsEnabled, isPartialUploadEnabled }: Props = this.props;
|
|
954
|
-
const someUploadIsInProgress = items.some(uploadItem => uploadItem.status !== STATUS_COMPLETE);
|
|
955
|
-
const someUploadHasFailed = items.some(uploadItem => uploadItem.status === STATUS_ERROR);
|
|
956
|
-
const allItemsArePending = !items.some(uploadItem => uploadItem.status !== STATUS_PENDING);
|
|
957
|
-
const noFileIsPendingOrInProgress = items.every(
|
|
958
|
-
uploadItem => uploadItem.status !== STATUS_PENDING && uploadItem.status !== STATUS_IN_PROGRESS,
|
|
959
|
-
);
|
|
960
|
-
const areAllItemsFinished = items.every(
|
|
961
|
-
uploadItem => uploadItem.status === STATUS_COMPLETE || uploadItem.status === STATUS_ERROR,
|
|
962
|
-
);
|
|
963
|
-
const uploadItemsStatus = isResumableUploadsEnabled ? areAllItemsFinished : noFileIsPendingOrInProgress;
|
|
964
|
-
|
|
965
|
-
let view = '';
|
|
966
|
-
if ((items && items.length === 0) || allItemsArePending) {
|
|
967
|
-
view = VIEW_UPLOAD_EMPTY;
|
|
968
|
-
} else if (isPartialUploadEnabled && areAllItemsFinished) {
|
|
969
|
-
const filesToBeUploaded = items.filter(item => item.status === STATUS_COMPLETE);
|
|
970
|
-
view = VIEW_UPLOAD_SUCCESS;
|
|
971
|
-
|
|
972
|
-
if (!useUploadsManager) {
|
|
973
|
-
onComplete(cloneDeep(filesToBeUploaded.map(item => item.boxFile)));
|
|
974
|
-
// Reset item collection after successful upload
|
|
975
|
-
items = [];
|
|
976
|
-
}
|
|
977
|
-
} else if (someUploadHasFailed && useUploadsManager) {
|
|
978
|
-
view = VIEW_ERROR;
|
|
979
|
-
} else if (someUploadIsInProgress) {
|
|
980
|
-
view = VIEW_UPLOAD_IN_PROGRESS;
|
|
981
|
-
} else {
|
|
982
|
-
view = VIEW_UPLOAD_SUCCESS;
|
|
983
|
-
|
|
984
|
-
if (!useUploadsManager) {
|
|
985
|
-
onComplete(cloneDeep(items.map(item => item.boxFile)));
|
|
986
|
-
// Reset item collection after successful upload
|
|
987
|
-
items = [];
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
if (uploadItemsStatus && useUploadsManager) {
|
|
992
|
-
if (this.isAutoExpanded) {
|
|
993
|
-
this.resetUploadManagerExpandState();
|
|
994
|
-
} // Else manually expanded so don't close
|
|
995
|
-
onComplete(items);
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
const state: Object = {
|
|
999
|
-
items,
|
|
1000
|
-
view,
|
|
1001
|
-
};
|
|
1002
|
-
|
|
1003
|
-
if (items.length === 0) {
|
|
1004
|
-
state.itemIds = {};
|
|
1005
|
-
state.errorCode = '';
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
this.setState(state, callback);
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
/**
|
|
1012
|
-
* Handles an upload error.
|
|
1013
|
-
*
|
|
1014
|
-
* @private
|
|
1015
|
-
* @param {UploadItem} item - Upload item corresponding to error
|
|
1016
|
-
* @param {Error} error - Upload error
|
|
1017
|
-
* @return {void}
|
|
1018
|
-
*/
|
|
1019
|
-
handleUploadError = (item: UploadItem, error: Error) => {
|
|
1020
|
-
const { onError, useUploadsManager } = this.props;
|
|
1021
|
-
const { file } = item;
|
|
1022
|
-
const { items } = this.state;
|
|
1023
|
-
|
|
1024
|
-
item.status = STATUS_ERROR;
|
|
1025
|
-
item.error = error;
|
|
1026
|
-
|
|
1027
|
-
const newItems = [...items];
|
|
1028
|
-
const index = newItems.findIndex(singleItem => singleItem === item);
|
|
1029
|
-
if (index !== -1) {
|
|
1030
|
-
newItems[index] = item;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
// Broadcast that there was an error uploading a file
|
|
1034
|
-
const errorData = useUploadsManager
|
|
1035
|
-
? {
|
|
1036
|
-
item,
|
|
1037
|
-
error,
|
|
1038
|
-
}
|
|
1039
|
-
: {
|
|
1040
|
-
file,
|
|
1041
|
-
error,
|
|
1042
|
-
};
|
|
1043
|
-
|
|
1044
|
-
onError(errorData);
|
|
1045
|
-
|
|
1046
|
-
this.updateViewAndCollection(newItems, () => {
|
|
1047
|
-
if (useUploadsManager) {
|
|
1048
|
-
this.isAutoExpanded = true;
|
|
1049
|
-
this.expandUploadsManager();
|
|
1050
|
-
}
|
|
1051
|
-
const { view } = this.state;
|
|
1052
|
-
if (view === VIEW_UPLOAD_IN_PROGRESS) {
|
|
1053
|
-
this.upload();
|
|
1054
|
-
}
|
|
1055
|
-
});
|
|
1056
|
-
};
|
|
1057
|
-
|
|
1058
|
-
/**
|
|
1059
|
-
* Handles an upload progress event.
|
|
1060
|
-
*
|
|
1061
|
-
* @private
|
|
1062
|
-
* @param {UploadItem} item - Upload item corresponding to progress event
|
|
1063
|
-
* @param {ProgressEvent} event - Progress event
|
|
1064
|
-
* @return {void}
|
|
1065
|
-
*/
|
|
1066
|
-
handleUploadProgress = (item: UploadItem, event: any) => {
|
|
1067
|
-
if (!event.total || item.status === STATUS_COMPLETE || item.status === STATUS_STAGED) {
|
|
1068
|
-
return;
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
item.progress = Math.min(Math.round((event.loaded / event.total) * 100), 100);
|
|
1072
|
-
item.status = item.progress === 100 ? STATUS_STAGED : STATUS_IN_PROGRESS;
|
|
1073
|
-
|
|
1074
|
-
const { onProgress } = this.props;
|
|
1075
|
-
onProgress(item);
|
|
1076
|
-
|
|
1077
|
-
const { items } = this.state;
|
|
1078
|
-
items[items.indexOf(item)] = item;
|
|
1079
|
-
|
|
1080
|
-
this.updateViewAndCollection(items);
|
|
1081
|
-
};
|
|
1082
|
-
|
|
1083
|
-
/**
|
|
1084
|
-
* Updates item based on its status.
|
|
1085
|
-
*
|
|
1086
|
-
* @private
|
|
1087
|
-
* @param {UploadItem} item - The upload item to update
|
|
1088
|
-
* @return {void}
|
|
1089
|
-
*/
|
|
1090
|
-
onClick = (item: UploadItem) => {
|
|
1091
|
-
const { chunked, isResumableUploadsEnabled, onClickCancel, onClickResume, onClickRetry } = this.props;
|
|
1092
|
-
const { status, file } = item;
|
|
1093
|
-
const isChunkedUpload =
|
|
1094
|
-
chunked && !item.isFolder && file.size > CHUNKED_UPLOAD_MIN_SIZE_BYTES && isMultiputSupported();
|
|
1095
|
-
const isResumable = isResumableUploadsEnabled && isChunkedUpload && item.api.sessionId;
|
|
1096
|
-
|
|
1097
|
-
switch (status) {
|
|
1098
|
-
case STATUS_IN_PROGRESS:
|
|
1099
|
-
case STATUS_STAGED:
|
|
1100
|
-
case STATUS_COMPLETE:
|
|
1101
|
-
case STATUS_PENDING:
|
|
1102
|
-
this.removeFileFromUploadQueue(item);
|
|
1103
|
-
onClickCancel(item);
|
|
1104
|
-
break;
|
|
1105
|
-
case STATUS_ERROR:
|
|
1106
|
-
if (isResumable) {
|
|
1107
|
-
item.bytesUploadedOnLastResume = item.api.totalUploadedBytes;
|
|
1108
|
-
this.resumeFile(item);
|
|
1109
|
-
onClickResume(item);
|
|
1110
|
-
} else {
|
|
1111
|
-
this.resetFile(item);
|
|
1112
|
-
this.uploadFile(item);
|
|
1113
|
-
onClickRetry(item);
|
|
1114
|
-
}
|
|
1115
|
-
break;
|
|
1116
|
-
default:
|
|
1117
|
-
break;
|
|
1118
|
-
}
|
|
1119
|
-
};
|
|
1120
|
-
|
|
1121
|
-
/**
|
|
1122
|
-
* Click action button for all uploads in the Uploads Manager with the given status.
|
|
1123
|
-
*
|
|
1124
|
-
* @private
|
|
1125
|
-
* @param {UploadStatus} - the status that items should have for their action button to be clicked
|
|
1126
|
-
* @return {void}
|
|
1127
|
-
*/
|
|
1128
|
-
clickAllWithStatus = (status?: UploadStatus) => {
|
|
1129
|
-
const { items } = this.state;
|
|
1130
|
-
|
|
1131
|
-
items.forEach(item => {
|
|
1132
|
-
if (!status || item.status === status) {
|
|
1133
|
-
this.onClick(item);
|
|
1134
|
-
}
|
|
1135
|
-
});
|
|
1136
|
-
};
|
|
1137
|
-
|
|
1138
|
-
/**
|
|
1139
|
-
* Expands the upload manager
|
|
1140
|
-
*
|
|
1141
|
-
* @return {void}
|
|
1142
|
-
*/
|
|
1143
|
-
expandUploadsManager = (): void => {
|
|
1144
|
-
const { useUploadsManager } = this.props;
|
|
1145
|
-
|
|
1146
|
-
if (!useUploadsManager) {
|
|
1147
|
-
return;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
clearTimeout(this.resetItemsTimeout);
|
|
1151
|
-
|
|
1152
|
-
this.setState({ isUploadsManagerExpanded: true });
|
|
1153
|
-
};
|
|
1154
|
-
|
|
1155
|
-
/**
|
|
1156
|
-
* Minimizes the upload manager
|
|
1157
|
-
*
|
|
1158
|
-
* @return {void}
|
|
1159
|
-
*/
|
|
1160
|
-
minimizeUploadsManager = (): void => {
|
|
1161
|
-
const { useUploadsManager, onMinimize } = this.props;
|
|
1162
|
-
|
|
1163
|
-
if (!useUploadsManager || !onMinimize) {
|
|
1164
|
-
return;
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
clearTimeout(this.resetItemsTimeout);
|
|
1168
|
-
|
|
1169
|
-
onMinimize();
|
|
1170
|
-
this.resetUploadManagerExpandState();
|
|
1171
|
-
this.checkClearUploadItems();
|
|
1172
|
-
};
|
|
1173
|
-
|
|
1174
|
-
/**
|
|
1175
|
-
* Checks if the upload items should be cleared after a timeout
|
|
1176
|
-
*
|
|
1177
|
-
* @return {void}
|
|
1178
|
-
*/
|
|
1179
|
-
checkClearUploadItems = () => {
|
|
1180
|
-
this.resetItemsTimeout = setTimeout(
|
|
1181
|
-
this.resetUploadsManagerItemsWhenUploadsComplete,
|
|
1182
|
-
HIDE_UPLOAD_MANAGER_DELAY_MS_DEFAULT,
|
|
1183
|
-
);
|
|
1184
|
-
};
|
|
1185
|
-
|
|
1186
|
-
/**
|
|
1187
|
-
* Toggles the upload manager
|
|
1188
|
-
*
|
|
1189
|
-
* @return {void}
|
|
1190
|
-
*/
|
|
1191
|
-
toggleUploadsManager = (): void => {
|
|
1192
|
-
const { isUploadsManagerExpanded } = this.state;
|
|
1193
|
-
|
|
1194
|
-
if (isUploadsManagerExpanded) {
|
|
1195
|
-
this.minimizeUploadsManager();
|
|
1196
|
-
} else {
|
|
1197
|
-
this.expandUploadsManager();
|
|
1198
|
-
}
|
|
1199
|
-
};
|
|
1200
|
-
|
|
1201
|
-
/**
|
|
1202
|
-
* Empties the items queue
|
|
1203
|
-
*
|
|
1204
|
-
* @return {void}
|
|
1205
|
-
*/
|
|
1206
|
-
resetUploadsManagerItemsWhenUploadsComplete = (): void => {
|
|
1207
|
-
const { view, items, isUploadsManagerExpanded } = this.state;
|
|
1208
|
-
const { useUploadsManager, onCancel } = this.props;
|
|
1209
|
-
|
|
1210
|
-
// Do not reset items when upload manger is expanded or there're uploads in progress
|
|
1211
|
-
if ((isUploadsManagerExpanded && useUploadsManager && !!items.length) || view === VIEW_UPLOAD_IN_PROGRESS) {
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
onCancel(items);
|
|
1216
|
-
|
|
1217
|
-
this.setState({
|
|
1218
|
-
items: [],
|
|
1219
|
-
itemIds: {},
|
|
1220
|
-
});
|
|
1221
|
-
};
|
|
1222
|
-
|
|
1223
|
-
/**
|
|
1224
|
-
* Adds file to the upload queue and starts upload immediately
|
|
1225
|
-
*
|
|
1226
|
-
* @param {Array<UploadFileWithAPIOptions | File>} files - Files to be added to upload queue
|
|
1227
|
-
* @return {void}
|
|
1228
|
-
*/
|
|
1229
|
-
addFilesWithOptionsToUploadQueueAndStartUpload = (
|
|
1230
|
-
files?: Array<UploadFileWithAPIOptions | File>,
|
|
1231
|
-
dataTransferItems: Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>,
|
|
1232
|
-
): void => {
|
|
1233
|
-
this.addFilesToUploadQueue(files, this.upload);
|
|
1234
|
-
this.addDataTransferItemsToUploadQueue(dataTransferItems, this.upload);
|
|
1235
|
-
};
|
|
1236
|
-
|
|
1237
|
-
/**
|
|
1238
|
-
* Renders the content uploader
|
|
1239
|
-
*
|
|
1240
|
-
* @inheritdoc
|
|
1241
|
-
* @return {Component}
|
|
1242
|
-
*/
|
|
1243
|
-
render() {
|
|
1244
|
-
const {
|
|
1245
|
-
className,
|
|
1246
|
-
fileLimit,
|
|
1247
|
-
isDraggingItemsToUploadsManager = false,
|
|
1248
|
-
isFolderUploadEnabled,
|
|
1249
|
-
isResumableUploadsEnabled,
|
|
1250
|
-
isTouch,
|
|
1251
|
-
language,
|
|
1252
|
-
measureRef,
|
|
1253
|
-
messages,
|
|
1254
|
-
onClose,
|
|
1255
|
-
onUpgradeCTAClick,
|
|
1256
|
-
useUploadsManager,
|
|
1257
|
-
}: Props = this.props;
|
|
1258
|
-
const { view, items, errorCode, isUploadsManagerExpanded }: State = this.state;
|
|
1259
|
-
const isEmpty = items.length === 0;
|
|
1260
|
-
const isVisible = !isEmpty || !!isDraggingItemsToUploadsManager;
|
|
1261
|
-
|
|
1262
|
-
const hasFiles = items.length !== 0;
|
|
1263
|
-
const isLoading = items.some(item => item.status === STATUS_IN_PROGRESS);
|
|
1264
|
-
const isDone = items.every(item => item.status === STATUS_COMPLETE || item.status === STATUS_STAGED);
|
|
1265
|
-
|
|
1266
|
-
const styleClassName = classNames('bcu', className, {
|
|
1267
|
-
'be-app-element': !useUploadsManager,
|
|
1268
|
-
be: !useUploadsManager,
|
|
1269
|
-
});
|
|
1270
|
-
|
|
1271
|
-
return (
|
|
1272
|
-
<Internationalize language={language} messages={messages}>
|
|
1273
|
-
<TooltipProvider>
|
|
1274
|
-
{useUploadsManager ? (
|
|
1275
|
-
<div ref={measureRef} className={styleClassName} id={this.id}>
|
|
1276
|
-
<UploadsManager
|
|
1277
|
-
isDragging={isDraggingItemsToUploadsManager}
|
|
1278
|
-
isExpanded={isUploadsManagerExpanded}
|
|
1279
|
-
isResumableUploadsEnabled={isResumableUploadsEnabled}
|
|
1280
|
-
isVisible={isVisible}
|
|
1281
|
-
items={items}
|
|
1282
|
-
onItemActionClick={this.onClick}
|
|
1283
|
-
onRemoveActionClick={this.removeFileFromUploadQueue}
|
|
1284
|
-
onUpgradeCTAClick={onUpgradeCTAClick}
|
|
1285
|
-
onUploadsManagerActionClick={this.clickAllWithStatus}
|
|
1286
|
-
toggleUploadsManager={this.toggleUploadsManager}
|
|
1287
|
-
view={view}
|
|
1288
|
-
/>
|
|
1289
|
-
</div>
|
|
1290
|
-
) : (
|
|
1291
|
-
<div ref={measureRef} className={styleClassName} id={this.id}>
|
|
1292
|
-
<DroppableContent
|
|
1293
|
-
addDataTransferItemsToUploadQueue={this.addDroppedItemsToUploadQueue}
|
|
1294
|
-
addFiles={this.addFilesToUploadQueue}
|
|
1295
|
-
allowedTypes={['Files']}
|
|
1296
|
-
isFolderUploadEnabled={isFolderUploadEnabled}
|
|
1297
|
-
isTouch={isTouch}
|
|
1298
|
-
items={items}
|
|
1299
|
-
onClick={this.onClick}
|
|
1300
|
-
view={view}
|
|
1301
|
-
/>
|
|
1302
|
-
<Footer
|
|
1303
|
-
errorCode={errorCode}
|
|
1304
|
-
fileLimit={fileLimit}
|
|
1305
|
-
hasFiles={hasFiles}
|
|
1306
|
-
isLoading={isLoading}
|
|
1307
|
-
onCancel={this.cancel}
|
|
1308
|
-
onClose={onClose}
|
|
1309
|
-
onUpload={this.upload}
|
|
1310
|
-
isDone={isDone}
|
|
1311
|
-
/>
|
|
1312
|
-
</div>
|
|
1313
|
-
)}
|
|
1314
|
-
</TooltipProvider>
|
|
1315
|
-
</Internationalize>
|
|
1316
|
-
);
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
export type ContentUploaderProps = Props;
|
|
1321
|
-
export default makeResponsive(ContentUploader);
|
|
1322
|
-
export { ContentUploader as ContentUploaderComponent, CHUNKED_UPLOAD_MIN_SIZE_BYTES };
|