Skip to content

Commit c965cae

Browse files
committed
feat: Refactored code to handle duplicate files when selected from multiple input[type="file"] elements.
1 parent 5c06442 commit c965cae

File tree

1 file changed

+251
-48
lines changed

1 file changed

+251
-48
lines changed

src/client.js

Lines changed: 251 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ async function fileInputChange(event) {
4949
const files = input.files;
5050
let selected = inputs.get(input) || new Map()
5151
for (let i = 0; i < files.length; i++) {
52-
let fileId = await getFileId(files[i], selected)
53-
selected.set(fileId, { handle, file: files[i] })
52+
files[i].input = input
53+
files[i].id = await getFileId(files[i])
54+
if (selected.has(files[i].id)) {
55+
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
56+
}
57+
58+
selected.set(files[i].id, files[i])
5459
}
5560
inputs.set(input, selected);
5661
console.log("FileList:", Array.from(selected.values()));
@@ -66,9 +71,15 @@ async function selectFile(event) {
6671
const selectedFiles = await window.showOpenFilePicker({ multiple });
6772

6873
for (const handle of selectedFiles) {
69-
let file = handle.getFile()
70-
let fileId = await getFileId(file, selected)
71-
selected.set(fileId, { handle, file })
74+
let file = await handle.getFile()
75+
file.input = input
76+
file.id = await getFileId(file)
77+
if (selected.has(file.id)) {
78+
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
79+
}
80+
81+
file.handle = handle
82+
selected.set(file.id, file)
7283
}
7384

7485
if (selected.size) {
@@ -98,21 +109,35 @@ async function selectDirectory(event) {
98109
type: 'text/directory',
99110
'content-type': 'text/directory'
100111
}
112+
file.input = input
113+
file.id = await getFileId(file)
114+
if (selected.has(file.id)) {
115+
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
116+
}
101117

102-
file.id = await getFileId(file, selected)
103-
selected.set(file.id, { handle, file })
118+
file.handle = handle
119+
selected.set(file.id, file)
104120

105121
const handles = await getSelectedDirectoryHandles(handle, handle.name)
106122
for (let i = 0; i < handles.length; i++) {
107123
let file = handles[i]
108124
if (handles[i].kind === 'file') {
109125
file = await handles[i].getFile();
110-
file = { ...file, ...handles[i] }
126+
file.directory = handles[i].directory
127+
file.parentDirectory = handles[i].parentDirectory
128+
file.path = handles[i].path
111129
}
112130

131+
file.input = input
132+
file.id = await getFileId(file)
133+
if (selected.has(file.id)) {
134+
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
135+
}
136+
137+
file.handle = handles[i]
113138
file['content-type'] = file.type
114-
file.id = await getFileId(file, selected)
115-
selected.set(file.id, { handle: handles[i], file })
139+
140+
selected.set(file.id, file)
116141
}
117142

118143
if (selected.size) {
@@ -156,28 +181,27 @@ async function getFileId(file, selected) {
156181
const { name, size, type, lastModified } = file;
157182
const key = `${name}${size}${type}${lastModified}`;
158183

159-
if (selected.has(key)) {
160-
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
161-
}
162-
163184
file.id = key
164185
return key;
165186
}
166187
}
167188

168-
async function getFiles(fileInputs) {
189+
async function getFiles(fileInputs, isGetData) {
169190
const files = [];
170191

171192
if (!Array.isArray(fileInputs))
172193
fileInputs = [fileInputs]
173194

174195
for (let input of fileInputs) {
175196
const selected = inputs.get(input)
176-
for (const value of selected.values()) {
177-
let file = await readFile(value.file)
178-
file = getData({ ...file })
179-
files.push(file)
180-
}
197+
if (selected)
198+
for (let file of selected.values()) {
199+
if (!file.src)
200+
file = await readFile(file)
201+
if (isGetData !== false)
202+
file = getData({ ...file })
203+
files.push(file)
204+
}
181205
}
182206

183207
return files
@@ -256,26 +280,36 @@ function readFile(file) {
256280
});
257281
}
258282

259-
async function getNewFileHandle() {
260-
// const options = {
261-
// types: [
262-
// {
263-
// description: 'Text Files',
264-
// accept: {
265-
// 'text/plain': ['.txt'],
266-
// },
267-
// },
268-
// ],
269-
// };
270-
const handle = await window.showSaveFilePicker(options);
271-
return handle;
283+
async function renderFiles(input) {
284+
let template_id = input.getAttribute('template_id')
285+
if (template_id) {
286+
let template = document.querySelector(`[template="${template_id}"]`)
287+
template.setAttribute('file_id', '{{id}}')
288+
const data = await getFiles(input, false)
289+
if (!data.length) return
290+
render.data({
291+
selector: `[template='${template_id}']`,
292+
data
293+
});
294+
}
272295
}
273296

274-
async function fileAction(btn, params, action) {
297+
async function fileFormAction(btn, params, action) {
275298
const form = btn.closest('form')
276299
let inputs = form.querySelectorAll('input[type="file"]')
277300
for (let i = 0; i < inputs.length; i++) {
278-
await save(inputs[i])
301+
if (action === 'upload')
302+
upload(inputs[i])
303+
else if (action === 'saveLocally' || action === 'saveAs') {
304+
save(inputs[i])
305+
}
306+
else if (action === 'export') {
307+
// Export(inputs[i])
308+
}
309+
else if (action === 'import') {
310+
// Import(inputs[i])
311+
} else {
312+
}
279313
}
280314

281315
document.dispatchEvent(new CustomEvent(action, {
@@ -284,7 +318,96 @@ async function fileAction(btn, params, action) {
284318

285319
}
286320

321+
async function fileRenderAction(btn, params, action) {
322+
let file_id = btn.getAttribute('file_id');
323+
if (!file_id) {
324+
const closestElement = btn.closest('[file_id]');
325+
if (closestElement) {
326+
file_id = closestElement.getAttribute('file_id');
327+
}
328+
}
329+
if (!file_id) return
330+
331+
let templateid = btn.closest('[templateid]')
332+
if (templateid)
333+
templateid = templateid.getAttribute('templateid')
334+
335+
const input = document.querySelector(`[type="file"][template_id="${templateid}"]`)
336+
if (!input) return
337+
338+
let file = inputs.get(input).get(file_id)
339+
if (!file) return
340+
341+
if (action === 'createFile') {
342+
let name = btn.getAttribute('value')
343+
create(file, 'file', name)
344+
}
345+
else if (action === 'deleteFile')
346+
Delete(file)
347+
else if (action === 'createDirectory') {
348+
let name = btn.getAttribute('value')
349+
create(file, 'directory', name)
350+
}
351+
else if (action === 'deleteDirectory')
352+
Delete(file)
353+
354+
document.dispatchEvent(new CustomEvent(action, {
355+
detail: {}
356+
}));
357+
358+
}
359+
287360
async function save(input) {
361+
try {
362+
let files = await getFiles(input)
363+
364+
for (let i = 0; i < files.length; i++) {
365+
if (!files[i].src) continue
366+
367+
if (files[i].handle) {
368+
if ('saveAs' == 'true') {
369+
if (files[i].kind === 'file') {
370+
const options = {
371+
suggestedName: files[i].name,
372+
types: [
373+
{
374+
description: 'Text Files',
375+
}
376+
],
377+
};
378+
files[i].handle = await window.showSaveFilePicker(options);
379+
} else if (files[i].kind === 'directory') {
380+
// Create a new subdirectory
381+
files[i].handle = await files[i].handle.getDirectoryHandle('new_directory', { create: true });
382+
return
383+
}
384+
}
385+
386+
const writable = await files[i].handle.createWritable();
387+
await writable.write(files[i].src);
388+
await writable.close();
389+
390+
} else {
391+
const blob = new Blob([files[i].src], { type: files[i].type });
392+
393+
// Create a temporary <a> element to trigger the file download
394+
const downloadLink = document.createElement('a');
395+
downloadLink.href = URL.createObjectURL(blob);
396+
downloadLink.download = files[i].name;
397+
398+
// Trigger the download
399+
downloadLink.click();
400+
}
401+
402+
}
403+
} catch (error) {
404+
if (error.name !== 'AbortError') {
405+
console.error("Error selecting files:", error);
406+
}
407+
}
408+
}
409+
410+
async function upload(input) {
288411
let collection = input.getAttribute('collection')
289412
let document_id = input.getAttribute('document_id')
290413
let files = await getFiles(input)
@@ -309,17 +432,63 @@ async function save(input) {
309432
return response
310433
}
311434

312-
async function renderFiles(input) {
313-
let template_id = input.getAttribute('template_id')
314-
if (template_id) {
315-
let template = document.querySelector(`[template="${template_id}"]`)
316-
template.setAttribute('file_id', '{{id}}')
317-
const data = await getFiles(input)
318-
if (!data.length) return
319-
render.data({
320-
selector: `[template='${template_id}']`,
321-
data
322-
});
435+
async function create(directory, type, name, src = "") {
436+
try {
437+
if (directory.handle && directory.input) {
438+
if (!name) {
439+
const name = prompt('Enter the file name:');
440+
if (!name) {
441+
console.log('Invalid file name.');
442+
return;
443+
}
444+
445+
}
446+
447+
let handle, file
448+
if (type === 'directory') {
449+
handle = await directory.handle.getDirectoryHandle(name, { create: true });
450+
file = { name: handle.name, type: 'text/directory' }
451+
} else if (type === 'file') {
452+
handle = await directory.handle.getFileHandle(name, { create: true });
453+
const writable = await handle.createWritable();
454+
455+
// Write data to the new file...
456+
await writable.write(src);
457+
await writable.close();
458+
459+
file = handle.getFile()
460+
}
461+
462+
if (directory.input) {
463+
file.directory = directory.path
464+
file.parentDirectory = directory.name
465+
file.path = directory.path + '/' + file.name
466+
file.input = directory.input
467+
file.handle = handle
468+
file['content-type'] = file.type
469+
470+
file.id = await getFileId(file)
471+
if (inputs.get(directory.input).has(file.id)) {
472+
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
473+
}
474+
475+
inputs.get(directory.input).set(file.id, file)
476+
}
477+
}
478+
} catch (error) {
479+
console.log('Error adding file:', error);
480+
}
481+
}
482+
483+
async function Delete(file) {
484+
try {
485+
if (file.handle) {
486+
await file.handle.remove();
487+
if (file.input && file.id)
488+
inputs.get(file.input).delete(file.id)
489+
}
490+
} catch (error) {
491+
console.log('Error deleting file:', error);
323492
}
324493
}
325494

@@ -350,10 +519,44 @@ observer.init({
350519
action.init({
351520
name: "upload",
352521
callback: (btn, params) => {
353-
fileAction(btn, params, "upload")
522+
fileFormAction(btn, params, "upload")
523+
}
524+
})
525+
526+
action.init({
527+
name: "saveLocally",
528+
callback: (btn, params) => {
529+
fileFormAction(btn, params, "saveLocally")
530+
}
531+
})
532+
533+
action.init({
534+
name: "createFile",
535+
callback: (btn, params) => {
536+
fileRenderAction(btn, params, "createFile")
537+
}
538+
})
539+
540+
action.init({
541+
name: "deleteFile",
542+
callback: (btn, params) => {
543+
fileRenderAction(btn, params, "deleteFile")
544+
}
545+
})
546+
action.init({
547+
name: "createDirectory",
548+
callback: (btn, params) => {
549+
fileRenderAction(btn, params, "createDirectory")
550+
}
551+
})
552+
553+
action.init({
554+
name: "deleteDirectory",
555+
callback: (btn, params) => {
556+
fileRenderAction(btn, params, "deleteDirectory")
354557
}
355558
})
356559

357560
init()
358561

359-
export default { getFiles }
562+
export default { getFiles, create, Delete }

0 commit comments

Comments
 (0)