// code is borrowed from // https://googlechrome.github.io/samples/service-worker/post-message/ const send_message = (message) => { return new Promise(function(resolve) { const messageChannel = new MessageChannel(); messageChannel.port1.onmessage = function(event) { resolve(event.data) }; if(navigator.serviceWorker.controller == null) { // Service worker will be available after reload window.location.reload() } navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]); }); } globalThis.clear_directory_handle = () => { send_message({type: 'SET', data: null}) window.location.reload() } let dir_handle const request_directory_handle = async () => { dir_handle = await showDirectoryPicker() await send_message({type: 'SET', data: dir_handle}) return dir_handle } export const load_persisted_directory_handle = () => { return navigator.serviceWorker.register('service_worker.js') .then(() => navigator.serviceWorker.ready) .then(() => send_message({type: 'GET'})) .then(async h => { if(h == null || (await h.queryPermission()) != 'granted') { return null } // test if directory handle is valid try { await h.entries().next() } catch(e) { return null } dir_handle = h return dir_handle }) } const file_handle = async (dir_handle, filename, is_directory = false, options) => { if(typeof(filename) == 'string') { filename = filename.split('/') } const [first, ...rest] = filename if(rest.length == 0) { return is_directory ? await dir_handle.getDirectoryHandle(first, options) : await dir_handle.getFileHandle(first, options) } else { const nested_dir_handle = await dir_handle.getDirectoryHandle(first) return file_handle(nested_dir_handle, rest, is_directory, options) } } export const write_file = async (name, contents) => { const f_hanlde = await file_handle(dir_handle, name) // Create a FileSystemWritableFileStream to write to. const writable = await f_hanlde.createWritable() // Write the contents of the file to the stream. await writable.write(contents) // Close the file and write the contents to disk. await writable.close() } // Blacklist hidden dirs and node_modules const is_blacklisted = h => h.name == 'node_modules' || h.name.startsWith('.') const read_file = async handle => { const file_data = await handle.getFile() return await file_data.text() } const do_load_dir = async (handle, path) => { if(handle.kind == 'directory') { const children = [] for await (let [name, h] of handle) { if(!is_blacklisted(h)) { children.push(h) } } return { name: handle.name, path, kind: 'directory', children: await Promise.all( children .map(c => do_load_dir(c, path == null ? c.name : path + '/' + c.name) ) .sort((a,b) => a.name > b.name) ) } } else if(handle.kind == 'file') { return { name: handle.name, path, kind: 'file', contents: await read_file(handle) } } else { throw new Error('unknown kind') } } export const create_file = (path, is_dir) => { return file_handle( dir_handle, path, is_dir, {create: true} ) } export const load_dir = async (should_request_access) => { let handle if(should_request_access) { handle = await request_directory_handle() } else { handle = await load_persisted_directory_handle() if(handle == null) { return null } } return do_load_dir(handle, null) }