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