@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.
@@ -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
+ }