@georapbox/files-dropzone-element 1.0.0
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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/files-dropzone-defined.js +2 -0
- package/dist/files-dropzone-defined.js.map +1 -0
- package/dist/files-dropzone.js +2 -0
- package/dist/files-dropzone.js.map +1 -0
- package/package.json +79 -0
- package/src/files-dropzone-defined.js +5 -0
- package/src/files-dropzone.js +505 -0
- package/src/utils/files-selector.js +272 -0
- package/src/utils/is-valid-file.js +38 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
const COMMON_MIME_TYPES = new Map([
|
|
2
|
+
// https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
|
3
|
+
['aac', 'audio/aac'],
|
|
4
|
+
['abw', 'application/x-abiword'],
|
|
5
|
+
['arc', 'application/x-freearc'],
|
|
6
|
+
['avif', 'image/avif'],
|
|
7
|
+
['avi', 'video/x-msvideo'],
|
|
8
|
+
['azw', 'application/vnd.amazon.ebook'],
|
|
9
|
+
['bin', 'application/octet-stream'],
|
|
10
|
+
['bmp', 'image/bmp'],
|
|
11
|
+
['bz', 'application/x-bzip'],
|
|
12
|
+
['bz2', 'application/x-bzip2'],
|
|
13
|
+
['cda', 'application/x-cdf'],
|
|
14
|
+
['csh', 'application/x-csh'],
|
|
15
|
+
['css', 'text/css'],
|
|
16
|
+
['csv', 'text/csv'],
|
|
17
|
+
['doc', 'application/msword'],
|
|
18
|
+
['docx', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
|
|
19
|
+
['eot', 'application/vnd.ms-fontobject'],
|
|
20
|
+
['epub', 'application/epub+zip'],
|
|
21
|
+
['gz', 'application/gzip'],
|
|
22
|
+
['gif', 'image/gif'],
|
|
23
|
+
['heic', 'image/heic'],
|
|
24
|
+
['heif', 'image/heif'],
|
|
25
|
+
['htm', 'text/html'],
|
|
26
|
+
['html', 'text/html'],
|
|
27
|
+
['ico', 'image/vnd.microsoft.icon'],
|
|
28
|
+
['ics', 'text/calendar'],
|
|
29
|
+
['jar', 'application/java-archive'],
|
|
30
|
+
['jpeg', 'image/jpeg'],
|
|
31
|
+
['jpg', 'image/jpeg'],
|
|
32
|
+
['js', 'text/javascript'],
|
|
33
|
+
['json', 'application/json'],
|
|
34
|
+
['jsonld', 'application/ld+json'],
|
|
35
|
+
['mid', 'audio/midi'],
|
|
36
|
+
['midi', 'audio/midi'],
|
|
37
|
+
['mjs', 'text/javascript'],
|
|
38
|
+
['mp3', 'audio/mpeg'],
|
|
39
|
+
['mp4', 'video/mp4'],
|
|
40
|
+
['mpeg', 'video/mpeg'],
|
|
41
|
+
['mpkg', 'application/vnd.apple.installer+xml'],
|
|
42
|
+
['odp', 'application/vnd.oasis.opendocument.presentation'],
|
|
43
|
+
['ods', 'application/vnd.oasis.opendocument.spreadsheet'],
|
|
44
|
+
['odt', 'application/vnd.oasis.opendocument.text'],
|
|
45
|
+
['oga', 'audio/ogg'],
|
|
46
|
+
['ogv', 'video/ogg'],
|
|
47
|
+
['ogx', 'application/ogg'],
|
|
48
|
+
['opus', 'audio/opus'],
|
|
49
|
+
['otf', 'font/otf'],
|
|
50
|
+
['png', 'image/png'],
|
|
51
|
+
['pdf', 'application/pdf'],
|
|
52
|
+
['php', 'application/x-httpd-php'],
|
|
53
|
+
['ppt', 'application/vnd.ms-powerpoint'],
|
|
54
|
+
['pptx', 'application/vnd.openxmlformats-officedocument.presentationml.presentation'],
|
|
55
|
+
['rar', 'application/vnd.rar'],
|
|
56
|
+
['rtf', 'application/rtf'],
|
|
57
|
+
['sh', 'application/x-sh'],
|
|
58
|
+
['svg', 'image/svg+xml'],
|
|
59
|
+
['swf', 'application/x-shockwave-flash'],
|
|
60
|
+
['tar', 'application/x-tar'],
|
|
61
|
+
['tif', 'image/tiff'],
|
|
62
|
+
['tiff', 'image/tiff'],
|
|
63
|
+
['ts', 'video/mp2t'],
|
|
64
|
+
['ttf', 'font/ttf'],
|
|
65
|
+
['txt', 'text/plain'],
|
|
66
|
+
['vsd', 'application/vnd.visio'],
|
|
67
|
+
['wav', 'audio/wav'],
|
|
68
|
+
['weba', 'audio/webm'],
|
|
69
|
+
['webm', 'video/webm'],
|
|
70
|
+
['webp', 'image/webp'],
|
|
71
|
+
['woff', 'font/woff'],
|
|
72
|
+
['woff2', 'font/woff2'],
|
|
73
|
+
['xhtml', 'application/xhtml+xml'],
|
|
74
|
+
['xls', 'application/vnd.ms-excel'],
|
|
75
|
+
['xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
|
|
76
|
+
['xml', 'application/xml'],
|
|
77
|
+
['xul', 'application/vnd.mozilla.xul+xml'],
|
|
78
|
+
['zip', 'application/zip'],
|
|
79
|
+
['7z', 'application/x-7z-compressed'],
|
|
80
|
+
|
|
81
|
+
// Others
|
|
82
|
+
['mkv', 'video/x-matroska'],
|
|
83
|
+
['mov', 'video/quicktime'],
|
|
84
|
+
['msg', 'application/vnd.ms-outlook']
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const FILES_TO_IGNORE = [
|
|
88
|
+
// Thumbnail cache files for macOS and Windows
|
|
89
|
+
'.DS_Store', // macOs
|
|
90
|
+
'Thumbs.db' // Windows
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Adds a `type` property to the file object if it doesn't have one and the file has an extension.
|
|
95
|
+
* This is needed because Firefox doesn't add a type property to files dragged from the desktop.
|
|
96
|
+
* @bug https://bugzilla.mozilla.org/show_bug.cgi?id=1424689
|
|
97
|
+
*
|
|
98
|
+
* @param {File} file The file object to add the type property to.
|
|
99
|
+
* @returns {File} The file object with the type property added.
|
|
100
|
+
*/
|
|
101
|
+
const toFileWithMimeType = file => {
|
|
102
|
+
const { name } = file;
|
|
103
|
+
const hasExtension = name && name.lastIndexOf('.') !== -1;
|
|
104
|
+
|
|
105
|
+
if (hasExtension && !file.type) {
|
|
106
|
+
const extension = name.split('.').pop().toLowerCase();
|
|
107
|
+
const type = COMMON_MIME_TYPES.get(extension);
|
|
108
|
+
|
|
109
|
+
if (type) {
|
|
110
|
+
Object.defineProperty(file, 'type', {
|
|
111
|
+
value: type,
|
|
112
|
+
writable: false,
|
|
113
|
+
configurable: false,
|
|
114
|
+
enumerable: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return file;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Adds a `path` property to the file object if it doesn't have one.
|
|
124
|
+
* If `path` is not provided, the `webkitRelativePath` property of the file will be used
|
|
125
|
+
* or the file's name if `webkitRelativePath` is not available.
|
|
126
|
+
*
|
|
127
|
+
* @param {File} file The file object to add the path property to.
|
|
128
|
+
* @param {String} [path] The path to set on the file object.
|
|
129
|
+
* @returns {File} The file object with the path property added.
|
|
130
|
+
*/
|
|
131
|
+
const toFileWithPath = (file, path) => {
|
|
132
|
+
const fileWithMimeType = toFileWithMimeType(file);
|
|
133
|
+
|
|
134
|
+
if (typeof fileWithMimeType.path !== 'string') {
|
|
135
|
+
const { webkitRelativePath } = file;
|
|
136
|
+
|
|
137
|
+
Object.defineProperty(fileWithMimeType, 'path', {
|
|
138
|
+
value: typeof path === 'string' ? path : webkitRelativePath || file.name,
|
|
139
|
+
writable: false,
|
|
140
|
+
configurable: false,
|
|
141
|
+
enumerable: true
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return fileWithMimeType;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Wrap `FileSystemDirectoryReader.readEntries` in a promise to make working with read entries easier.
|
|
150
|
+
* https://developer.mozilla.org/docs/Web/API/FileSystemDirectoryReader/readEntries
|
|
151
|
+
*
|
|
152
|
+
* @param {FileSystemDirectoryReader} directoryReader The directory reader to read entries from.
|
|
153
|
+
* @returns {Promise<FileSystemFileEntry[]>} A promise that resolves with an array of `FileSystemFileEntry` objects.
|
|
154
|
+
*/
|
|
155
|
+
const readEntriesPromise = async directoryReader => {
|
|
156
|
+
return await new Promise((resolve, reject) => {
|
|
157
|
+
directoryReader.readEntries(resolve, reject);
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Read all entries in a directory or sub-directory
|
|
163
|
+
* by calling `readEntries` until it returns an empty array.
|
|
164
|
+
*
|
|
165
|
+
* @param {FileSystemDirectoryReader} directoryReader The directory reader to read entries from.
|
|
166
|
+
* @returns {Promise<FileSystemFileEntry[]>} A promise that resolves with an array of `FileSystemFileEntry` objects.
|
|
167
|
+
*/
|
|
168
|
+
const readAllDirectoryEntries = async directoryReader => {
|
|
169
|
+
const entries = [];
|
|
170
|
+
let readEntries = await readEntriesPromise(directoryReader);
|
|
171
|
+
|
|
172
|
+
while (readEntries.length > 0) {
|
|
173
|
+
entries.push(...readEntries);
|
|
174
|
+
readEntries = await readEntriesPromise(directoryReader);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return entries;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get a `File` object from a `FileSystemFileEntry` object.
|
|
182
|
+
*
|
|
183
|
+
* @param {FileSystemFileEntry} fileEntry The file entry to get a `File` object from.
|
|
184
|
+
* @returns {Promise<File>} A promise that resolves with a `File` object.
|
|
185
|
+
*/
|
|
186
|
+
const getFileFromFileEntry = fileEntry => {
|
|
187
|
+
return new Promise((resolve, reject) => {
|
|
188
|
+
fileEntry.file(file => resolve(toFileWithPath(file, fileEntry.fullPath)), reject);
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get an array of `File` objects from a `DataTransferItemList` object.
|
|
194
|
+
*
|
|
195
|
+
* @param {DataTransferItemList} dataTransferItemList The item list to get an array of `File` objects from.
|
|
196
|
+
* @returns {Promise<File[]>} A promise that resolves with an array of `File` objects.
|
|
197
|
+
*/
|
|
198
|
+
const getFilesFromDataTransferItemList = async dataTransferItemList => {
|
|
199
|
+
const files = [];
|
|
200
|
+
|
|
201
|
+
// Chromium browsers read only 100 files at a time as per the spec, so we need to use
|
|
202
|
+
// BFS (Breadth-first search) to traverse the entire directory/file structure.
|
|
203
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry#javascript_content
|
|
204
|
+
const queue = [];
|
|
205
|
+
|
|
206
|
+
for (const item of dataTransferItemList) {
|
|
207
|
+
if (item.kind !== 'file') {
|
|
208
|
+
// Ignore non-file items, such as links.
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// https://developer.mozilla.org/docs/Web/API/DataTransferItem/webkitGetAsEntry
|
|
213
|
+
// This function is implemented as `webkitGetAsEntry()` in non-WebKit browsers
|
|
214
|
+
// including Firefox at this time but it may be renamed to `getAsEntry()` in the future.
|
|
215
|
+
const entry = item.getAsEntry ? item.getAsEntry() : item.webkitGetAsEntry();
|
|
216
|
+
|
|
217
|
+
queue.push(entry);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
while (queue.length > 0) {
|
|
221
|
+
const entry = queue.shift();
|
|
222
|
+
|
|
223
|
+
if (!entry) {
|
|
224
|
+
continue;
|
|
225
|
+
} else if (entry.isFile) {
|
|
226
|
+
const file = await getFileFromFileEntry(entry);
|
|
227
|
+
|
|
228
|
+
if (FILES_TO_IGNORE.indexOf(file.name) === -1) {
|
|
229
|
+
files.push(file);
|
|
230
|
+
}
|
|
231
|
+
} else if (entry.isDirectory) {
|
|
232
|
+
queue.push(...await readAllDirectoryEntries(entry.createReader()));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return files;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get an array of `File` objects from a `FileList` object.
|
|
241
|
+
*
|
|
242
|
+
* @param {FileList} fileList The file list to get an array of `File` objects from.
|
|
243
|
+
* @returns {Promise<File[]>} A promise that resolves with an array of `File` objects.
|
|
244
|
+
*/
|
|
245
|
+
const getFilesFromFileList = async fileList => {
|
|
246
|
+
const files = [];
|
|
247
|
+
|
|
248
|
+
for (const file of fileList) {
|
|
249
|
+
if (FILES_TO_IGNORE.indexOf(file.name) === -1) {
|
|
250
|
+
files.push(toFileWithPath(file));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return files;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get an array of `File` objects from an event.
|
|
259
|
+
* This function supports both `drop` and `change` events.
|
|
260
|
+
*
|
|
261
|
+
* @param {DragEvent | Event} evt The event to get an array of `File` objects from.
|
|
262
|
+
* @returns {Promise<File[]>} A promise that resolves with an array of `File` objects.
|
|
263
|
+
*/
|
|
264
|
+
export const getFilesFromEvent = async evt => {
|
|
265
|
+
if (evt.dataTransfer) {
|
|
266
|
+
return evt.dataTransfer.items
|
|
267
|
+
? await getFilesFromDataTransferItemList(evt.dataTransfer.items)
|
|
268
|
+
: await getFilesFromFileList(evt.dataTransfer.files);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return await getFilesFromFileList(evt.target.files);
|
|
272
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a file is valid based on the accepted file type specifiers.
|
|
3
|
+
*
|
|
4
|
+
* @param {File} file The File object to validate.
|
|
5
|
+
* @param {String} [acceptedTypeSpecifiers=''] The accepted file type specifiers.
|
|
6
|
+
* @returns {Boolean} True if the file is valid, false otherwise.
|
|
7
|
+
*/
|
|
8
|
+
export function isValidFile(file, acceptedTypeSpecifiers = '') {
|
|
9
|
+
if (!acceptedTypeSpecifiers) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const acceptedMimeTypesList = [
|
|
14
|
+
...new Set(acceptedTypeSpecifiers.split(',').map(v => v.trim()).filter(Boolean))
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const fileMimeType = file.type;
|
|
18
|
+
const baseMimeType = fileMimeType.replace(/\/.*$/, '');
|
|
19
|
+
|
|
20
|
+
for (const validType of acceptedMimeTypesList) {
|
|
21
|
+
if (validType.charAt(0) === '.') {
|
|
22
|
+
if (file.name.toLowerCase().indexOf(validType.toLowerCase(), file.name.length - validType.length) !== -1) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
} else if (/\/\*$/.test(validType)) {
|
|
26
|
+
// Check for mime type that looks like "image/*" or similar.
|
|
27
|
+
if (baseMimeType === validType.replace(/\/.*$/, '')) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
if (fileMimeType === validType) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return false;
|
|
38
|
+
}
|