From 80136f48c9c91ba0bdcd9911a0eaa0aeae565b8a Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Fri, 15 Dec 2023 19:20:50 +0100 Subject: Port the preliminary UI to Mithril.js --- main.go | 111 ++++++++---------------------------------------------- public/gallery.js | 83 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 103 deletions(-) diff --git a/main.go b/main.go index d00dcec..b01e687 100644 --- a/main.go +++ b/main.go @@ -149,107 +149,16 @@ var page = template.Must(template.New("/").Parse(` - -

{{ .Name }}

- - - {{ range .Entries }} - - - - {{ end }} - + `)) -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// XXX: This is preliminary. -type entry struct { - Parent int64 - Name string - Mtime int64 - Sha1 string - - Thumbw int - Thumbh int - Dhash int64 -} - -// XXX: This is preliminary. -type directory struct { - Id int64 - Name string - Parent int64 - Children []int64 - Entries []entry -} - -func dbCollectDirectory(id int64) (directory, error) { - d := directory{Id: id} - dbID := sql.NullInt64{Int64: id, Valid: id != 0} - if id != 0 { - err := db.QueryRow(`SELECT name, IFNULL(parent, 0) - FROM directory WHERE id IS ?`, dbID).Scan(&d.Name, &d.Parent) - if err != nil { - return d, err - } - } - - rows1, err := db.Query(`SELECT id FROM directory WHERE parent IS ?`, dbID) - if err != nil { - return d, err - } - defer rows1.Close() - for rows1.Next() { - var child int64 - if err := rows1.Scan(&child); err != nil { - return d, err - } - d.Children = append(d.Children, child) - } - if err := rows1.Err(); err != nil { - return d, err - } - - rows2, err := db.Query(`SELECT IFNULL(entry.parent, 0), - entry.name, entry.mtime, entry.sha1, - IFNULL(image.thumbw, 0), IFNULL(image.thumbh, 0), IFNULL(image.dhash, 0) - FROM entry JOIN image ON entry.sha1 = image.sha1 - WHERE entry.parent IS ?`, dbID) - if err != nil { - return d, err - } - defer rows2.Close() - for rows2.Next() { - var e entry - if err := rows2.Scan(&e.Parent, &e.Name, &e.Mtime, &e.Sha1, - &e.Thumbw, &e.Thumbh, &e.Dhash); err != nil { - return d, err - } - d.Entries = append(d.Entries, e) - } - return d, rows2.Err() -} - func handleRequest(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { staticHandler.ServeHTTP(w, r) return } - - id, _ := strconv.ParseInt(r.URL.Query().Get("id"), 10, 64) - d, err := dbCollectDirectory(id) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if err := page.Execute(w, d); err != nil { + if err := page.Execute(w, nil); err != nil { log.Println(err) } } @@ -274,13 +183,15 @@ func handleThumbs(w http.ResponseWriter, r *http.Request) { func getSubdirectories(tx *sql.Tx, parent int64) (names []string, err error) { // TODO: This is like dbCollectStrings(), just needs an argument. - rows, err := tx.Query(`SELECT name FROM directory WHERE parent = ?`, - parent) + // TODO: Should this return full paths, or not? Clean it up. + rows, err := tx.Query( + `SELECT name FROM directory WHERE IFNULL(parent, 0) = ?`, parent) if err != nil { return nil, err } defer rows.Close() + names = []string{} for rows.Next() { var name string if err := rows.Scan(&name); err != nil { @@ -309,6 +220,7 @@ func getSubentries(tx *sql.Tx, parent int64) (entries []webEntry, err error) { } defer rows.Close() + entries = []webEntry{} for rows.Next() { var e webEntry if err := rows.Scan( @@ -362,6 +274,8 @@ func handleAPIBrowse(w http.ResponseWriter, r *http.Request) { } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + func getImagePaths(sha1 string) (paths []string, err error) { rows, err := db.Query(`WITH RECURSIVE paths(parent, path) AS ( SELECT parent, name AS path FROM entry WHERE sha1 = ? @@ -446,6 +360,8 @@ func handleAPIInfo(w http.ResponseWriter, r *http.Request) { } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // cmdRun runs a web UI against GD on ADDRESS. func cmdRun(args []string) error { if len(args) != 2 { @@ -514,10 +430,13 @@ func idForPath(tx *sql.Tx, path []string, create bool) (int64, error) { } func decodeWebPath(path string) []string { + // Trailing slashes don't provide information. + path = strings.TrimSuffix(path, "/") + // Relative paths could be handled differently, // but right now, they're assumed to start at the root. list := strings.Split(path, "/") - if len(list) > 1 && list[0] == "" { + if len(list) > 0 && list[0] == "" { list = list[1:] } return list diff --git a/public/gallery.js b/public/gallery.js index 1935220..d25c544 100644 --- a/public/gallery.js +++ b/public/gallery.js @@ -3,27 +3,96 @@ function call(method, params) { return m.request({ method: "POST", - url: `/api/{method}`, + url: `/api/${method}`, body: params, }) } +let BrowseModel = { + path: undefined, + subdirectories: [], + entries: [], + + async reload(path) { + this.path = path + this.subdirectories = [] + this.entries = [] + + let resp = await call('browse', {path: path}) + this.subdirectories = resp.subdirectories + this.entries = resp.entries + }, +} + let Browse = { - view: vnode => { - return m('') + // Reload the model immediately, to improve responsibility. + // 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', {}, "Browse"), + m('h1', "Root"), + m('ul', BrowseModel.subdirectories.map(sd => { + const name = sd.split('/').pop() + return m('li', m(m.route.Link, { + href: `/browse/:key`, + params: {key: `${BrowseModel.path}/${sd}`}, + }, 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})) + })), + ]) + }, +} + +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 = { - view: vnode => { - return m('') + oninit(vnode) { + let sha1 = vnode.attrs.key || "" + ViewModel.reload(sha1) + }, + + view(vnode) { + // TODO: Show more information. + return m('.container', {}, [ + m('.header', {}, "View"), + m('ul', ViewModel.paths.map(sd => m('li', sd))), + ViewModel.sha1 !== undefined + ? m('img', {src: `/image/${ViewModel.sha1}`}) + : "No image.", + ]) }, } window.addEventListener('load', () => { m.route(document.body, "/browse/", { - "/browse/:path": Browse, - "/view/:sha1": View, + // 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, -- cgit v1.2.3-70-g09d2