@cocreate/file 1.3.11 → 1.5.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/CHANGELOG.md +35 -0
- package/CoCreate.config.js +2 -2
- package/LICENSE +683 -21
- package/demo/index.html +33 -9
- package/docs/index.html +27 -27
- package/package.json +7 -7
- package/src/client.js +555 -156
- package/src/index.js +22 -0
- package/src/server.js +66 -62
package/src/client.js
CHANGED
|
@@ -1,188 +1,235 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2023 CoCreate and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
********************************************************************************/
|
|
17
|
+
|
|
18
|
+
// Commercial Licensing Information:
|
|
19
|
+
// For commercial use of this software without the copyleft provisions of the AGPLv3,
|
|
20
|
+
// you must obtain a commercial license from CoCreate LLC.
|
|
21
|
+
// For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
|
|
22
|
+
|
|
23
|
+
import Observer from '@cocreate/Observer';
|
|
24
|
+
import Crud from '@cocreate/crud-client';
|
|
25
|
+
import Elements from '@cocreate/elements';
|
|
26
|
+
import Actions from '@cocreate/Actions';
|
|
4
27
|
import render from '@cocreate/render';
|
|
28
|
+
import { queryElements } from '@cocreate/utils';
|
|
5
29
|
import '@cocreate/element-prototype';
|
|
6
30
|
|
|
7
|
-
|
|
31
|
+
const inputs = new Map();
|
|
32
|
+
const Files = new Map();
|
|
8
33
|
|
|
9
34
|
function init(elements) {
|
|
10
35
|
// Returns an array of elements.
|
|
11
36
|
if (!elements)
|
|
12
37
|
elements = document.querySelectorAll('[type="file"]')
|
|
13
38
|
|
|
14
|
-
// If elements is an array of elements returns an array of elements.
|
|
39
|
+
// If elements is an not array of elements returns an array of elements.
|
|
15
40
|
else if (!Array.isArray(elements))
|
|
16
41
|
elements = [elements]
|
|
17
42
|
for (let i = 0; i < elements.length; i++) {
|
|
18
43
|
if (elements[i].tagName !== 'INPUT') {
|
|
19
44
|
// TODO: create input and append to div if input dos not exist
|
|
45
|
+
// check if input exist
|
|
46
|
+
let input = document.createElement("input");
|
|
47
|
+
input.type = "file";
|
|
48
|
+
input.setAttribute('hidden', '')
|
|
49
|
+
elements[i].appendChild(input);
|
|
20
50
|
}
|
|
21
|
-
|
|
51
|
+
|
|
52
|
+
elements[i].getValue = async () => await getFiles([elements[i]])
|
|
53
|
+
elements[i].getFiles = async () => await getFiles([elements[i]])
|
|
22
54
|
|
|
23
55
|
// elements[i].setValue = (value) => pickr.setColor(value);
|
|
56
|
+
|
|
24
57
|
if (elements[i].hasAttribute('directory')) {
|
|
25
58
|
if (window.showDirectoryPicker)
|
|
26
|
-
elements[i].addEventListener("click",
|
|
59
|
+
elements[i].addEventListener("click", fileEvent);
|
|
27
60
|
else if ('webkitdirectory' in elements[i]) {
|
|
28
61
|
elements[i].webkitdirectory = true
|
|
29
|
-
elements[i].addEventListener("change",
|
|
62
|
+
elements[i].addEventListener("change", fileEvent)
|
|
30
63
|
} else
|
|
31
64
|
console.error("Directory selection not supported in this browser.");
|
|
32
65
|
} else if (window.showOpenFilePicker)
|
|
33
|
-
elements[i].addEventListener("click",
|
|
66
|
+
elements[i].addEventListener("click", fileEvent);
|
|
34
67
|
else
|
|
35
|
-
elements[i].addEventListener("change",
|
|
68
|
+
elements[i].addEventListener("change", fileEvent);
|
|
36
69
|
}
|
|
37
70
|
}
|
|
38
71
|
|
|
72
|
+
async function fileEvent(event) {
|
|
73
|
+
try {
|
|
74
|
+
const input = event.target;
|
|
75
|
+
let selected = inputs.get(input) || new Map()
|
|
76
|
+
let files = input.files;
|
|
77
|
+
if (!files.length) {
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
const multiple = input.multiple
|
|
80
|
+
if (input.hasAttribute('directory')) {
|
|
81
|
+
let handle = await window.showDirectoryPicker();
|
|
82
|
+
let file = {
|
|
83
|
+
name: handle.name,
|
|
84
|
+
directory: '/',
|
|
85
|
+
path: '/' + handle.name,
|
|
86
|
+
type: 'text/directory',
|
|
87
|
+
'content-type': 'text/directory'
|
|
88
|
+
}
|
|
89
|
+
file.input = input
|
|
90
|
+
file.id = await getFileId(file)
|
|
91
|
+
if (selected.has(file.id)) {
|
|
92
|
+
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
|
|
93
|
+
}
|
|
39
94
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
let selected = inputs.get(input) || []
|
|
44
|
-
selected.push(...files)
|
|
45
|
-
inputs.set(input, selected);
|
|
46
|
-
console.log("Files selected:", files);
|
|
47
|
-
renderFiles(input)
|
|
48
|
-
}
|
|
95
|
+
file.handle = handle
|
|
96
|
+
selected.set(file.id, file)
|
|
97
|
+
Files.set(file.id, file)
|
|
49
98
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const multiple = input.multiple
|
|
56
|
-
const selectedFiles = await window.showOpenFilePicker({ multiple });
|
|
99
|
+
files = await getDirectoryHandles(handle, handle.name)
|
|
100
|
+
} else {
|
|
101
|
+
files = await window.showOpenFilePicker({ multiple });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
57
104
|
|
|
58
|
-
for (
|
|
59
|
-
|
|
105
|
+
for (let i = 0; i < files.length; i++) {
|
|
106
|
+
const handle = files[i]
|
|
107
|
+
if (files[i].kind === 'file') {
|
|
108
|
+
files[i] = await files[i].getFile();
|
|
109
|
+
files[i].handle = handle
|
|
110
|
+
} else if (files[i].kind === 'directory') {
|
|
111
|
+
files[i].handle = handle
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!files[i].src)
|
|
115
|
+
files[i] = await readFile(files[i])
|
|
116
|
+
|
|
117
|
+
files[i].directory = handle.directory || '/'
|
|
118
|
+
files[i].parentDirectory = handle.parentDirectory || ''
|
|
119
|
+
files[i].path = handle.path || '/' + handle.name
|
|
120
|
+
files[i]['content-type'] = files[i].type
|
|
121
|
+
files[i].input = input
|
|
122
|
+
files[i].id = await getFileId(files[i])
|
|
123
|
+
if (selected.has(files[i].id)) {
|
|
124
|
+
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
selected.set(files[i].id, files[i])
|
|
128
|
+
Files.set(files[i].id, files[i])
|
|
60
129
|
}
|
|
61
130
|
|
|
62
|
-
if (selected.
|
|
131
|
+
if (selected.size) {
|
|
63
132
|
inputs.set(input, selected);
|
|
64
133
|
console.log("Files selected:", selected);
|
|
65
|
-
renderFiles(input)
|
|
66
|
-
}
|
|
67
134
|
|
|
135
|
+
if (!input.renderValue)
|
|
136
|
+
input.renderValue(selected.values())
|
|
137
|
+
// render.render({
|
|
138
|
+
// source: input,
|
|
139
|
+
// data
|
|
140
|
+
// });
|
|
141
|
+
|
|
142
|
+
const isImport = input.getAttribute('import')
|
|
143
|
+
const isRealtime = input.getAttribute('realtime')
|
|
144
|
+
if (isRealtime !== 'false' && (isImport || isImport == "")) {
|
|
145
|
+
Import(input)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
}
|
|
68
149
|
} catch (error) {
|
|
69
150
|
if (error.name !== 'AbortError') {
|
|
70
|
-
console.error("Error selecting
|
|
151
|
+
console.error("Error selecting directory:", error);
|
|
71
152
|
}
|
|
72
153
|
}
|
|
73
|
-
}
|
|
74
154
|
|
|
75
|
-
|
|
76
|
-
event.preventDefault()
|
|
77
|
-
const input = event.target;
|
|
78
|
-
let selected = inputs.get(input) || []
|
|
155
|
+
}
|
|
79
156
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
157
|
+
async function getDirectoryHandles(handle, name) {
|
|
158
|
+
let handles = [];
|
|
159
|
+
for await (const entry of handle.values()) {
|
|
160
|
+
entry.directory = '/' + name
|
|
161
|
+
entry.parentDirectory = name.split("/").pop();
|
|
162
|
+
entry.path = '/' + name + '/' + entry.name
|
|
163
|
+
if (!entry.webkitRelativePath)
|
|
164
|
+
entry.webkitRelativePath = name
|
|
83
165
|
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.error("Error selecting directory:", error);
|
|
166
|
+
if (entry.kind === 'file') {
|
|
167
|
+
handles.push(entry);
|
|
168
|
+
} else if (entry.kind === 'directory') {
|
|
169
|
+
entry.type = 'text/directory'
|
|
170
|
+
handles.push(entry);
|
|
171
|
+
const entries = await getDirectoryHandles(entry, name + '/' + entry.name);
|
|
172
|
+
handles = handles.concat(entries);
|
|
92
173
|
}
|
|
93
174
|
}
|
|
175
|
+
return handles;
|
|
94
176
|
}
|
|
95
177
|
|
|
96
|
-
async function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const handle = await window.showSaveFilePicker(options);
|
|
108
|
-
return handle;
|
|
178
|
+
async function getFileId(file) {
|
|
179
|
+
|
|
180
|
+
if (file.id = file.path || file.webkitRelativePath) {
|
|
181
|
+
return file.id;
|
|
182
|
+
} else {
|
|
183
|
+
const { name, size, type, lastModified } = file;
|
|
184
|
+
const key = `${name}${size}${type}${lastModified}`;
|
|
185
|
+
|
|
186
|
+
file.id = key
|
|
187
|
+
return key;
|
|
188
|
+
}
|
|
109
189
|
}
|
|
110
190
|
|
|
111
|
-
async function
|
|
191
|
+
async function getFiles(fileInputs) {
|
|
112
192
|
const files = [];
|
|
113
193
|
|
|
114
194
|
if (!Array.isArray(fileInputs))
|
|
115
195
|
fileInputs = [fileInputs]
|
|
116
196
|
|
|
117
197
|
for (let input of fileInputs) {
|
|
118
|
-
const selected = inputs.get(input)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (selected[i] instanceof FileSystemDirectoryHandle) {
|
|
123
|
-
// The object is an instance of FileSystemFileHandle
|
|
124
|
-
const handles = await getSelectedDirectoryFiles(selected[i], selected[i].name)
|
|
125
|
-
for (let handle of handles) {
|
|
126
|
-
if (handle.kind === 'file')
|
|
127
|
-
file = await handle.getFile();
|
|
128
|
-
else if (handle.kind === 'directory')
|
|
129
|
-
file = { ...handle, name: handle.name }
|
|
130
|
-
else continue
|
|
131
|
-
|
|
132
|
-
if (isObject)
|
|
133
|
-
file = await readFile(file)
|
|
134
|
-
|
|
135
|
-
files.push(file)
|
|
136
|
-
}
|
|
137
|
-
} else {
|
|
138
|
-
if (selected[i] instanceof FileSystemFileHandle) {
|
|
139
|
-
// The object is an instance of FileSystemFileHandle
|
|
140
|
-
file = await selected[i].getFile();
|
|
141
|
-
} else {
|
|
142
|
-
// The object is not an instance of FileSystemFileHandle
|
|
143
|
-
console.log("It's not a FileSystemFileHandle object");
|
|
144
|
-
file = selected[i]
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (isObject)
|
|
198
|
+
const selected = inputs.get(input)
|
|
199
|
+
if (selected) {
|
|
200
|
+
for (let file of selected.values()) {
|
|
201
|
+
if (!file.src)
|
|
148
202
|
file = await readFile(file)
|
|
149
203
|
|
|
204
|
+
file = getCustomData({ ...file })
|
|
150
205
|
files.push(file)
|
|
151
206
|
}
|
|
152
|
-
|
|
153
207
|
}
|
|
154
208
|
}
|
|
155
209
|
|
|
156
210
|
return files
|
|
157
211
|
}
|
|
158
212
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
files.push(entry);
|
|
170
|
-
} else if (entry.kind === 'directory') {
|
|
171
|
-
entry.type = 'text/directory'
|
|
172
|
-
files.push(entry);
|
|
173
|
-
const entries = await getSelectedDirectoryFiles(entry, name + '/' + entry.name);
|
|
174
|
-
files = files.concat(entries);
|
|
213
|
+
// gets file custom data
|
|
214
|
+
function getCustomData(file) {
|
|
215
|
+
let form = document.querySelector(`[file_id="${file.id}"]`);
|
|
216
|
+
if (form) {
|
|
217
|
+
let elements = form.querySelectorAll('[file]');
|
|
218
|
+
for (let i = 0; i < elements.length; i++) {
|
|
219
|
+
let name = elements[i].getAttribute('file')
|
|
220
|
+
if (name) {
|
|
221
|
+
file[name] = elements[i].getValue()
|
|
222
|
+
}
|
|
175
223
|
}
|
|
176
224
|
}
|
|
177
|
-
return
|
|
225
|
+
return file;
|
|
178
226
|
}
|
|
179
227
|
|
|
228
|
+
|
|
180
229
|
// This function reads the file and returns its src
|
|
181
230
|
function readFile(file) {
|
|
182
231
|
// Return a new promise that resolves the file object
|
|
183
232
|
return new Promise((resolve) => {
|
|
184
|
-
file["content-type"] = file.type
|
|
185
|
-
|
|
186
233
|
// Split the file type into an array
|
|
187
234
|
const fileType = file.type.split('/');
|
|
188
235
|
let readAs;
|
|
@@ -237,60 +284,389 @@ function readFile(file) {
|
|
|
237
284
|
});
|
|
238
285
|
}
|
|
239
286
|
|
|
240
|
-
async function
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
287
|
+
async function save(element, action, data) {
|
|
288
|
+
try {
|
|
289
|
+
if (!data)
|
|
290
|
+
data = []
|
|
291
|
+
|
|
292
|
+
if (!Array.isArray(element))
|
|
293
|
+
element = [element]
|
|
294
|
+
|
|
295
|
+
for (let i = 0; i < element.length; i++) {
|
|
296
|
+
const inputs = []
|
|
297
|
+
if (element[i].type === 'file')
|
|
298
|
+
inputs.push(element[i])
|
|
299
|
+
else if (element[i].tagName === 'form') {
|
|
300
|
+
let fileInputs = element[i].querySelectorAll('input[type="file"]')
|
|
301
|
+
inputs.push(...fileInputs)
|
|
302
|
+
} else {
|
|
303
|
+
const form = element[i].closest('form')
|
|
304
|
+
if (form)
|
|
305
|
+
inputs.push(...form.querySelectorAll('input[type="file"]'))
|
|
306
|
+
}
|
|
244
307
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
308
|
+
for (let input of inputs) {
|
|
309
|
+
let files = await getFiles(input)
|
|
310
|
+
|
|
311
|
+
for (let i = 0; i < files.length; i++) {
|
|
312
|
+
if (!files[i].src) continue
|
|
313
|
+
|
|
314
|
+
if (files[i].handle && action !== 'download') {
|
|
315
|
+
if (action === 'saveAs') {
|
|
316
|
+
if (files[i].kind === 'file') {
|
|
317
|
+
const options = {
|
|
318
|
+
suggestedName: files[i].name,
|
|
319
|
+
types: [
|
|
320
|
+
{
|
|
321
|
+
description: 'Text Files',
|
|
322
|
+
}
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
files[i].handle = await window.showSaveFilePicker(options);
|
|
326
|
+
} else if (files[i].kind === 'directory') {
|
|
327
|
+
// Create a new subdirectory
|
|
328
|
+
files[i].handle = await files[i].handle.getDirectoryHandle('new_directory', { create: true });
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (files[i].handle.kind === 'directory') continue
|
|
334
|
+
|
|
335
|
+
const writable = await files[i].handle.createWritable();
|
|
336
|
+
await writable.write(files[i].src);
|
|
337
|
+
await writable.close();
|
|
338
|
+
|
|
339
|
+
} else {
|
|
340
|
+
const blob = new Blob([files[i].src], { type: files[i].type });
|
|
341
|
+
|
|
342
|
+
// Create a temporary <a> element to trigger the file download
|
|
343
|
+
const downloadLink = document.createElement('a');
|
|
344
|
+
downloadLink.href = URL.createObjectURL(blob);
|
|
345
|
+
downloadLink.download = files[i].name;
|
|
346
|
+
|
|
347
|
+
// Trigger the download
|
|
348
|
+
downloadLink.click();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let queryElements = queryElements({ element: element[i], prefix: action })
|
|
355
|
+
if (queryElements) {
|
|
356
|
+
save(queryElements, action, data)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return data
|
|
249
360
|
|
|
361
|
+
} catch (error) {
|
|
362
|
+
if (error.name !== 'AbortError') {
|
|
363
|
+
console.error("Error selecting files:", error);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
250
366
|
}
|
|
251
367
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
368
|
+
async function upload(element, data) {
|
|
369
|
+
if (!data)
|
|
370
|
+
data = []
|
|
371
|
+
|
|
372
|
+
if (!Array.isArray(element))
|
|
373
|
+
element = [element]
|
|
374
|
+
|
|
375
|
+
for (let i = 0; i < element.length; i++) {
|
|
376
|
+
const inputs = []
|
|
377
|
+
if (element[i].type === 'file')
|
|
378
|
+
inputs.push(element[i])
|
|
379
|
+
else if (element[i].tagName === 'form') {
|
|
380
|
+
let fileInputs = element[i].querySelectorAll('input[type="file"]')
|
|
381
|
+
inputs.push(...fileInputs)
|
|
382
|
+
} else {
|
|
383
|
+
const form = element[i].closest('form')
|
|
384
|
+
if (form)
|
|
385
|
+
inputs.push(...form.querySelectorAll('input[type="file"]'))
|
|
386
|
+
}
|
|
255
387
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
388
|
+
for (let input of inputs) {
|
|
389
|
+
let Data = Elements.getObject(input);
|
|
390
|
+
if (Data.type) {
|
|
391
|
+
if (input.getFilter)
|
|
392
|
+
Data.filter = input.getFilter()
|
|
393
|
+
|
|
394
|
+
let files = await getFiles(input)
|
|
395
|
+
|
|
396
|
+
let key = getAttribute('key')
|
|
397
|
+
if (Data.type === 'key')
|
|
398
|
+
Data.type = 'object'
|
|
399
|
+
|
|
400
|
+
if (Data.type === 'object') {
|
|
401
|
+
let object = input.getAttribute('object')
|
|
402
|
+
if (key) {
|
|
403
|
+
Data[Data.type] = { _id: object, [key]: files }
|
|
404
|
+
} else {
|
|
405
|
+
Data[Data.type] = files
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
let action = 'update' + Data.type.charAt(0).toUpperCase() + Data.type.slice(1)
|
|
410
|
+
if (Crud[action]) {
|
|
411
|
+
let response = await Crud[action](Data)({
|
|
412
|
+
array,
|
|
413
|
+
object,
|
|
414
|
+
upsert: true
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
data.push(response)
|
|
418
|
+
if (response && (!object || object !== response.object)) {
|
|
419
|
+
Elements.setTypeValue(element, response);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
261
424
|
|
|
262
|
-
|
|
263
|
-
|
|
425
|
+
let queriedElements = queryElements({ element: element[i], prefix: 'upload' })
|
|
426
|
+
if (queriedElements) {
|
|
427
|
+
upload(queriedElements, data)
|
|
428
|
+
}
|
|
264
429
|
}
|
|
430
|
+
return data
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function Import(element, data) {
|
|
434
|
+
if (!data)
|
|
435
|
+
data = []
|
|
436
|
+
|
|
437
|
+
if (!Array.isArray(element))
|
|
438
|
+
element = [element]
|
|
439
|
+
|
|
440
|
+
for (let i = 0; i < element.length; i++) {
|
|
441
|
+
const inputs = []
|
|
442
|
+
if (element[i].type === 'file')
|
|
443
|
+
inputs.push(element[i])
|
|
444
|
+
else if (element[i].tagName === 'form') {
|
|
445
|
+
let fileInputs = element[i].querySelectorAll('input[type="file"]')
|
|
446
|
+
inputs.push(...fileInputs)
|
|
447
|
+
} else {
|
|
448
|
+
const form = element[i].closest('form')
|
|
449
|
+
if (form)
|
|
450
|
+
inputs.push(...form.querySelectorAll('input[type="file"]'))
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (inputs.length) {
|
|
454
|
+
let Data = await getFiles(inputs)
|
|
455
|
+
Data.reduce((result, { src }) => {
|
|
456
|
+
try {
|
|
457
|
+
const parsedSrc = JSON.parse(src);
|
|
458
|
+
if (Array.isArray(parsedSrc))
|
|
459
|
+
data.push(...parsedSrc);
|
|
460
|
+
else
|
|
461
|
+
data.push(parsedSrc);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
console.error(`Error parsing JSON: ${error}`);
|
|
464
|
+
}
|
|
465
|
+
return result;
|
|
466
|
+
}, []);
|
|
467
|
+
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (element[i].type !== 'file') {
|
|
471
|
+
let Data = Elements.getObject(element[i]);
|
|
472
|
+
if (Data.type) {
|
|
473
|
+
if (element[i].getFilter)
|
|
474
|
+
Data.filter = element[i].getFilter()
|
|
475
|
+
|
|
476
|
+
if (Data.type === 'key')
|
|
477
|
+
Data.type = 'object'
|
|
478
|
+
|
|
479
|
+
data.push(Data)
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (data.length) {
|
|
484
|
+
for (let i = 0; i < data.length; i++) {
|
|
485
|
+
let action = 'create' + data[i].type.charAt(0).toUpperCase() + data[i].type.slice(1)
|
|
486
|
+
if (Crud[action]) {
|
|
487
|
+
data[i] = await Crud[action](data[i])
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
265
491
|
|
|
266
|
-
|
|
492
|
+
let queriedElements = queryElements({ element: element[i], prefix: 'import' })
|
|
493
|
+
if (queriedElements) {
|
|
494
|
+
Import(queriedElements, data)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return data
|
|
267
498
|
}
|
|
268
499
|
|
|
269
|
-
async function
|
|
270
|
-
|
|
271
|
-
|
|
500
|
+
async function Export(element, data) {
|
|
501
|
+
if (!data)
|
|
502
|
+
data = []
|
|
503
|
+
|
|
504
|
+
if (!Array.isArray(element))
|
|
505
|
+
element = [element]
|
|
506
|
+
|
|
507
|
+
for (let i = 0; i < element.length; i++) {
|
|
508
|
+
const inputs = []
|
|
509
|
+
if (element[i].type === 'file')
|
|
510
|
+
inputs.push(element[i])
|
|
511
|
+
else if (element[i].tagName === 'form') {
|
|
512
|
+
let fileInputs = element[i].querySelectorAll('input[type="file"]')
|
|
513
|
+
inputs.push(...fileInputs)
|
|
514
|
+
} else {
|
|
515
|
+
const form = element[i].closest('form')
|
|
516
|
+
if (form)
|
|
517
|
+
inputs.push(...form.querySelectorAll('input[type="file"]'))
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (inputs.length)
|
|
521
|
+
data.push(...getFiles(inputs))
|
|
522
|
+
|
|
523
|
+
let Data = Elements.getObject(element[i]);
|
|
524
|
+
if (Data.type) {
|
|
525
|
+
if (element[i].getFilter)
|
|
526
|
+
Data.filter = element[i].getFilter()
|
|
527
|
+
|
|
528
|
+
if (Data.type === 'key')
|
|
529
|
+
Data.type = 'object'
|
|
530
|
+
let action = 'read' + Data.type.charAt(0).toUpperCase() + Data.type.slice(1)
|
|
531
|
+
if (Crud[action]) {
|
|
532
|
+
Data = await Crud[action](Data)
|
|
533
|
+
data.push(...Data[Data.type])
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
let queriedElements = queryElements({ element: element[i], prefix: 'export' })
|
|
538
|
+
if (queriedElements) {
|
|
539
|
+
Export(queriedElements, data)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (data.length)
|
|
545
|
+
exportFile(data);
|
|
546
|
+
|
|
547
|
+
return data
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async function exportFile(data) {
|
|
551
|
+
let name = data.type || 'download';
|
|
552
|
+
let exportData = JSON.stringify(data, null, 2);
|
|
553
|
+
let blob = new Blob([exportData], { type: "application/json" });
|
|
554
|
+
let url = URL.createObjectURL(blob);
|
|
555
|
+
|
|
556
|
+
let link = document.createElement("a");
|
|
557
|
+
|
|
558
|
+
link.href = url;
|
|
559
|
+
link.download = name;
|
|
560
|
+
|
|
561
|
+
document.body.appendChild(link);
|
|
562
|
+
|
|
563
|
+
link.dispatchEvent(
|
|
564
|
+
new MouseEvent('click', {
|
|
565
|
+
bubbles: true,
|
|
566
|
+
cancelable: true,
|
|
567
|
+
view: window
|
|
568
|
+
})
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
URL.revokeObjectURL(url);
|
|
572
|
+
link.remove();
|
|
272
573
|
}
|
|
273
574
|
|
|
274
|
-
async function
|
|
275
|
-
|
|
276
|
-
|
|
575
|
+
async function fileRenderAction(action) {
|
|
576
|
+
const element = action.element
|
|
577
|
+
|
|
578
|
+
let file_id = element.getAttribute('file_id');
|
|
579
|
+
if (!file_id) {
|
|
580
|
+
const closestElement = element.closest('[file_id]');
|
|
581
|
+
if (closestElement)
|
|
582
|
+
file_id = closestElement.getAttribute('file_id');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let input = Files.get(file_id).input
|
|
586
|
+
|
|
587
|
+
if (!file_id || !input) return
|
|
588
|
+
|
|
589
|
+
let file = inputs.get(input).get(file_id)
|
|
590
|
+
if (!file) return
|
|
591
|
+
|
|
592
|
+
if (action.name === 'createFile') {
|
|
593
|
+
let name = element.getAttribute('value')
|
|
594
|
+
create(file, 'file', name)
|
|
595
|
+
} else if (action.name === 'deleteFile')
|
|
596
|
+
Delete(file)
|
|
597
|
+
else if (action.name === 'createDirectory') {
|
|
598
|
+
let name = element.getAttribute('value')
|
|
599
|
+
create(file, 'directory', name)
|
|
600
|
+
} else if (action.name === 'deleteDirectory')
|
|
601
|
+
Delete(file)
|
|
602
|
+
|
|
603
|
+
document.dispatchEvent(new CustomEvent(action.name, {
|
|
604
|
+
detail: {}
|
|
605
|
+
}));
|
|
606
|
+
|
|
277
607
|
}
|
|
278
608
|
|
|
279
|
-
function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
609
|
+
async function create(directory, type, name, src = "") {
|
|
610
|
+
try {
|
|
611
|
+
if (directory.handle && directory.input) {
|
|
612
|
+
if (!name) {
|
|
613
|
+
const name = prompt('Enter the file name:');
|
|
614
|
+
if (!name) {
|
|
615
|
+
console.log('Invalid file name.');
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
let handle, file
|
|
622
|
+
if (type === 'directory') {
|
|
623
|
+
handle = await directory.handle.getDirectoryHandle(name, { create: true });
|
|
624
|
+
file = { name: handle.name, type: 'text/directory' }
|
|
625
|
+
} else if (type === 'file') {
|
|
626
|
+
handle = await directory.handle.getFileHandle(name, { create: true });
|
|
627
|
+
const writable = await handle.createWritable();
|
|
628
|
+
|
|
629
|
+
// Write data to the new file...
|
|
630
|
+
await writable.write(src);
|
|
631
|
+
await writable.close();
|
|
632
|
+
|
|
633
|
+
file = handle.getFile()
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (directory.input) {
|
|
637
|
+
file.directory = directory.path
|
|
638
|
+
file.parentDirectory = directory.name
|
|
639
|
+
file.path = directory.path + '/' + file.name
|
|
640
|
+
file.input = directory.input
|
|
641
|
+
file.handle = handle
|
|
642
|
+
file['content-type'] = file.type
|
|
643
|
+
|
|
644
|
+
file.id = await getFileId(file)
|
|
645
|
+
if (inputs.get(directory.input).has(file.id)) {
|
|
646
|
+
console.log('Duplicate file has been selected. This could be in error as the browser does not provide a clear way of checking duplictaes')
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
inputs.get(directory.input).set(file.id, file)
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} catch (error) {
|
|
653
|
+
console.log('Error adding file:', error);
|
|
290
654
|
}
|
|
291
655
|
}
|
|
292
656
|
|
|
293
|
-
|
|
657
|
+
async function Delete(file) {
|
|
658
|
+
try {
|
|
659
|
+
if (file.handle) {
|
|
660
|
+
await file.handle.remove();
|
|
661
|
+
if (file.input && file.id)
|
|
662
|
+
inputs.get(file.input).delete(file.id)
|
|
663
|
+
}
|
|
664
|
+
} catch (error) {
|
|
665
|
+
console.log('Error deleting file:', error);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
Observer.init({
|
|
294
670
|
name: 'CoCreateFileAddedNodes',
|
|
295
671
|
observe: ['addedNodes'],
|
|
296
672
|
target: 'input[type="file"]',
|
|
@@ -298,7 +674,7 @@ observer.init({
|
|
|
298
674
|
|
|
299
675
|
});
|
|
300
676
|
|
|
301
|
-
|
|
677
|
+
Observer.init({
|
|
302
678
|
name: 'CoCreateFileAttributes',
|
|
303
679
|
observe: ['attributes'],
|
|
304
680
|
attributeName: ['type'],
|
|
@@ -306,13 +682,36 @@ observer.init({
|
|
|
306
682
|
callback: mutation => init(mutation.target)
|
|
307
683
|
});
|
|
308
684
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
685
|
+
Actions.init([
|
|
686
|
+
{
|
|
687
|
+
name: ["upload", "download", "saveLocally", "import", "export"],
|
|
688
|
+
callback: (action) => {
|
|
689
|
+
if (action.name === 'upload')
|
|
690
|
+
upload(action.element)
|
|
691
|
+
else if (action.name === 'saveLocally' || action.name === 'saveAs') {
|
|
692
|
+
save(action.element)
|
|
693
|
+
} else if (action.name === 'export') {
|
|
694
|
+
Export(action.element)
|
|
695
|
+
} else if (action.name === 'import') {
|
|
696
|
+
Import(action.element)
|
|
697
|
+
} else {
|
|
698
|
+
// Something...
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
document.dispatchEvent(new CustomEvent(action.name, {
|
|
702
|
+
detail: {}
|
|
703
|
+
}));
|
|
704
|
+
|
|
705
|
+
}
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
name: ["createFile", "deleteFile", "createDirectory", "deleteDirectory"],
|
|
709
|
+
callback: (action) => {
|
|
710
|
+
fileRenderAction(action)
|
|
711
|
+
}
|
|
313
712
|
}
|
|
314
|
-
|
|
713
|
+
])
|
|
315
714
|
|
|
316
715
|
init()
|
|
317
716
|
|
|
318
|
-
export default { getFiles,
|
|
717
|
+
export default { inputs, getFiles, create, Delete }
|