Skip to content

Commit 4b815a1

Browse files
committed
Feature: dropzone now supports folder upload
1 parent 4320539 commit 4b815a1

File tree

2 files changed

+78
-46
lines changed

2 files changed

+78
-46
lines changed

packages/uui-file-dropzone/lib/UUIFileDropzoneEvent.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { UUIEvent } from '@umbraco-ui/uui-base/lib/events';
2-
import { UUIFileDropzoneElement } from './uui-file-dropzone.element';
2+
import {
3+
UUIFileDropzoneElement,
4+
UUIFileFolder,
5+
} from './uui-file-dropzone.element';
36

47
export class UUIFileDropzoneEvent extends UUIEvent<
5-
{ files: File[] },
8+
{ files: File[]; folders: UUIFileFolder[] },
69
UUIFileDropzoneElement
710
> {
811
public static readonly CHANGE: string = 'change';

packages/uui-file-dropzone/lib/uui-file-dropzone.element.ts

+73-44
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ import { UUIFileDropzoneEvent } from './UUIFileDropzoneEvent';
55
import { LabelMixin } from '@umbraco-ui/uui-base/lib/mixins';
66
import { demandCustomElement } from '@umbraco-ui/uui-base/lib/utils';
77

8+
export interface UUIFileFolder {
9+
folderName: string;
10+
folders: UUIFileFolder[];
11+
files: File[];
12+
}
13+
814
/**
915
* @element uui-file-dropzone
1016
* @fires {UUIFileDropzoneEvent} change - fires when the a file has been selected.
@@ -66,6 +72,13 @@ export class UUIFileDropzoneElement extends LabelMixin('', LitElement) {
6672
return this._accept;
6773
}
6874

75+
@property({
76+
type: Boolean,
77+
reflect: true,
78+
attribute: 'disallow-folder-upload',
79+
})
80+
public disallowFolderUpload: boolean = false;
81+
6982
/**
7083
* Allows for multiple files to be selected.
7184
* @type {boolean}
@@ -97,59 +110,69 @@ export class UUIFileDropzoneElement extends LabelMixin('', LitElement) {
97110
demandCustomElement(this, 'uui-symbol-file-dropzone');
98111
}
99112

100-
private async _getAllFileEntries(
101-
dataTransferItemList: DataTransferItemList,
102-
): Promise<File[]> {
103-
const fileEntries: File[] = [];
113+
private async _getAllEntries(dataTransferItemList: DataTransferItemList) {
104114
// Use BFS to traverse entire directory/file structure
105115
const queue = [...dataTransferItemList];
106116

107-
while (queue.length > 0) {
108-
const entry = queue.shift()!;
117+
const folders: UUIFileFolder[] = [];
118+
const files: File[] = [];
109119

110-
if (entry.kind === 'file') {
120+
for (const entry of queue) {
121+
if (entry.type) {
122+
// Entry is a file
111123
const file = entry.getAsFile();
112124
if (!file) continue;
113125
if (this._isAccepted(file)) {
114-
fileEntries.push(file);
126+
files.push(file);
127+
}
128+
} else if (!entry.type && !this.disallowFolderUpload) {
129+
// Entry is a directive. The entry kind is "file" for both files and directories which seems like a bug. The file type is empty however. Can we trust this?
130+
if ('webkitGetAsEntry' in entry === true) {
131+
const dir = entry.webkitGetAsEntry() as FileSystemDirectoryEntry;
132+
folders.push(await this._mkdir(dir));
133+
} else if ('getAsEntry' in entry === true) {
134+
// non-WebKit browsers may rename webkitGetAsEntry to getAsEntry. MDN recommends looking for both.
135+
//@ts-ignore
136+
const dir = entry.getAsEntry() as FileSystemDirectoryEntry;
137+
folders.push(await this._mkdir(dir));
115138
}
116-
} else if (entry.kind === 'directory') {
117-
if ('webkitGetAsEntry' in entry === false) continue;
118-
const directory = entry.webkitGetAsEntry()! as FileSystemDirectoryEntry;
119-
queue.push(
120-
...(await this._readAllDirectoryEntries(directory.createReader())),
121-
);
122139
}
123140
}
124-
125-
return fileEntries;
141+
return { files, folders };
126142
}
127143

128-
// Get all the entries (files or sub-directories) in a directory
129-
// by calling readEntries until it returns empty array
130-
private async _readAllDirectoryEntries(
131-
directoryReader: FileSystemDirectoryReader,
132-
) {
133-
const entries: any = [];
134-
let readEntries: any = await this._readEntriesPromise(directoryReader);
135-
while (readEntries.length > 0) {
136-
entries.push(...readEntries);
137-
readEntries = await this._readEntriesPromise(directoryReader);
138-
}
139-
return entries;
140-
}
144+
// Make directory structure
145+
private async _mkdir(
146+
entry: FileSystemDirectoryEntry,
147+
): Promise<UUIFileFolder> {
148+
const reader = entry.createReader();
149+
const folders: UUIFileFolder[] = [];
150+
const files: File[] = [];
151+
152+
const readEntries = (reader: FileSystemDirectoryReader) => {
153+
reader.readEntries(async entries => {
154+
if (!entries.length) return;
155+
156+
for (const en of entries) {
157+
if (en.isFile) {
158+
const file = await this._getAsFile(en as FileSystemFileEntry);
159+
if (this._isAccepted(file)) {
160+
files.push(file);
161+
}
162+
} else if (en.isDirectory) {
163+
const directory = await this._mkdir(en as FileSystemDirectoryEntry);
164+
folders.push(directory);
165+
}
166+
}
141167

142-
private async _readEntriesPromise(
143-
directoryReader: FileSystemDirectoryReader,
144-
) {
145-
return new Promise((resolve, reject) => {
146-
try {
147-
directoryReader.readEntries(resolve, reject);
148-
} catch (err) {
149-
console.log(err);
150-
reject(err);
151-
}
152-
});
168+
readEntries(reader);
169+
});
170+
};
171+
172+
readEntries(reader);
173+
174+
const result: UUIFileFolder = { folderName: entry.name, folders, files };
175+
return result;
153176
}
154177

155178
private _isAccepted(file: File) {
@@ -184,22 +207,28 @@ export class UUIFileDropzoneElement extends LabelMixin('', LitElement) {
184207
return false;
185208
}
186209

210+
private async _getAsFile(fileEntry: FileSystemFileEntry): Promise<File> {
211+
return new Promise((resolve, reject) => fileEntry.file(resolve, reject));
212+
}
213+
187214
private async _onDrop(e: DragEvent) {
188215
e.preventDefault();
189216
this._dropzone.classList.remove('hover');
190217

191218
const items = e.dataTransfer?.items;
192219

193220
if (items) {
194-
let result = await this._getAllFileEntries(items);
221+
const fileSystemResult = await this._getAllEntries(items);
195222

196-
if (this.multiple === false && result.length) {
197-
result = [result[0]];
223+
if (this.multiple === false && fileSystemResult.files.length) {
224+
fileSystemResult.files = [fileSystemResult.files[0]];
198225
}
199226

227+
this._getAllEntries(items);
228+
200229
this.dispatchEvent(
201230
new UUIFileDropzoneEvent(UUIFileDropzoneEvent.CHANGE, {
202-
detail: { files: result },
231+
detail: fileSystemResult,
203232
}),
204233
);
205234
}

0 commit comments

Comments
 (0)