-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathHtml5FileSelector.js
163 lines (152 loc) · 5.4 KB
/
Html5FileSelector.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
const DEFAULT_FILES_TO_IGNORE = [
'.DS_Store', // OSX indexing file
'Thumbs.db' // Windows indexing file
];
// map of common (mostly media types) mime types to use when the browser does not supply the mime type
const EXTENSION_TO_MIME_TYPE_MAP = {
avi: 'video/avi',
gif: 'image/gif',
ico: 'image/x-icon',
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
mkv: 'video/x-matroska',
mov: 'video/quicktime',
mp4: 'video/mp4',
pdf: 'application/pdf',
png: 'image/png',
zip: 'application/zip'
};
function shouldIgnoreFile(file) {
return DEFAULT_FILES_TO_IGNORE.indexOf(file.name) >= 0;
}
function copyString(aString) {
return ` ${aString}`.slice(1);
}
function traverseDirectory(entry) {
const reader = entry.createReader();
// Resolved when the entire directory is traversed
return new Promise((resolveDirectory) => {
const iterationAttempts = [];
const errorHandler = () => {};
function readEntries() {
// According to the FileSystem API spec, readEntries() must be called until
// it calls the callback with an empty array.
reader.readEntries((batchEntries) => {
if (!batchEntries.length) {
// Done iterating this particular directory
resolveDirectory(Promise.all(iterationAttempts));
} else {
// Add a list of promises for each directory entry. If the entry is itself
// a directory, then that promise won't resolve until it is fully traversed.
iterationAttempts.push(Promise.all(batchEntries.map((batchEntry) => {
if (batchEntry.isDirectory) {
return traverseDirectory(batchEntry);
}
return Promise.resolve(batchEntry);
})));
// Try calling readEntries() again for the same dir, according to spec
readEntries();
}
}, errorHandler);
}
// initial call to recursive entry reader function
readEntries();
});
}
// package the file in an object that includes the fullPath from the file entry
// that would otherwise be lost
function packageFile(file, entry) {
let fileTypeOverride = '';
// handle some browsers sometimes missing mime types for dropped files
const hasExtension = file.name && file.name.lastIndexOf('.') !== -1;
if (hasExtension && !file.type) {
const fileExtension = (file.name || '').split('.').pop();
fileTypeOverride = EXTENSION_TO_MIME_TYPE_MAP[fileExtension];
}
return {
fileObject: file, // provide access to the raw File object (required for uploading)
fullPath: entry ? copyString(entry.fullPath) : file.name,
lastModified: file.lastModified,
lastModifiedDate: file.lastModifiedDate,
name: file.name,
size: file.size,
type: file.type ? file.type : fileTypeOverride,
webkitRelativePath: file.webkitRelativePath
};
}
function getFile(entry) {
return new Promise((resolve) => {
entry.file((file) => {
resolve(packageFile(file, entry));
});
});
}
function handleFilePromises(promises, fileList) {
return Promise.all(promises).then((files) => {
files.forEach((file) => {
if (!shouldIgnoreFile(file)) {
fileList.push(file);
}
});
return fileList;
});
}
export function getDataTransferFiles(dataTransfer) {
const dataTransferFiles = [];
const folderPromises = [];
const filePromises = [];
[].slice.call(dataTransfer.items).forEach((listItem) => {
if (typeof listItem.webkitGetAsEntry === 'function') {
const entry = listItem.webkitGetAsEntry();
if (entry) {
if (entry.isDirectory) {
folderPromises.push(traverseDirectory(entry));
} else {
filePromises.push(getFile(entry));
}
}
} else {
dataTransferFiles.push(listItem);
}
});
if (folderPromises.length) {
const flatten = (array) => array.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []);
return Promise.all(folderPromises).then((fileEntries) => {
const flattenedEntries = flatten(fileEntries);
// collect async promises to convert each fileEntry into a File object
flattenedEntries.forEach((fileEntry) => {
filePromises.push(getFile(fileEntry));
});
return handleFilePromises(filePromises, dataTransferFiles);
});
} else if (filePromises.length) {
return handleFilePromises(filePromises, dataTransferFiles);
}
return Promise.resolve(dataTransferFiles);
}
/**
* This function should be called from both the onDrop event from your drag/drop
* dropzone as well as from the HTML5 file selector input field onChange event
* handler. Pass the event object from the triggered event into this function.
* Supports mix of files and folders dropped via drag/drop.
*
* Returns: an array of File objects, that includes all files within folders
* and subfolders of the dropped/selected items.
*/
export function getDroppedOrSelectedFiles(event) {
const dataTransfer = event.dataTransfer;
if (dataTransfer && dataTransfer.items) {
return getDataTransferFiles(dataTransfer).then((fileList) => {
return Promise.resolve(fileList);
});
}
const files = [];
const dragDropFileList = dataTransfer && dataTransfer.files;
const inputFieldFileList = event.target && event.target.files;
const fileList = dragDropFileList || inputFieldFileList || [];
// convert the FileList to a simple array of File objects
for (let i = 0; i < fileList.length; i++) {
files.push(packageFile(fileList[i]));
}
return Promise.resolve(files);
}