From 515b0bf62c1230c7507ca89e3eb73e471f2880e3 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Thu, 31 Mar 2022 17:09:08 +0200 Subject: [PATCH] UX: Refactor photo viewer API and frontend components #1307 #1438 --- frontend/src/common/scrollbar.js | 5 +- frontend/src/common/viewer.js | 101 ++++++++++++++++-- frontend/src/component/components.js | 2 +- .../{photo/viewer.vue => photo-viewer.vue} | 20 ++-- frontend/src/component/video/player.vue | 2 +- frontend/src/model/thumb.js | 90 ++++++++-------- frontend/src/pages/album/photos.vue | 78 ++------------ frontend/src/pages/library/index.vue | 6 -- frontend/src/pages/photos.vue | 76 ++----------- frontend/src/share/components.js | 8 +- frontend/src/share/photos.vue | 78 ++------------ frontend/tests/unit/model/thumb_test.js | 94 ++++++++-------- internal/search/viewer.go | 72 +++++++------ internal/viewer/result.go | 43 ++++---- 14 files changed, 298 insertions(+), 377 deletions(-) rename frontend/src/component/{photo/viewer.vue => photo-viewer.vue} (93%) diff --git a/frontend/src/common/scrollbar.js b/frontend/src/common/scrollbar.js index 27350588f..583ab0eae 100644 --- a/frontend/src/common/scrollbar.js +++ b/frontend/src/common/scrollbar.js @@ -63,8 +63,11 @@ const Scrollbar = { this.update(preserveOverflow); }, + disabled: function () { + return hidePending > 0; + }, hidden: function () { - return hidePending > 0 || hideDefault; + return this.disabled() || hideDefault; }, }; diff --git a/frontend/src/common/viewer.js b/frontend/src/common/viewer.js index 774000dee..74a7cf794 100644 --- a/frontend/src/common/viewer.js +++ b/frontend/src/common/viewer.js @@ -27,6 +27,8 @@ import PhotoSwipe from "photoswipe"; import PhotoSwipeUI_Default from "photoswipe/dist/photoswipe-ui-default.js"; import Event from "pubsub-js"; import Util from "util.js"; +import Api from "./api"; +import Thumb from "model/thumb"; const thumbs = window.__CONFIG__.thumbs; @@ -119,24 +121,26 @@ class Viewer { // isFake - true when content is added to fake caption container // (used to get size of next or previous caption) - if (!item.title) { + item.title = item.Title; + + if (!item.Title) { captionEl.children[0].innerHTML = ""; return false; } - captionEl.children[0].innerHTML = Util.encodeHTML(item.title); + captionEl.children[0].innerHTML = Util.encodeHTML(item.Title); - if (item.playable) { + if (item.Playable) { captionEl.children[0].innerHTML += ' '; } - if (item.description) { + if (item.Description) { captionEl.children[0].innerHTML += - '
' + Util.encodeHTML(item.description) + ""; + '
' + Util.encodeHTML(item.Description) + ""; } - if (item.playable) { + if (item.Playable) { captionEl.children[0].innerHTML = ""; } @@ -195,9 +199,9 @@ class Viewer { }); gallery.listen("gettingData", function (index, item) { - item.src = item[nextSize].src; - item.w = item[nextSize].w; - item.h = item[nextSize].h; + item.src = item.Thumbs[nextSize].src; + item.w = item.Thumbs[nextSize].w; + item.h = item.Thumbs[nextSize].h; previousSize = nextSize; }); @@ -215,6 +219,85 @@ class Viewer { return "fit_7680"; } + + static show(ctx, index) { + if (ctx.loading || !ctx.listen || ctx.viewer.loading || !ctx.results[index]) { + return false; + } + + const selected = ctx.results[index]; + + if (!ctx.viewer.dirty && ctx.viewer.results && ctx.viewer.results.length > index) { + // Reuse existing viewer result if possible. + let i = -1; + + if (ctx.viewer.results[index] && ctx.viewer.results[index].UID === selected.UID) { + i = index; + } else { + i = ctx.viewer.results.findIndex((p) => p.UID === selected.UID); + } + + if ( + i > -1 && + (((ctx.viewer.complete || ctx.complete) && + ctx.viewer.results.length >= ctx.results.length) || + i + ctx.viewer.batchSize <= ctx.viewer.results.length) + ) { + ctx.$viewer.show(ctx.viewer.results, i); + return; + } + } + + // Fetch photos from server API. + ctx.viewer.loading = true; + + const params = ctx.searchParams(); + params.count = params.offset + ctx.viewer.batchSize; + params.offset = 0; + + // Fetch viewer results from API. + return Api.get("photos/view", { params }) + .then((response) => { + const count = response && response.data ? response.data.length : 0; + if (count === 0) { + ctx.$notify.warn(ctx.$gettext("No pictures found")); + ctx.viewer.dirty = true; + ctx.viewer.complete = false; + return; + } + + // Process response. + if (response.headers && response.headers["x-count"]) { + const c = parseInt(response.headers["x-count"]); + const l = parseInt(response.headers["x-limit"]); + ctx.viewer.complete = c < l; + } else { + ctx.viewer.complete = ctx.complete; + } + + let i; + + if (response.data[index] && response.data[index].UID === selected.UID) { + i = index; + } else { + i = response.data.findIndex((p) => p.UID === selected.UID); + } + + ctx.viewer.results = Thumb.wrap(response.data); + + // Show photos. + ctx.$viewer.show(ctx.viewer.results, i); + ctx.viewer.dirty = false; + }) + .catch(() => { + ctx.viewer.dirty = true; + ctx.viewer.complete = false; + }) + .finally(() => { + // Unblock. + ctx.viewer.loading = false; + }); + } } export default Viewer; diff --git a/frontend/src/component/components.js b/frontend/src/component/components.js index 4cadae651..fe03a4932 100644 --- a/frontend/src/component/components.js +++ b/frontend/src/component/components.js @@ -27,8 +27,8 @@ import PNotify from "component/notify.vue"; import PNavigation from "component/navigation.vue"; import PScrollTop from "component/scroll-top.vue"; import PLoadingBar from "component/loading-bar.vue"; +import PPhotoViewer from "component/photo-viewer.vue"; import PVideoPlayer from "component/video/player.vue"; -import PPhotoViewer from "component/photo/viewer.vue"; import PPhotoToolbar from "component/photo/toolbar.vue"; import PPhotoCards from "component/photo/cards.vue"; import PPhotoMosaic from "component/photo/mosaic.vue"; diff --git a/frontend/src/component/photo/viewer.vue b/frontend/src/component/photo-viewer.vue similarity index 93% rename from frontend/src/component/photo/viewer.vue rename to frontend/src/component/photo-viewer.vue index 847564b70..c7d69a6c7 100644 --- a/frontend/src/component/photo/viewer.vue +++ b/frontend/src/component/photo-viewer.vue @@ -11,7 +11,7 @@
-
{{ formatDate(item.taken) }}
+
{{ formatDate(item.TakenAtLocal) }}
@@ -35,7 +35,7 @@ @@ -158,7 +158,7 @@ export default { this.onPause(); } - if (data.item && this.item && this.item.uid !== data.item.uid) { + if (data.item && this.item && this.item.UID !== data.item.UID) { this.closePlayer(); } @@ -171,8 +171,8 @@ export default { this.$clipboard.toggle(this.item); }, onPlay() { - if (this.item && this.item.playable) { - new Photo().find(this.item.uid).then((video) => this.openPlayer(video)); + if (this.item && this.item.Playable) { + new Photo().find(this.item.UID).then((video) => this.openPlayer(video)); } }, openPlayer(video) { @@ -236,14 +236,14 @@ export default { onDownload() { this.onPause(); - if (!this.item || !this.item.download_url) { + if (!this.item || !this.item.DownloadUrl) { console.warn("photo viewer: no download url"); return; } Notify.success(this.$gettext("Downloading…")); - new Photo().find(this.item.uid).then(p => p.downloadAll()); + new Photo().find(this.item.UID).then(p => p.downloadAll()); }, onEdit() { this.onPause(); @@ -253,15 +253,15 @@ export default { // remove duplicates let filtered = g.items.filter(function (p, i, s) { - return !(i > 0 && p.uid === s[i - 1].uid); + return !(i > 0 && p.UID === s[i - 1].UID); }); let selection = filtered.map((p, i) => { - if (g.currItem.uid === p.uid) { + if (g.currItem.UID === p.UID) { index = i; } - return p.uid; + return p.UID; }); let album = null; diff --git a/frontend/src/component/video/player.vue b/frontend/src/component/video/player.vue index 914d5c8c5..c8f18a9f2 100644 --- a/frontend/src/component/video/player.vue +++ b/frontend/src/component/video/player.vue @@ -10,7 +10,7 @@