From f244e503d7935cbdc70f23eff6eb6067627dbf8f Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Sun, 17 Dec 2023 16:51:07 +0100 Subject: Add a view for similar images --- public/gallery.js | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++--- public/style.css | 9 ++++- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/public/gallery.js b/public/gallery.js index 0967534..f18e4a9 100644 --- a/public/gallery.js +++ b/public/gallery.js @@ -8,6 +8,8 @@ function call(method, params) { }) } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + let BrowseModel = { path: undefined, subdirectories: [], @@ -113,6 +115,8 @@ let Browse = { }, } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + let ViewModel = { sha1: undefined, width: 0, @@ -121,10 +125,12 @@ let ViewModel = { tags: {}, async reload(sha1) { - this.sha1 = sha1 - this.width = this.height = 0 - this.paths = [] - this.tags = {} + if (this.sha1 !== sha1) { + this.sha1 = sha1 + this.width = this.height = 0 + this.paths = [] + this.tags = {} + } let resp = await call('info', {sha1: sha1}) this.width = resp.width @@ -195,20 +201,117 @@ let View = { : "No image.", ]) return m('.container', {}, [ - m('.header', {}, "View"), + m('.header', {}, [ + "View", + m(m.route.Link, { + href: `/similar/:key`, + params: {key: ViewModel.sha1}, + }, "Similar") + ]), m('.body', {}, [view, m(ViewBar)]), ]) }, } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +let SimilarModel = { + sha1: undefined, + info: {paths: []}, + groups: {}, + + async reload(sha1) { + if (this.sha1 !== sha1) { + this.sha1 = sha1 + this.info = {paths: []} + this.groups = {} + } + + let resp = await call('similar', {sha1: sha1}) + this.info = resp.info + this.groups = resp.groups + }, +} + +let SimilarThumbnail = { + view(vnode) { + const info = vnode.attrs.info + return m(m.route.Link, {href: `/view/${info.sha1}`}, + m('img', {src: `/thumb/${info.sha1}`, + width: info.thumbW, height: info.thumbH})) + }, +} + +let SimilarGroup = { + view(vnode) { + const images = vnode.attrs.images + let result = [ + m('h2', vnode.attrs.name), + images.map(info => m('.row', [ + m(SimilarThumbnail, {info}), + m('ul', [ + m('li', Math.round(info.pixelsRatio * 100) + + "% pixels of input image"), + info.paths.map(path => + m('li', m(ViewBarPath, {path}))), + ]), + ])) + ] + if (!images.length) + result.push("No matches.") + return result + }, +} + +let SimilarList = { + view(vnode) { + if (SimilarModel.sha1 === undefined || + SimilarModel.info.paths.length == 0) + return "No image" + + const info = SimilarModel.info + return m('.similar', {}, [ + m('.row', [ + m(SimilarThumbnail, {info}), + m('ul', info.paths.map(path => + m('li', m(ViewBarPath, {path})))), + ]), + Object.entries(SimilarModel.groups).map(([name, images]) => + m(SimilarGroup, {name, images})), + ]) + }, +} + +let Similar = { + oninit(vnode) { + let sha1 = vnode.attrs.key || "" + SimilarModel.reload(sha1) + }, + + view(vnode) { + return m('.container', {}, [ + m('.header', {}, [ + "Similarity search", + m(m.route.Link, { + href: `/view/:key`, + params: {key: SimilarModel.sha1}, + }, "View") + ]), + m('.body', {}, m(SimilarList)), + ]) + }, +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 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/:key": Similar, - "/similar/:sha1": undefined, "/tags": undefined, "/tags/:space": undefined, "/tags/:space/:tag": undefined, diff --git a/public/style.css b/public/style.css index 8a42a51..d6c2e3f 100644 --- a/public/style.css +++ b/public/style.css @@ -7,7 +7,9 @@ a { color: inherit; } .body { display: flex; flex-grow: 1; overflow: hidden; } .header { padding: .25rem .5rem; background: #aaa; color: white; - border-bottom: 1px solid #444; } + border-bottom: 1px solid #444; + display: flex; justify-content: space-between; align-items: baseline; + column-gap: .5rem; } ul.sidebar { margin: 0; padding: 0; background: #eee; border-right: 1px solid #ccc; min-width: 10rem; overflow: auto; } @@ -47,3 +49,8 @@ ul.sidebar li.child a { .viewbar li { margin: 0; padding: 0; } .viewbar meter { width: 1.25rem; /* background: white; border: 1px solid #ccc; */ } + +.similar { padding: .5rem; flex-grow: 1; overflow: auto; } +.similar h2 { margin: 1em 0 0.5em 0; padding: 0; font-size: 1.2rem; } +.similar .row { display: flex; } +.similar .row ul { margin: 0; padding: 0 0 0 1.25em; list-style-type: "- "; } -- cgit v1.2.3-70-g09d2