Skip to content

Commit a0439eb

Browse files
authored
fix: upload drag directory can not work (#562)
* fix: upload drag directory can not work * feat: add test case * fix: fix when readEntries method throw error and add new test * refactor: for...of chang to for
1 parent 8b516f7 commit a0439eb

File tree

3 files changed

+176
-35
lines changed

3 files changed

+176
-35
lines changed

src/AjaxUploader.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class AjaxUploader extends Component<UploadProps> {
6666
}
6767
};
6868

69-
onFileDrop = (e: React.DragEvent<HTMLDivElement>) => {
69+
onFileDrop = async (e: React.DragEvent<HTMLDivElement>) => {
7070
const { multiple } = this.props;
7171

7272
e.preventDefault();
@@ -76,11 +76,11 @@ class AjaxUploader extends Component<UploadProps> {
7676
}
7777

7878
if (this.props.directory) {
79-
traverseFileTree(
79+
const files = await traverseFileTree(
8080
Array.prototype.slice.call(e.dataTransfer.items),
81-
this.uploadFiles,
8281
(_file: RcFile) => attrAccept(_file, this.props.accept),
8382
);
83+
this.uploadFiles(files);
8484
} else {
8585
let files = [...e.dataTransfer.files].filter((file: RcFile) =>
8686
attrAccept(file, this.props.accept),

src/traverseFileTree.ts

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,35 @@ interface InternalDataTransferItem extends DataTransferItem {
1010
path: string;
1111
}
1212

13-
const traverseFileTree = (files: InternalDataTransferItem[], callback, isAccepted) => {
13+
// https://github.com/ant-design/ant-design/issues/50080
14+
const traverseFileTree = async (files: InternalDataTransferItem[], isAccepted) => {
1415
const flattenFileList = [];
1516
const progressFileList = [];
1617
files.forEach(file => progressFileList.push(file.webkitGetAsEntry() as any));
17-
function loopFiles(item: InternalDataTransferItem) {
18-
const dirReader = item.createReader();
1918

20-
function sequence() {
21-
dirReader.readEntries((entries: InternalDataTransferItem[]) => {
22-
const entryList = Array.prototype.slice.apply(entries);
19+
async function readDirectory(directory: InternalDataTransferItem) {
20+
const dirReader = directory.createReader();
21+
const entries = [];
2322

24-
progressFileList.push(...entryList);
25-
// Check if all the file has been viewed
26-
const isFinished = !entryList.length;
27-
if (!isFinished) {
28-
sequence();
29-
}
23+
while (true) {
24+
const results = await new Promise<InternalDataTransferItem[]>((resolve) => {
25+
dirReader.readEntries(resolve, () => resolve([]));
3026
});
31-
}
27+
const n = results.length;
28+
29+
if (!n) {
30+
break;
31+
}
3232

33-
sequence();
34-
}
35-
// eslint-disable-next-line @typescript-eslint/naming-convention
36-
const _traverseFileTree = (item: InternalDataTransferItem, path?: string) => {
37-
if (!item) {
38-
return;
33+
for (let i = 0; i < n; i++) {
34+
entries.push(results[i]);
35+
}
3936
}
40-
// eslint-disable-next-line no-param-reassign
41-
item.path = path || '';
42-
if (item.isFile) {
37+
return entries;
38+
}
39+
40+
async function readFile(item: InternalDataTransferItem) {
41+
return new Promise<RcFile & { webkitRelativePath?: string }>(reslove => {
4342
item.file(file => {
4443
if (isAccepted(file)) {
4544
// https://github.com/ant-design/ant-design/issues/16426
@@ -57,23 +56,39 @@ const traverseFileTree = (files: InternalDataTransferItem[], callback, isAccepte
5756
},
5857
});
5958
}
60-
flattenFileList.push(file);
59+
reslove(file);
60+
} else {
61+
reslove(null);
6162
}
6263
});
64+
});
65+
}
66+
67+
// eslint-disable-next-line @typescript-eslint/naming-convention
68+
const _traverseFileTree = async (item: InternalDataTransferItem, path?: string) => {
69+
if (!item) {
70+
return;
71+
}
72+
// eslint-disable-next-line no-param-reassign
73+
item.path = path || '';
74+
if (item.isFile) {
75+
const file = await readFile(item);
76+
if (file) {
77+
flattenFileList.push(file);
78+
}
6379
} else if (item.isDirectory) {
64-
loopFiles(item);
80+
const entries = await readDirectory(item);
81+
progressFileList.push(...entries);
6582
}
6683
};
6784

68-
function walkFiles() {
69-
let wipIndex = 0;
70-
while (wipIndex < progressFileList.length) {
71-
_traverseFileTree(progressFileList[wipIndex]);
72-
wipIndex++;
73-
}
74-
callback(flattenFileList);
85+
let wipIndex = 0;
86+
while (wipIndex < progressFileList.length) {
87+
await _traverseFileTree(progressFileList[wipIndex]);
88+
wipIndex++;
7589
}
76-
walkFiles();
90+
91+
return flattenFileList;
7792
};
7893

7994
export default traverseFileTree;

tests/uploader.spec.tsx

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,49 @@ const makeFileSystemEntry = item => {
3737
return ret;
3838
};
3939

40+
const makeFileSystemEntryAsync = item => {
41+
const isDirectory = Array.isArray(item.children);
42+
const ret = {
43+
isDirectory,
44+
isFile: !isDirectory,
45+
file: handle => {
46+
handle(new Item(item.name));
47+
},
48+
createReader: () => {
49+
let first = true;
50+
return {
51+
async readEntries(handle, error) {
52+
await sleep(100);
53+
54+
if (!first) {
55+
return handle([]);
56+
}
57+
58+
if (item.error && first) {
59+
return error && error(new Error('read file error'))
60+
}
61+
62+
first = false;
63+
return handle(item.children.map(makeFileSystemEntryAsync));
64+
},
65+
};
66+
},
67+
};
68+
return ret;
69+
};
70+
4071
const makeDataTransferItem = item => {
4172
return {
4273
webkitGetAsEntry: () => makeFileSystemEntry(item),
4374
};
4475
};
4576

77+
const makeDataTransferItemAsync = item => {
78+
return {
79+
webkitGetAsEntry: () => makeFileSystemEntryAsync(item),
80+
};
81+
};
82+
4683
describe('uploader', () => {
4784
let requests;
4885
let xhr;
@@ -462,6 +499,95 @@ describe('uploader', () => {
462499
}, 100);
463500
});
464501

502+
it('dragging and dropping files to upload through asynchronous file reading is run normal', done => {
503+
const input = uploader.container.querySelector('input')!;
504+
505+
const files = {
506+
name: 'foo',
507+
children: [
508+
{
509+
name: 'bar',
510+
children: [
511+
{
512+
name: '1.png',
513+
},
514+
{
515+
name: '2.png',
516+
},
517+
{
518+
name: 'rc',
519+
children: [
520+
{
521+
name: '5.webp',
522+
},
523+
{
524+
name: '4.webp',
525+
},
526+
],
527+
},
528+
],
529+
},
530+
],
531+
};
532+
fireEvent.drop(input, { dataTransfer: { items: [makeDataTransferItemAsync(files)] } });
533+
const mockStart = jest.fn();
534+
handlers.onStart = mockStart;
535+
536+
setTimeout(() => {
537+
expect(mockStart.mock.calls.length).toBe(2);
538+
done();
539+
}, 1000);
540+
});
541+
542+
it('dragging and dropping files to upload through asynchronous file reading with some readEntries method throw error', (done) => {
543+
const input = uploader.container.querySelector('input')!;
544+
545+
const files = {
546+
name: 'foo',
547+
children: [
548+
{
549+
name: 'bar',
550+
error: true,
551+
children: [
552+
{
553+
name: '1.png',
554+
},
555+
{
556+
name: 'ffc',
557+
children: [
558+
{
559+
name: '7.png',
560+
},
561+
{
562+
name: '8.png',
563+
},
564+
],
565+
}
566+
],
567+
},
568+
{
569+
name: 'rc',
570+
children: [
571+
{
572+
name: '3.png',
573+
},
574+
{
575+
name: '4.webp',
576+
},
577+
],
578+
},
579+
],
580+
};
581+
fireEvent.drop(input, { dataTransfer: { items: [makeDataTransferItemAsync(files)] } });
582+
const mockStart = jest.fn();
583+
handlers.onStart = mockStart;
584+
585+
setTimeout(() => {
586+
expect(mockStart.mock.calls.length).toBe(1);
587+
done();
588+
}, 1000);
589+
});
590+
465591
it('unaccepted type files to upload will not trigger onStart when select directory', done => {
466592
const input = uploader.container.querySelector('input')!;
467593
const files = [

0 commit comments

Comments
 (0)