UNPKG

janusweb

Version:
453 lines (432 loc) 14.4 kB
elation.extend('engine.assetloader.agent.janus-vfs', new function() { this.initBrowserFS = function() { return new Promise((accept, reject) => { BrowserFS.configure({ fs: "MountableFileSystem", options: { //'/': { fs: "LocalStorage" }, //'/blurp': { fs: "IndexedDB", options: { storeName: 'janusVFS' } }, '/': { fs: "IndexedDB", options: { storeName: 'janusVFS' } }, //'/mnt/usb0': { fs: "LocalStorage" } } }, (e) => { if (e) { } else { var fs = BrowserFS.BFSRequire('fs'); this.fs = fs; accept(this.fs); } }); }); } this.getFS = async function() { if (!this.fs) { await this.initBrowserFS(); } return this.fs; } this.fetch = async function(url) { let fs = await this.getFS(); let pathparts = url.split('://'); return new Promise((resolve, reject) => { let filepath = pathparts[1]; fs.readFile(filepath, ( e, file) => { if (file) { resolve({ target: { responseURL: url, data: file.buffer, response: file.buffer, getResponseHeader(header) { if (header.toLowerCase() == 'content-type') { // FIXME - We should implement mime type detection at a lower level, and avoid the need to access the XHR object in calling code return 'application/octet-stream'; } } }, }); } else { reject(); } }); }); } }); elation.elements.define('janus.ui.vfs.browser', class extends elation.elements.base { init() { super.init(); this.defineAttributes({ cwd: { type: 'string', default: '/' }, chroot: { type: 'string', default: '' }, }); } create() { super.create(); /* this.win = elation.elements.create('ui-window', { append: document.body, center: true, windowtitle: '/', }); */ this.content = elation.elements.create('ui-rowlayout', { append: this, }); this.controls = elation.elements.create('ui-content', { append: this.content, }); this.updirbutton = elation.elements.create('ui-button', { append: this.controls, label: '⬅', onclick: ev => this.handleUpDirectoryClick(ev), }); this.deletebutton = elation.elements.create('ui-button', { append: this.controls, label: '🗑️', onclick: ev => this.handleDeleteClick(ev), }); this.addbuttonlist = elation.elements.create('ui-list', { selectable: true, items: ['Upload', 'Directory', 'HTML', 'JS'], onselectionchange: ev => this.handleAddButtonSelect(ev), }); this.addbutton = elation.elements.create('ui-popupbutton', { append: this.controls, label: '➕', popupcontent: this.addbuttonlist, }); this.mainpanel = elation.elements.create('ui-flexpanel', { append: this.content, }); this.dirtree = elation.elements.create('ui-treeview', { append: this.mainpanel, attrs: { name: 'path', label: 'name', }, }); elation.events.add(this.dirtree, 'ui_treeview_select', ev => this.handleDirectoryTreeSelect(ev)); this.dirlist = elation.elements.create('janus-ui-vfs-browser-directorylist', { append: this.mainpanel, itemcomponent: 'janus-ui-vfs-browser-file', selectable: true, multiselect: true, draggable: true, }); elation.events.add(this.dirlist, 'directorychange', ev => this.handleDirectoryChange(ev)); elation.events.add(this.dirlist, 'selectionchange', ev => this.handleFilesSelectionChange(ev)); elation.events.add(this.dirlist, 'dragstart', ev => this.handleDragStart(ev)); elation.events.add(this.dirlist, 'dragover', ev => this.handleDragOver(ev)); elation.events.add(this.dirlist, 'dragenter', ev => this.handleDragEnter(ev)); elation.events.add(this.dirlist, 'dragleave', ev => this.handleDragLeave(ev)); elation.events.add(this.dirlist, 'drop', ev => this.handleDrop(ev)); elation.events.add(this.dirlist, 'keypress', ev => this.handleFilesKeypress(ev)); //this.win.setcontent(this); this.updateDirectories(); this.updateFiles(); } async updateFiles() { let fs = await elation.engine.assetloader.agent['janus-vfs'].getFS(); fs.readdir(this.chroot + this.cwd, (e, contents) => { let entries = {}; let promises = []; for (let i = 0; i < contents.length; i++) { promises.push(new Promise(accept => { fs.stat(this.chroot + this.cwd + contents[i], (e, stats) => { entries[contents[i]] = { name: contents[i], path: this.cwd, stats: stats, }; accept(stats); }); })); } Promise.all(promises).then(() => { this.dirlist.clear(); this.dirlist.setItems(entries); }); }); this.updirbutton.disabled = (this.cwd == '/'); this.deletebutton.disabled = (this.dirlist.selection.length == 0); //this.win.settitle(this.cwd); } async updateDirectories() { let dirtree = { '/': { name: '/', path: '/', items: await this.getDirectoryTree(), } }; this.dirtree.setItems(dirtree); let selected = this.dirtree.find(this.cwd); if (selected) { selected.select(); } console.log('my full dirtree', dirtree, this.dirtree, selected); } async getDirectoryTree(dir='/', entries) { if (!entries) entries = {}; let fs = await elation.engine.assetloader.agent['janus-vfs'].getFS(); return new Promise(async (resolve, reject) => { fs.readdir(this.chroot + dir, async (e, contents) => { let promises = []; for (let i = 0; i < contents.length; i++) { promises.push(new Promise(accept => { let stats = fs.stat(this.chroot + dir + contents[i], async (e, stats) => { if (stats.isDirectory()) { entries[contents[i]] = { name: contents[i], path: dir + contents[i] + '/', stats: stats, }; let children = await this.getDirectoryTree(dir + contents[i] + '/'); entries[contents[i]].items = (Object.keys(children).length > 0 ? children : false); } accept(); }); })); } await Promise.all(promises); resolve(entries); }); }); } chdir(dir) { this.cwd = dir + (dir.lastIndexOf('/') != dir.length - 1 ? '/' : ''); this.dirlist.selectall(false); let selected = this.dirtree.find(this.cwd); if (selected && selected !== this.dirtree.selected) { selected.select(); } else { this.updateFiles(); } } updir() { let pathparts = this.cwd.split('/'); pathparts.pop(); pathparts.pop(); let newpath = (pathparts.length > 0 ? pathparts.join('/') + '/' : '/'); //newpath = newpath.replaceAll('//', '/'); this.chdir(newpath); } async mkdir(dir) { let fs = await elation.engine.assetloader.agent['janus-vfs'].getFS(); let fulldir = this.chroot + (dir[0] == '/' ? dir : this.cwd + dir); await fs.mkdir(fulldir); this.updateDirectories(); this.updateFiles(); } async rm(files) { let fs = await elation.engine.assetloader.agent['janus-vfs'].getFS(); if (!elation.utils.isArray(files)) files = [files]; console.log('delete files', files); let promises = []; files.forEach(file => { promises.push(new Promise((resolve, reject) => { fs.unlink(this.chroot + this.cwd + file, e => { if (!e) { resolve(); } else { reject(); } }); })); }); await Promise.all(promises); this.updateFiles(); } async rmdir(dirs) { let fs = await elation.engine.assetloader.agent['janus-vfs'].getFS(); if (!elation.utils.isArray(dirs)) dirs = [dirs]; console.log('delete dirs', dirs); let promises = []; dirs.forEach(dir => { promises.push(new Promise((resolve, reject) => { fs.rmdir(this.chroot + this.cwd + dir, e => { if (!e) { resolve(); } else { reject(); } }); })); }); await Promise.all(promises); this.updateDirectories(); this.updateFiles(); } showUploadPrompt() { let input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.style.width = 0; input.style.height = 0; input.style.overflow = 'hidden'; input.addEventListener('change', ev => this.handleFileUpload(ev)); this.appendChild(input); input.click(); } handleDirectoryTreeSelect(ev) { let newpath = ev.data.item.path; this.chdir(newpath); } handleDirectoryChange(ev) { let newdir = ev.target; let newpath = newdir.path + newdir.name; this.chdir(newpath); } handleUpDirectoryClick(ev) { if (!ev.target.disabled) { this.updir(); } } handleDeleteClick(ev) { if (this.dirlist.selection.length > 0) { console.log('delete', this.dirlist.selection); let files = this.dirlist.selection.filter(n => !n.stats.isDirectory()).map(n => n.name), dirs = this.dirlist.selection.filter(n => n.stats.isDirectory()).map(n => n.name); if (files.length > 0) this.rm(files); if (dirs.length > 0) this.rmdir(dirs); this.dirlist.selectall(false); } } handleAddButtonSelect(ev) { let selected = this.addbuttonlist.selection[0]; if (selected) { this.addbuttonlist.selectall(false); this.addbutton.hidePopup(); switch(selected.value) { case 'Upload': this.showUploadPrompt(); break; case 'Directory': let dirname = prompt('Directory name?'); // FIXME - make this an HTML pop-up prompt if (dirname !== null) { this.mkdir(dirname); } } } } handleFilesSelectionChange(ev) { this.deletebutton.disabled = (this.dirlist.selection.length == 0); } handleFilesKeypress(ev) { if (ev.keyCode == 127) { this.handleDeleteClick(ev); } } async handleFileUpload(ev) { let files = ev.target.files; console.log('upload some files', ev, files); if (files && files.length > 0) { let filenames = await this.saveFiles(files); console.log('finished writing files', filenames); } } handleDragStart(ev) { let urilist = ''; this.dirlist.selection.forEach(f => { urilist += 'janus-vfs://' + this.chroot + this.cwd + f.name + '\r\n'; }); ev.dataTransfer.setData('text/uri-list', urilist); console.log('drag it!', urilist, ev, this.dirlist.selection); } handleDragOver(ev) { ev.dataTransfer.dropEffect = "copy" ev.preventDefault(); } handleDragEnter(ev) { let dragtarget = this.getDragTarget(ev); if (dragtarget && !dragtarget.hasclass('dragactive')) { dragtarget.addclass('dragactive'); } } handleDragLeave(ev) { //let dragtarget = this.getDragTarget(ev); let dragtarget = ev.target; if (dragtarget && elation.html.hasclass(dragtarget, 'dragactive')) elation.html.removeclass(dragtarget, 'dragactive'); if (this.dirlist.hasclass('dragactive') && !this.dirlist.contains(dragtarget)) this.dirlist.removeclass('dragactive'); } async handleDrop(ev) { ev.preventDefault(); let dragtarget = this.getDragTarget(ev); if (dragtarget) dragtarget.removeclass('dragactive'); let files = ev.dataTransfer.files; let subdir = (dragtarget instanceof elation.elements.janus.ui.vfs.browser.file && dragtarget.stats.isDirectory() ? dragtarget.name : ''); console.log('upload some files', ev, files); if (files && files.length > 0) { let filenames = await this.saveFiles(files, subdir); console.log('finished writing files', filenames); } } getDragTarget(ev) { let dragtarget = ev.target; while (dragtarget !== this.dirlist && !(dragtarget instanceof elation.elements.janus.ui.vfs.browser.file && dragtarget.stats.isDirectory())) { dragtarget = dragtarget.parentNode; } console.log('my dragtarget!', dragtarget); return dragtarget; } async saveFiles(files, subdir) { let fs = await elation.engine.assetloader.agent['janus-vfs'].getFS(); let Buffer = BrowserFS.BFSRequire('buffer').Buffer; let promises = []; for (let i = 0; i < files.length; i++) { let file = files[i]; let fname = this.chroot + this.cwd + (subdir ? subdir + '/' : '') + file.name; console.log('Got file!', file.name, file.size, fname); promises.push(new Promise((accept, reject) => { let reader = new FileReader(); reader.onload = function() { let buffer = Buffer.from(this.result); fs.writeFile(fname, buffer, (e, f) => { accept(fname); }); } reader.readAsArrayBuffer(file); })); } Promise.all(promises).then((filenames) => { this.updateFiles(); return filenames; }); } }); elation.elements.define('janus.ui.vfs.browser.directorytree', class extends elation.elements.ui.treeview { create() { super.create(); } }); elation.elements.define('janus.ui.vfs.browser.directorylist', class extends elation.elements.ui.list { create() { super.create(); } }); elation.elements.define('janus.ui.vfs.browser.directory', class extends elation.elements.ui.item { create() { } }); elation.elements.define('janus.ui.vfs.browser.file', class extends elation.elements.ui.item { create() { super.create(); this.name = this.value.name; this.path = this.value.path; this.stats = this.value.stats; this.title = this.name; elation.elements.create('ui-label', { append: this, label: this.name + (this.stats.isDirectory() ? '/' : ''), }); this.addEventListener('dblclick', ev => this.handleDblClick(ev)); } handleDblClick(ev) { if (this.stats.isDirectory()) { this.dispatchEvent({type: 'directorychange', bubbles: true}); } else { console.log('do something with this thing!', this); } ev.preventDefault(); } });