diff options
author | Přemysl Eric Janouch <p@janouch.name> | 2023-12-16 19:15:32 +0100 |
---|---|---|
committer | Přemysl Eric Janouch <p@janouch.name> | 2023-12-16 19:15:32 +0100 |
commit | 4e694d07851c6d6f9d0650b610a085939ed0938d (patch) | |
tree | 6ecb6a693e9d3fdf3b4bf4654e90387213c497c1 | |
parent | 11f637e43c120e528a94cb3073883187a8d696b2 (diff) | |
download | gallery-4e694d07851c6d6f9d0650b610a085939ed0938d.tar.gz gallery-4e694d07851c6d6f9d0650b610a085939ed0938d.tar.xz gallery-4e694d07851c6d6f9d0650b610a085939ed0938d.zip |
Style the web at all
-rw-r--r-- | public/gallery.js | 70 | ||||
-rw-r--r-- | public/style.css | 40 |
2 files changed, 94 insertions, 16 deletions
diff --git a/public/gallery.js b/public/gallery.js index 940b0d2..bf73c84 100644 --- a/public/gallery.js +++ b/public/gallery.js @@ -53,6 +53,44 @@ let BrowseModel = { }, } +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 @@ -64,20 +102,12 @@ let Browse = { view(vnode) { return m('.container', {}, [ - m('.header', {}, "Browse"), - m('ul', BrowseModel.getBrowseLinks().map(link => { - // TODO: Differentiate the level as CSS classes. - // - Last entry (current path) should look selected. - return m('li', m(m.route.Link, { - href: `/browse/:key`, - params: {key: link.path}, - }, `${link.level} ${link.name}`)) - })), - m('.browser', {}, 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})) - })), + m('.header', {}, "Browser"), + m('.body', {}, [ + m('ul.sidebar', BrowseModel.getBrowseLinks().map(link => + m(BrowseLink, {link}))), + m(BrowseView), + ]), ]) }, } @@ -105,11 +135,12 @@ let View = { }, view(vnode) { - return m('.container', {}, [ - m('.header', {}, "View"), + 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"), @@ -119,6 +150,13 @@ let View = { ([tag, weight]) => m("li", `${tag}: ${weight}`))), ]), ]) + return m('.container', {}, [ + m('.header', {}, "View"), + m('.body', {}, [ + view, + viewbar, + ]), + ]) }, } diff --git a/public/style.css b/public/style.css index e69de29..7daa69b 100644 --- a/public/style.css +++ b/public/style.css @@ -0,0 +1,40 @@ +body { margin: 0; padding: 0; font-family: sans-serif; } + +.container { display: flex; flex-direction: column; + height: 100vh; width: 100vw; overflow: hidden; } +.body { display: flex; flex-grow: 1; overflow: hidden; } + +.header { padding: .25rem .5rem; background: #aaa; color: white; + border-bottom: 1px solid #444; } + +ul.sidebar { margin: 0; padding: 0; background: #eee; + border-right: 1px solid #ccc; min-width: 10rem; overflow: auto; } +ul.sidebar li { margin: 0; padding: 0; } +ul.sidebar li a { padding: .25rem .5rem; padding-left: 30px; + color: #000; display: block; text-decoration: none; white-space: nowrap; } +ul.sidebar li a:hover { background: #ddd; } + +ul.sidebar li.parent a { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath d='M 4 14 10 8 16 14' stroke='%23888' stroke-width='4' fill='none' /%3E%3C/svg%3E%0A"); + background-repeat: no-repeat; background-position: 5px center; } + +ul.sidebar li.selected a { font-weight: bold; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Ccircle cx='10' cy='10' r='6' fill='%23888' /%3E%3C/svg%3E%0A"); + background-repeat: no-repeat; background-position: 5px center; } + +ul.sidebar li.child a { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath d='M 4 6 10 12 16 6' stroke='%23888' stroke-width='4' fill='none' /%3E%3C/svg%3E%0A"); + background-repeat: no-repeat; background-position: 5px center; } + +.browser { overflow: auto; display: flex; flex-wrap: wrap; + align-content: flex-start; justify-content: center; align-items: center; + gap: 10px; padding: 9px; } +.browser:focus-visible { outline: 0; box-shadow: 0; } +.browser img { display: block; box-shadow: 0 0 3px rgba(0, 0, 0, 0.75); } + +.view { overflow: hidden; display: flex; + justify-content: center; align-items: center; } +.view img { max-width: 100%; max-height: 100%; } + +.viewbar { padding: .25rem .5rem; background: #eee; + border-left: 1px solid #ccc; min-width: 20rem; overflow: auto; } |