'use strict' function call(method, params) { return m.request({ method: "POST", url: `/api/${method}`, body: params, }) } let BrowseModel = { path: undefined, subdirectories: [], entries: [], async reload(path) { if (this.path !== path) { this.path = path this.subdirectories = [] this.entries = [] } let resp = await call('browse', {path: path}) this.subdirectories = resp.subdirectories this.entries = resp.entries }, joinPath(parent, child) { if (!parent) return child if (!child) return parent return `${parent}/${child}` }, getBrowseLinks() { if (this.path === undefined) return [] let links = [{name: "Root", path: "", level: -1}], path for (const crumb of this.path.split('/').filter(s => !!s)) { path = this.joinPath(path, crumb) links.push({name: crumb, path: path, level: -1}) } links[links.length - 1].level = 0 for (const sub of this.subdirectories) { links.push( {name: sub, path: this.joinPath(this.path, sub), level: +1}) } return links }, } let BrowseLink = { view(vnode) { const link = vnode.attrs.link let c = 'selected' if (link.level < 0) c = 'parent' if (link.level > 0) c = 'child' return m('li', { class: c, }, m(m.route.Link, { href: `/browse/:key`, params: {key: link.path}, }, link.name)) }, } let BrowseView = { // So that Page Up/Down, etc., work after changing directories. // Programmatically focusing a scrollable element requires setting tabindex, // and causes :focus-visible on page load, which we suppress in CSS. // I wish there was another way, but the workaround isn't particularly bad. // focus({focusVisible: true}) is FF 104+ only and experimental. oncreate(vnode) { vnode.dom.focus() }, view(vnode) { return m('.browser[tabindex=0]', { key: BrowseModel.path, }, BrowseModel.entries.map(e => { return m(m.route.Link, {href: `/view/${e.sha1}`}, m('img', {src: `/thumb/${e.sha1}`, width: e.thumbW, height: e.thumbH, title: e.name})) })) }, } let Browse = { // Reload the model immediately, to improve responsivity. // But we don't need to: https://mithril.js.org/route.html#preloading-data // Also see: https://mithril.js.org/route.html#route-cancellation--blocking oninit(vnode) { let path = vnode.attrs.key || "" BrowseModel.reload(path) }, view(vnode) { return m('.container', {}, [ m('.header', {}, "Browser"), m('.body', {}, [ m('ul.sidebar', BrowseModel.getBrowseLinks().map(link => m(BrowseLink, {link}))), m(BrowseView), ]), ]) }, } let ViewModel = { sha1: undefined, paths: [], tags: {}, async reload(sha1) { this.sha1 = sha1 this.paths = [] this.tags = {} let resp = await call('info', {sha1: sha1}) this.paths = resp.paths this.tags = resp.tags }, } let View = { oninit(vnode) { let sha1 = vnode.attrs.key || "" ViewModel.reload(sha1) }, view(vnode) { const view = m('.view', [ ViewModel.sha1 !== undefined ? m('img', {src: `/image/${ViewModel.sha1}`}) : "No image.", ]) const viewbar = m('.viewbar', [ m('h2', "Locations"), m('ul', ViewModel.paths.map(sd => m('li', sd))), m('h2', "Tags"), Object.entries(ViewModel.tags).map(([group, tags]) => [ m("h3", group), m("ul", Object.entries(tags).map( ([tag, weight]) => m("li", `${tag}: ${weight}`))), ]), ]) return m('.container', {}, [ m('.header', {}, "View"), m('.body', {}, [ view, viewbar, ]), ]) }, } window.addEventListener('load', () => { m.route(document.body, "/browse/", { // The path doesn't need to be escaped, perhaps change that (":key..."). "/browse/": Browse, "/browse/:key": Browse, "/view/:key": View, "/similar/:sha1": undefined, "/tags": undefined, "/tags/:space": undefined, "/tags/:space/:tag": undefined, }) })