Skip to content

Commit

Permalink
fix: upload drag directory can not work (#562)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
huiliangShen authored Aug 13, 2024
1 parent 8b516f7 commit a0439eb
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 35 deletions.
6 changes: 3 additions & 3 deletions src/AjaxUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class AjaxUploader extends Component<UploadProps> {
}
};

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

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

if (this.props.directory) {
traverseFileTree(
const files = await traverseFileTree(
Array.prototype.slice.call(e.dataTransfer.items),
this.uploadFiles,
(_file: RcFile) => attrAccept(_file, this.props.accept),
);
this.uploadFiles(files);
} else {
let files = [...e.dataTransfer.files].filter((file: RcFile) =>
attrAccept(file, this.props.accept),
Expand Down
79 changes: 47 additions & 32 deletions src/traverseFileTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,35 @@ interface InternalDataTransferItem extends DataTransferItem {
path: string;
}

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

function sequence() {
dirReader.readEntries((entries: InternalDataTransferItem[]) => {
const entryList = Array.prototype.slice.apply(entries);
async function readDirectory(directory: InternalDataTransferItem) {
const dirReader = directory.createReader();
const entries = [];

progressFileList.push(...entryList);
// Check if all the file has been viewed
const isFinished = !entryList.length;
if (!isFinished) {
sequence();
}
while (true) {
const results = await new Promise<InternalDataTransferItem[]>((resolve) => {
dirReader.readEntries(resolve, () => resolve([]));
});
}
const n = results.length;

if (!n) {
break;
}

sequence();
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const _traverseFileTree = (item: InternalDataTransferItem, path?: string) => {
if (!item) {
return;
for (let i = 0; i < n; i++) {
entries.push(results[i]);
}
}
// eslint-disable-next-line no-param-reassign
item.path = path || '';
if (item.isFile) {
return entries;
}

async function readFile(item: InternalDataTransferItem) {
return new Promise<RcFile & { webkitRelativePath?: string }>(reslove => {
item.file(file => {
if (isAccepted(file)) {
// https://github.com/ant-design/ant-design/issues/16426
Expand All @@ -57,23 +56,39 @@ const traverseFileTree = (files: InternalDataTransferItem[], callback, isAccepte
},
});
}
flattenFileList.push(file);
reslove(file);
} else {
reslove(null);
}
});
});
}

// eslint-disable-next-line @typescript-eslint/naming-convention
const _traverseFileTree = async (item: InternalDataTransferItem, path?: string) => {
if (!item) {
return;
}
// eslint-disable-next-line no-param-reassign
item.path = path || '';
if (item.isFile) {
const file = await readFile(item);
if (file) {
flattenFileList.push(file);
}
} else if (item.isDirectory) {
loopFiles(item);
const entries = await readDirectory(item);
progressFileList.push(...entries);
}
};

function walkFiles() {
let wipIndex = 0;
while (wipIndex < progressFileList.length) {
_traverseFileTree(progressFileList[wipIndex]);
wipIndex++;
}
callback(flattenFileList);
let wipIndex = 0;
while (wipIndex < progressFileList.length) {
await _traverseFileTree(progressFileList[wipIndex]);
wipIndex++;
}
walkFiles();

return flattenFileList;
};

export default traverseFileTree;
126 changes: 126 additions & 0 deletions tests/uploader.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,49 @@ const makeFileSystemEntry = item => {
return ret;
};

const makeFileSystemEntryAsync = item => {
const isDirectory = Array.isArray(item.children);
const ret = {
isDirectory,
isFile: !isDirectory,
file: handle => {
handle(new Item(item.name));
},
createReader: () => {
let first = true;
return {
async readEntries(handle, error) {
await sleep(100);

if (!first) {
return handle([]);
}

if (item.error && first) {
return error && error(new Error('read file error'))
}

first = false;
return handle(item.children.map(makeFileSystemEntryAsync));
},
};
},
};
return ret;
};

const makeDataTransferItem = item => {
return {
webkitGetAsEntry: () => makeFileSystemEntry(item),
};
};

const makeDataTransferItemAsync = item => {
return {
webkitGetAsEntry: () => makeFileSystemEntryAsync(item),
};
};

describe('uploader', () => {
let requests;
let xhr;
Expand Down Expand Up @@ -462,6 +499,95 @@ describe('uploader', () => {
}, 100);
});

it('dragging and dropping files to upload through asynchronous file reading is run normal', done => {
const input = uploader.container.querySelector('input')!;

const files = {
name: 'foo',
children: [
{
name: 'bar',
children: [
{
name: '1.png',
},
{
name: '2.png',
},
{
name: 'rc',
children: [
{
name: '5.webp',
},
{
name: '4.webp',
},
],
},
],
},
],
};
fireEvent.drop(input, { dataTransfer: { items: [makeDataTransferItemAsync(files)] } });
const mockStart = jest.fn();
handlers.onStart = mockStart;

setTimeout(() => {
expect(mockStart.mock.calls.length).toBe(2);
done();
}, 1000);
});

it('dragging and dropping files to upload through asynchronous file reading with some readEntries method throw error', (done) => {
const input = uploader.container.querySelector('input')!;

const files = {
name: 'foo',
children: [
{
name: 'bar',
error: true,
children: [
{
name: '1.png',
},
{
name: 'ffc',
children: [
{
name: '7.png',
},
{
name: '8.png',
},
],
}
],
},
{
name: 'rc',
children: [
{
name: '3.png',
},
{
name: '4.webp',
},
],
},
],
};
fireEvent.drop(input, { dataTransfer: { items: [makeDataTransferItemAsync(files)] } });
const mockStart = jest.fn();
handlers.onStart = mockStart;

setTimeout(() => {
expect(mockStart.mock.calls.length).toBe(1);
done();
}, 1000);
});

it('unaccepted type files to upload will not trigger onStart when select directory', done => {
const input = uploader.container.querySelector('input')!;
const files = [
Expand Down

0 comments on commit a0439eb

Please sign in to comment.