From 65648450a4be9ffbf4f1b4a15f5a4e984daef9e4 Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Tue, 21 May 2019 11:54:39 +0200 Subject: [PATCH] Frontend: Add global clipboard for photo selection #15 --- frontend/src/app.js | 10 +- frontend/src/common/clipboard.js | 105 +++++++++++++++++++++ frontend/src/component/p-photo-details.vue | 10 +- frontend/src/component/p-photo-list.vue | 4 +- frontend/src/component/p-photo-mosaic.vue | 8 +- frontend/src/component/p-photo-tiles.vue | 10 +- frontend/src/model/abstract.js | 2 +- frontend/src/model/user.js | 2 +- frontend/src/pages/photos.vue | 10 +- 9 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 frontend/src/common/clipboard.js diff --git a/frontend/src/app.js b/frontend/src/app.js index 478eb8093..f6f0d1423 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -5,6 +5,7 @@ import PhotoPrism from "photoprism.vue"; import Routes from "routes"; import Api from "common/api"; import Config from "common/config"; +import Clipboard from "common/clipboard"; import Components from "component/register"; import Maps from "maps/register"; import Alert from "common/alert"; @@ -16,17 +17,20 @@ import InfiniteScroll from "vue-infinite-scroll"; import VueTruncate from "vue-truncate-filter"; import VueFullscreen from "vue-fullscreen"; -// Initialize client-side session +// Initialize helpers const session = new Session(window.localStorage); const config = new Config(window.localStorage, window.appConfig); +const viewer = new Viewer(); +const clipboard = new Clipboard(window.localStorage, "photo_clipboard"); -// Set global helpers +// Assign helpers to VueJS prototype Vue.prototype.$event = Event; Vue.prototype.$alert = Alert; -Vue.prototype.$viewer = new Viewer; +Vue.prototype.$viewer = viewer; Vue.prototype.$session = session; Vue.prototype.$api = Api; Vue.prototype.$config = config; +Vue.prototype.$clipboard = clipboard; // Register Vuetify Vue.use(Vuetify, { diff --git a/frontend/src/common/clipboard.js b/frontend/src/common/clipboard.js new file mode 100644 index 000000000..3188d8cb6 --- /dev/null +++ b/frontend/src/common/clipboard.js @@ -0,0 +1,105 @@ +class Clipboard { + /** + * @param {Storage} storage + * @param {string} key + */ + constructor(storage, key) { + this.storageKey = key ? key : "clipboard"; + + this.storage = storage; + this.selectionMap = {}; + this.selection = []; + + this.loadFromStorage(); + } + + loadFromStorage() { + const photosJson = this.storage.getItem(this.storageKey); + + if (photosJson !== null && typeof photosJson !== "undefined") { + this.setIds(JSON.parse(photosJson)); + } + } + + saveToStorage() { + this.storage.setItem(this.storageKey, JSON.stringify(this.selection)); + } + + toggle(model) { + const id = model.getId(); + this.toggleId(id); + } + + toggleId(id) { + const index = this.selection.indexOf(id); + + if (index === -1) { + this.selection.push(id); + this.selectionMap["id:" + id] = true; + } else { + this.selection.splice(index, 1); + delete this.selectionMap["id:" + id]; + } + + this.saveToStorage(); + } + + add(model) { + const id = model.getId(); + + this.addId(id); + } + + addId(id) { + if (this.hasId(id)) return; + + this.selection.push(id); + this.selectionMap["id:" + id] = true; + + this.saveToStorage(); + } + + has(model) { + return this.hasId(model.getId()) + } + + hasId(id) { + return typeof this.selectionMap["id:" + id] !== "undefined"; + } + + remove(model) { + const id = model.getId(); + + if (!this.hasId(id)) return; + + const index = this.selection.indexOf(id); + + this.selection.splice(index, 1); + delete this.selectionMap["id:" + id]; + + this.saveToStorage(); + } + + getIds() { + return this.selection; + } + + setIds(ids) { + if (!Array.isArray(ids)) return; + + this.selection = ids; + this.selectionMap = {}; + + for (let i = 0; i < this.selection.length; i++) { + this.selectionMap["id:" + this.selection[i]] = true; + } + } + + clear() { + this.selectionMap = {}; + this.selection.splice(0, this.selection.length); + this.storage.removeItem(this.storageKey); + } +} + +export default Clipboard; diff --git a/frontend/src/component/p-photo-details.vue b/frontend/src/component/p-photo-details.vue index 8619ad764..10f387d02 100644 --- a/frontend/src/component/p-photo-details.vue +++ b/frontend/src/component/p-photo-details.vue @@ -17,12 +17,12 @@ > + :dark="$clipboard.has(photo)" + :class="$clipboard.has(photo) ? 'elevation-15 ma-1' : 'elevation-2 ma-2'"> - - check_circle + check_circle radio_button_off diff --git a/frontend/src/component/p-photo-list.vue b/frontend/src/component/p-photo-list.vue index 1b4e972a1..d44552639 100644 --- a/frontend/src/component/p-photo-list.vue +++ b/frontend/src/component/p-photo-list.vue @@ -14,8 +14,8 @@ - check_circle - radio_button_off + check_circle + radio_button_off {{ props.item.PhotoTitle }} diff --git a/frontend/src/component/p-photo-mosaic.vue b/frontend/src/component/p-photo-mosaic.vue index 83bab432c..9711dfd0f 100644 --- a/frontend/src/component/p-photo-mosaic.vue +++ b/frontend/src/component/p-photo-mosaic.vue @@ -12,13 +12,13 @@ + :class="$clipboard.has(photo) ? 'elevation-15 ma-1' : hover ? 'elevation-6 ma-2' : 'elevation-2 ma-2'"> - - check_circle + check_circle radio_button_off diff --git a/frontend/src/component/p-photo-tiles.vue b/frontend/src/component/p-photo-tiles.vue index 5ca364e7a..78bea3b08 100644 --- a/frontend/src/component/p-photo-tiles.vue +++ b/frontend/src/component/p-photo-tiles.vue @@ -12,13 +12,13 @@ + :class="$clipboard.has(photo) ? 'elevation-15 ma-1' : hover ? 'elevation-6 ma-2' : 'elevation-2 ma-2'"> - - check_circle - radio_button_off + check_circle + radio_button_off