Skip to content

Commit af50769

Browse files
committed
feat: client and server functions
1 parent f2f5b0a commit af50769

File tree

2 files changed

+672
-0
lines changed

2 files changed

+672
-0
lines changed

src/client.js

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
import observer from '@cocreate/observer';
2+
import crud from '@cocreate/crud-client';
3+
import action from '@cocreate/actions';
4+
import render from '@cocreate/render';
5+
import '@cocreate/element-prototype';
6+
7+
let inputs = new Map();
8+
9+
function init(elements) {
10+
// Returns an array of elements.
11+
if (!elements)
12+
elements = document.querySelectorAll('[type="file"]')
13+
14+
// If elements is an array of elements returns an array of elements.
15+
else if (!Array.isArray(elements))
16+
elements = [elements]
17+
for (let i = 0; i < elements.length; i++) {
18+
if (elements[i].tagName !== 'INPUT') {
19+
// TODO: create input and append to div if input dos not exist
20+
}
21+
elements[i].getValue = async () => await getSelectedFiles([elements[i]], true)
22+
23+
// elements[i].setValue = (value) => pickr.setColor(value);
24+
if (elements[i].hasAttribute('directory')) {
25+
if (window.showDirectoryPicker)
26+
elements[i].addEventListener("click", selectDirectory);
27+
else if ('webkitdirectory' in elements[i]) {
28+
elements[i].webkitdirectory = true
29+
elements[i].addEventListener("change", handleFileInputChange)
30+
} else
31+
console.error("Directory selection not supported in this browser.");
32+
} else if (window.showOpenFilePicker)
33+
elements[i].addEventListener("click", selectFile);
34+
else
35+
elements[i].addEventListener("change", handleFileInputChange);
36+
}
37+
}
38+
39+
40+
function handleFileInputChange(event) {
41+
const input = event.target;
42+
const files = input.files;
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+
}
49+
50+
async function selectFile(event) {
51+
event.preventDefault()
52+
const input = event.target;
53+
let selected = inputs.get(input) || []
54+
try {
55+
const multiple = input.multiple
56+
const selectedFiles = await window.showOpenFilePicker({ multiple });
57+
58+
for (const handle of selectedFiles) {
59+
selected.push(handle)
60+
}
61+
62+
if (selected.length) {
63+
inputs.set(input, selected);
64+
console.log("Files selected:", selected);
65+
renderFiles(input)
66+
}
67+
68+
} catch (error) {
69+
if (error.name !== 'AbortError') {
70+
console.error("Error selecting files:", error);
71+
}
72+
}
73+
}
74+
75+
async function selectDirectory(event) {
76+
event.preventDefault()
77+
const input = event.target;
78+
let selected = inputs.get(input) || []
79+
80+
try {
81+
const handle = await window.showDirectoryPicker();
82+
selected.push(handle)
83+
84+
if (selected.length) {
85+
inputs.set(input, selected);
86+
console.log("Directory selected:", selected);
87+
renderFiles(input)
88+
}
89+
} catch (error) {
90+
if (error.name !== 'AbortError') {
91+
console.error("Error selecting directory:", error);
92+
}
93+
}
94+
}
95+
96+
async function getNewFileHandle() {
97+
// const options = {
98+
// types: [
99+
// {
100+
// description: 'Text Files',
101+
// accept: {
102+
// 'text/plain': ['.txt'],
103+
// },
104+
// },
105+
// ],
106+
// };
107+
const handle = await window.showSaveFilePicker(options);
108+
return handle;
109+
}
110+
111+
async function getSelectedFiles(fileInputs, isObject) {
112+
const files = [];
113+
114+
if (!Array.isArray(fileInputs))
115+
fileInputs = [fileInputs]
116+
117+
for (let input of fileInputs) {
118+
const selected = inputs.get(input) || []
119+
120+
for (let i = 0; i < selected.length; i++) {
121+
let file
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)
148+
file = await readFile(file)
149+
150+
files.push(file)
151+
}
152+
153+
}
154+
}
155+
156+
return files
157+
}
158+
159+
async function getSelectedDirectoryFiles(handle, name) {
160+
let files = [];
161+
for await (const entry of handle.values()) {
162+
entry.directory = '/' + name
163+
entry.parentDirectory = name.split("/").pop();
164+
entry.path = '/' + name + '/' + entry.name
165+
if (!entry.webkitRelativePath)
166+
entry.webkitRelativePath = name
167+
168+
if (entry.kind === 'file') {
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);
175+
}
176+
}
177+
return files;
178+
}
179+
180+
// This function reads the file and returns its src
181+
function readFile(file) {
182+
// Return a new promise that resolves the file object
183+
return new Promise((resolve) => {
184+
file["content-type"] = file.type
185+
186+
// Split the file type into an array
187+
const fileType = file.type.split('/');
188+
let readAs;
189+
190+
// Check if the file type is a directory
191+
if (fileType[1] === 'directory') {
192+
return resolve(file)
193+
}
194+
// Check if the file type is a image
195+
else if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(fileType[1])
196+
|| fileType[0] === 'image') {
197+
readAs = 'readAsDataURL';
198+
}
199+
// Check if the file type is a video
200+
else if (['mp4', 'avi', 'mov', 'mpeg', 'flv'].includes(fileType[1])
201+
|| fileType[0] === 'video') {
202+
readAs = 'readAsDataURL';
203+
}
204+
// Check if the file type is an audio
205+
else if (['mp3', 'wav', 'wma', 'aac', 'ogg'].includes(fileType[1])
206+
|| fileType[0] === 'audio') { // updated condition
207+
readAs = 'readAsDataURL';
208+
}
209+
// Check if the file type is a pdf
210+
else if (fileType[1] === 'pdf') {
211+
readAs = 'readAsDataURL';
212+
}
213+
// Check if the file type is a document
214+
else if (['doc', 'msword', 'docx', 'xlsx', 'pptx'].includes(fileType[1])) {
215+
readAs = 'readAsBinaryString';
216+
}
217+
// Otherwise, assume the file type is text
218+
else {
219+
readAs = 'readAsText';
220+
}
221+
222+
// Create a FileReader instance to read the file
223+
const reader = new FileReader();
224+
// Read the file based on the file type
225+
reader[readAs](file);
226+
// When the file is loaded, resolve the file object
227+
reader.onload = () => {
228+
file.src = reader.result;
229+
// If the file type is a document, convert it to base64 encoding
230+
if (['doc', 'msword', 'docx', 'xlsx', 'pptx'].includes(fileType)) {
231+
file.src = btoa(file.src);
232+
}
233+
234+
// Resolve the file object
235+
resolve(file);
236+
};
237+
});
238+
}
239+
240+
async function fileAction(btn, params, action) {
241+
const form = btn.closest('form')
242+
let inputs = form.querySelectorAll('input[type="file"]')
243+
let fileObjects = await getSelectedFiles(Array.from(inputs), true)
244+
245+
console.log('fileObjects', fileObjects)
246+
document.dispatchEvent(new CustomEvent(action, {
247+
detail: {}
248+
}));
249+
250+
}
251+
252+
// may be best to use getValue() so form so inputtype files can be can be managed in forms
253+
async function save(inputs, collection, document_id) {
254+
let files = await getSelectedFiles(inputs, true)
255+
256+
let response = await crud.updateDocument({
257+
collection,
258+
document: files,
259+
upsert: true
260+
});
261+
262+
if (response && (!document_id || document_id !== response.document_id)) {
263+
crud.setDocumentId(element, collection, response.document_id);
264+
}
265+
266+
return response
267+
}
268+
269+
async function getFiles(inputs) {
270+
let files = await getSelectedFiles(inputs)
271+
return files
272+
}
273+
274+
async function getObjects(inputs) {
275+
let objects = await getSelectedFiles(inputs, true)
276+
return objects
277+
}
278+
279+
function renderFiles(input) {
280+
// TODO: support
281+
let template_id = input.getAttribute('template_id')
282+
if (template_id) {
283+
// if data items are handle it will not yet have all the details
284+
const data = inputs.get(input)
285+
if (data.length) return
286+
render.data({
287+
selector: `[template='${template_id}']`,
288+
data
289+
});
290+
}
291+
}
292+
293+
observer.init({
294+
name: 'CoCreateFileAddedNodes',
295+
observe: ['addedNodes'],
296+
target: 'input[type="file"]',
297+
callback: mutation => init(mutation.target)
298+
299+
});
300+
301+
observer.init({
302+
name: 'CoCreateFileAttributes',
303+
observe: ['attributes'],
304+
attributeName: ['type'],
305+
target: 'input[type="file"]',
306+
callback: mutation => init(mutation.target)
307+
});
308+
309+
action.init({
310+
name: "upload",
311+
callback: (btn, params) => {
312+
fileAction(btn, params, "upload")
313+
}
314+
})
315+
316+
init()
317+
318+
export default { getFiles, getObjects }

0 commit comments

Comments
 (0)