Skip to content

Commit 73e5d5f

Browse files
authored
New: ContentUploader fileLimit option (#71)
- Support `fileLimit` option that limits number of files that can be uploaded at once. If more than `fileLimit` files are selected, they will be truncated and a warning message will be shown in the footer. - Add null checks during error handling - Update uploader test file to use simple token generator function
1 parent 87e87d4 commit 73e5d5f

8 files changed

Lines changed: 64 additions & 12 deletions

File tree

src/api/ChunkedUpload.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class ChunkedUpload extends Base {
6767

6868
// Automatically handle name conflict errors
6969
const { response } = error;
70-
if (response.status === 409) {
70+
if (response && response.status === 409) {
7171
const { name } = this.file;
7272

7373
if (this.overwrite) {

src/api/PlainUpload.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ class PlainUpload extends Base {
7878
}
7979

8080
// Automatically handle name conflict errors
81-
if (error.status === 409) {
81+
if (error && error.status === 409) {
8282
if (this.overwrite) {
8383
// Error response contains file ID to upload a new file version for
8484
this.makePreflightRequest({

src/components/ContentUploader/ContentUploader.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type Props = {
4242
clientName: string,
4343
className: string,
4444
chunked: boolean,
45+
fileLimit: number,
4546
onClose: Function,
4647
onComplete: Function,
4748
getLocalizedMessage: Function,
@@ -57,17 +58,20 @@ type DefaultProps = {|
5758
chunked: boolean,
5859
className: string,
5960
clientName: string,
61+
fileLimit: number,
6062
uploadHost: string,
6163
onClose: Function,
6264
onComplete: Function
6365
|};
6466

6567
type State = {
6668
view: View,
67-
items: UploadItem[]
69+
items: UploadItem[],
70+
message: string
6871
};
6972

7073
const CHUNKED_UPLOAD_MIN_SIZE_BYTES = 52428800; // 50MB
74+
const FILE_LIMIT_DEFAULT = 100; // Upload at most 100 files at once by default
7175

7276
class ContentUploader extends Component<DefaultProps, Props, State> {
7377
id: string;
@@ -82,6 +86,7 @@ class ContentUploader extends Component<DefaultProps, Props, State> {
8286
chunked: true,
8387
className: '',
8488
clientName: CLIENT_NAME_CONTENT_UPLOADER,
89+
fileLimit: FILE_LIMIT_DEFAULT,
8590
uploadHost: DEFAULT_HOSTNAME_UPLOAD,
8691
onClose: noop,
8792
onComplete: noop
@@ -98,7 +103,8 @@ class ContentUploader extends Component<DefaultProps, Props, State> {
98103
const { rootFolderId, token } = props;
99104
this.state = {
100105
view: rootFolderId && token ? VIEW_UPLOAD_EMPTY : VIEW_ERROR,
101-
items: []
106+
items: [],
107+
message: ''
102108
};
103109
this.id = uniqueid('bcu_');
104110
}
@@ -162,7 +168,7 @@ class ContentUploader extends Component<DefaultProps, Props, State> {
162168
* @return {void}
163169
*/
164170
addFilesToUploadQueue = (files: File[]) => {
165-
const { chunked } = this.props;
171+
const { chunked, fileLimit, getLocalizedMessage } = this.props;
166172
const { view, items } = this.state;
167173

168174
// Disable chunked upload in IE11 for now until hashing is done in a worker
@@ -207,7 +213,20 @@ class ContentUploader extends Component<DefaultProps, Props, State> {
207213
return uploadItem;
208214
});
209215

210-
this.updateViewAndCollection(items.concat(newItems));
216+
let updatedItems = [];
217+
const totalNumOfItems = items.length + newItems.length;
218+
219+
// Don't add more than fileLimit # of items
220+
if (totalNumOfItems > fileLimit) {
221+
updatedItems = items.concat(newItems.slice(0, fileLimit - items.length));
222+
this.setState({
223+
message: getLocalizedMessage('buik.upload.message.toomanyfiles', { fileLimit })
224+
});
225+
} else {
226+
updatedItems = items.concat(newItems);
227+
}
228+
229+
this.updateViewAndCollection(updatedItems);
211230

212231
// Automatically start upload if other files are being uploaded
213232
if (view === VIEW_UPLOAD_IN_PROGRESS) {
@@ -357,7 +376,7 @@ class ContentUploader extends Component<DefaultProps, Props, State> {
357376
};
358377

359378
/**
360-
* Handles an upload error.
379+
* Handles an upload progress event.
361380
*
362381
* @private
363382
* @param {UploadItem} item - Upload item corresponding to progress event
@@ -409,7 +428,7 @@ class ContentUploader extends Component<DefaultProps, Props, State> {
409428
*/
410429
render() {
411430
const { onClose, getLocalizedMessage, className, measureRef, isTouch }: Props = this.props;
412-
const { view, items }: State = this.state;
431+
const { view, items, message }: State = this.state;
413432

414433
const hasFiles = items.length !== 0;
415434
const isLoading = items.some((item) => item.status === STATUS_IN_PROGRESS);
@@ -431,6 +450,7 @@ class ContentUploader extends Component<DefaultProps, Props, State> {
431450
getLocalizedMessage={getLocalizedMessage}
432451
hasFiles={hasFiles}
433452
isLoading={isLoading}
453+
message={message}
434454
onCancel={this.cancel}
435455
onClose={onClose}
436456
onUpload={this.upload}

src/components/ContentUploader/Footer.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,14 @@ import './Footer.scss';
1010
type Props = {
1111
isLoading: boolean,
1212
hasFiles: boolean,
13+
message: string,
1314
onCancel: Function,
1415
onClose?: Function,
1516
onUpload: Function,
1617
getLocalizedMessage: Function
1718
};
1819

19-
const Footer = ({ isLoading, hasFiles, onCancel, onClose, onUpload, getLocalizedMessage }: Props) =>
20+
const Footer = ({ isLoading, hasFiles, message, onCancel, onClose, onUpload, getLocalizedMessage }: Props) =>
2021
<div className='bcu-footer'>
2122
<div className='bcu-footer-left'>
2223
{onClose
@@ -25,6 +26,9 @@ const Footer = ({ isLoading, hasFiles, onCancel, onClose, onUpload, getLocalized
2526
</Button>
2627
: null}
2728
</div>
29+
<div className='bcu-footer-message'>
30+
{message}
31+
</div>
2832
<div className='bcu-footer-right'>
2933
<Button isDisabled={!hasFiles} onClick={onCancel}>
3034
{getLocalizedMessage('buik.footer.button.cancel.uploads')}

src/components/ContentUploader/Footer.scss

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,16 @@
99
justify-content: space-between;
1010
padding: 0 20px;
1111

12-
.bcu-footer-right .buik-btn {
13-
margin-left: 8px;
12+
.bcu-footer-message {
13+
padding: 0 10px;
14+
text-align: center;
15+
}
16+
17+
.bcu-footer-right {
18+
flex-shrink: 0;
19+
20+
.buik-btn {
21+
margin-left: 8px;
22+
}
1423
}
1524
}

src/i18n/en-US.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ buik.sort.option.name.desc = Name: Z → A
164164
buik.sort.option.size.asc = Size: Smallest → Largest
165165
# Size descending option shown in the share access drop down select.
166166
buik.sort.option.size.desc = Size: Largest → Smallest
167+
# Message shown when too many files are uploaded at once
168+
buik.upload.message.toomanyfiles = You can only upload up to {fileLimit} files at a time.
167169
# Message shown when there are no items to upload
168170
buik.upload.state.empty = Drag and drop files or
169171
# Message shown for upload link when there are no items to upload

src/messages.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,11 @@ const messages: { [string]: IntlDescriptor } = defineMessages({
298298
description: 'Text for create folder dialog button',
299299
defaultMessage: 'Create'
300300
},
301+
'buik.upload.message.toomanyfiles': {
302+
id: 'buik.upload.message.toomanyfiles',
303+
description: 'Message shown when too many files are uploaded at once',
304+
defaultMessage: 'You can only upload up to {fileLimit} files at a time.'
305+
},
301306
'buik.upload.state.error': {
302307
id: 'buik.upload.state.error',
303308
description: 'Message shown when there is a network error when uploading',

test/uploader.html

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,21 @@ <h1>Content Uploader</h1>
7474
localStorage.setItem('token', token);
7575
localStorage.setItem('folderId', folderId);
7676

77+
const typedId = `folder_${folderId}`;
78+
79+
const tokenGenerator = () => {
80+
81+
// Resolve with a map of typed ID (aka file_{FILE_ID} or folder_{FOLDER_ID}) -> token to use
82+
const tokenMap = {
83+
[typedId]: token
84+
}
85+
86+
return Promise.resolve(tokenMap);
87+
};
88+
7789
const uploader1 = new ContentUploader();
7890
document.querySelector('.uploader1').innerHTML = '';
79-
uploader1.show(folderId, token, {
91+
uploader1.show(folderId, tokenGenerator, {
8092
container: '.uploader1'
8193
});
8294
}

0 commit comments

Comments
 (0)