summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/gallery.js70
-rw-r--r--public/style.css40
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; }