From 3663a31df08f95c3bc006eb86b60c8d5a7fae9c3 Mon Sep 17 00:00:00 2001 From: Alexander Saltykov Date: Fri, 21 Jul 2023 12:29:07 +0300 Subject: [PATCH 1/8] Adds indication whether an album have this photo already Signed-off-by: Alexander Saltykov --- src/components/modal/AddToAlbumModal.vue | 2 +- src/components/modal/AlbumPicker.vue | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/modal/AddToAlbumModal.vue b/src/components/modal/AddToAlbumModal.vue index effb303b..502dd4f6 100644 --- a/src/components/modal/AddToAlbumModal.vue +++ b/src/components/modal/AddToAlbumModal.vue @@ -5,7 +5,7 @@
- +
diff --git a/src/components/modal/AlbumPicker.vue b/src/components/modal/AlbumPicker.vue index eb0c75b8..4e9407ad 100644 --- a/src/components/modal/AlbumPicker.vue +++ b/src/components/modal/AlbumPicker.vue @@ -16,6 +16,7 @@ @click="pickAlbum(album)" > {{ t('memories', 'Add to album') }} - +
diff --git a/src/components/modal/AddToAlbumModal.vue b/src/components/modal/AddToAlbumModal.vue index 502dd4f6..aa6673d9 100644 --- a/src/components/modal/AddToAlbumModal.vue +++ b/src/components/modal/AddToAlbumModal.vue @@ -5,7 +5,7 @@
- +
@@ -60,19 +60,32 @@ export default defineComponent({ this.$emit('close'); }, - async selectAlbum(album: IAlbum) { + async selectAlbums(albums: IAlbum[]) { if (this.processing) return; + const processed = new Set(); + const photosDone = new Set(); - const name = album.name || album.album_id.toString(); - const gen = dav.addToAlbum(album.user, name, this.photos); - this.processing = true; - - for await (const fids of gen) { - this.photosDone += fids.filter((f) => f).length; - this.added(this.photos.filter((p) => fids.includes(p.fileid))); - } - + await Promise.all(albums.map(async (album) => { + const name = album.name || album.album_id.toString(); + const gen = dav.addToAlbum(album.user, name, this.photos); + this.processing = true; + + for await (const fids of gen) { + fids.forEach((f) => { + if (f) { + photosDone.add(f); + } + }); + this.photos.forEach((p) => { + if (fids.includes(p.fileid)) { + processed.add(p); + } + }); + } + this.photosDone = photosDone.size; + })); const n = this.photosDone; + this.added(Array.from(processed)); showInfo(this.n('memories', '{n} item added to album', '{n} items added to album', n, { n })); this.close(); }, diff --git a/src/components/modal/AlbumPicker.vue b/src/components/modal/AlbumPicker.vue index bd5a12d3..632a4ad7 100644 --- a/src/components/modal/AlbumPicker.vue +++ b/src/components/modal/AlbumPicker.vue @@ -13,7 +13,7 @@ albumName: album.name, }) " - @click="pickAlbum(album)" + @click.prevent="pickAlbum(album)" > @@ -35,17 +35,35 @@ - - - {{ t('memories', 'Create new album') }} - +
+ + + {{ t('memories', 'Create new album') }} + + +
+ + {{ t('memories', 'Add to albums') }} + + + {{ t('memories', 'And remove from') }} {{ n('memories', '{n} album', '{n} albums', unselectedCount , { + n: unselectedCount, + })}} + +
+
(), + unselectedAlbums: new Set(), + selectedCount: 0, + unselectedCount: 0, }), mounted() { @@ -143,6 +165,9 @@ export default defineComponent({ try { const res = await axios.get(API.ALBUM_LIST(3, this.photoId)); this.albums = res.data; + this.selectedAlbums = new Set(this.albums.filter(album => album.has_file)); + this.unselectedAlbums = new Set(); + this.unselectedCount = 0; } catch (e) { console.error(e); } finally { @@ -151,8 +176,28 @@ export default defineComponent({ }, pickAlbum(album: IAlbum) { - this.$emit('select', album); + if (this.selectedAlbums.has(album)) { + this.selectedAlbums.delete(album); + this.unselectedAlbums.add(album) + } else if (this.unselectedAlbums.has(album)) { + this.selectedAlbums.add(album) + this.unselectedAlbums.delete(album); + } else { + this.selectedAlbums.add(album) + } + + this.unselectedCount = this.albums.reduce((acc, album) => { + if (album.has_file && this.unselectedAlbums.has(album)) { + acc += 1; + } + return acc; + }, 0); this.selectedAlbums.size; + this.selectedCount = this.selectedAlbums.size; }, + + submit() { + this.$emit('select', Array.from(this.selectedAlbums)); + } }, }); @@ -231,5 +276,21 @@ export default defineComponent({ .new-album-button { margin-top: 32px; } + + .actions { + display: flex; + justify-content: space-between; + align-items: flex-start; + } + + .submit-btn-wrapper { + display: flex; + flex-direction: column; + align-items: flex-end; + } + + .remove-notice { + font-size: small; + } } From c20b0db07425c6575c209ac192eebe40d9c63b6b Mon Sep 17 00:00:00 2001 From: Alexander Saltykov Date: Thu, 27 Jul 2023 08:44:52 +0300 Subject: [PATCH 6/8] Adds batch removing from albums feature Signed-off-by: Alexander Saltykov --- src/App.vue | 4 +- src/components/AlbumsList.vue | 14 +++--- src/components/modal/AddToAlbumModal.vue | 64 +++++++++++++----------- src/components/modal/AlbumPicker.vue | 2 +- 4 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/App.vue b/src/App.vue index e207a426..2785667f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -286,10 +286,10 @@ export default defineComponent({ this.albumsListComponent?.$destroy?.(); this.albumsListComponent = new Vue(AlbumsList as any); this.albumsListComponent.$mount(el); - this.albumsListComponent.update(Number(fileInfo.id)); + this.albumsListComponent.update(fileInfo); }, update(fileInfo) { - this.albumsListComponent.update(Number(fileInfo.id)); + this.albumsListComponent.update(fileInfo); }, destroy() { this.albumsListComponent?.$destroy?.(); diff --git a/src/components/AlbumsList.vue b/src/components/AlbumsList.vue index 3c2874a0..094744ec 100644 --- a/src/components/AlbumsList.vue +++ b/src/components/AlbumsList.vue @@ -64,6 +64,7 @@ data: () => ({ fileid: -1, + photo: {} as { id: number, name: string }, albums: [] as IAlbum[], loadingAlbums: false, }), @@ -77,15 +78,15 @@ }, methods: { - update(photoId: number){ - this.fileid = photoId; + update(photo: { id: number, name: string }){ + this.photo = photo; this.loadAlbums(); }, async loadAlbums() { try { this.loadingAlbums = true; - const res = await axios.get(API.ALBUM_LIST(3, this.fileid)); + const res = await axios.get(API.ALBUM_LIST(3, this.photo.id)); this.albums = res.data.filter(album => album.has_file); } catch (e) { console.error(e); @@ -95,8 +96,8 @@ }, handleFileUpdated({ fileid }: { fileid: number }) { - if (fileid && this.fileid === fileid) { - this.update(this.fileid); + if (fileid && this.photo.id === fileid) { + this.update(this.photo); } }, @@ -129,7 +130,8 @@ async addToAlbum() { (this.$refs.addToAlbumModal).open([{ - fileid: this.fileid, + fileid: this.photo.id, + basename: this.photo.name, }]); }, }, diff --git a/src/components/modal/AddToAlbumModal.vue b/src/components/modal/AddToAlbumModal.vue index aa6673d9..fae75e63 100644 --- a/src/components/modal/AddToAlbumModal.vue +++ b/src/components/modal/AddToAlbumModal.vue @@ -5,10 +5,10 @@
- +
- +
@@ -37,13 +37,16 @@ export default defineComponent({ data: () => ({ show: false, photos: [] as IPhoto[], - photosDone: 0, + progress: 0, processing: false, + processed: new Set(), + photosDone: 0, + totalOperations: 0, }), methods: { open(photos: IPhoto[]) { - this.photosDone = 0; + this.progress = 0; this.processing = false; this.show = true; this.photos = photos; @@ -60,33 +63,34 @@ export default defineComponent({ this.$emit('close'); }, - async selectAlbums(albums: IAlbum[]) { - if (this.processing) return; - const processed = new Set(); - const photosDone = new Set(); + async processAlbum(album: IAlbum, action: 'add' | 'remove') { + const name = album.name || album.album_id.toString(); + const gen = action === 'add' + ? dav.addToAlbum(album.user, name, this.photos) + : dav.removeFromAlbum(album.user, name, this.photos); + + for await (const fids of gen) { + this.photosDone += fids.length; + this.photos.forEach((p) => { + if (fids.includes(p.fileid)) { + this.processed.add(p); + } + }); + } + this.progress = Math.round((this.photosDone * 100) / this.totalOperations); + }, - await Promise.all(albums.map(async (album) => { - const name = album.name || album.album_id.toString(); - const gen = dav.addToAlbum(album.user, name, this.photos); - this.processing = true; - - for await (const fids of gen) { - fids.forEach((f) => { - if (f) { - photosDone.add(f); - } - }); - this.photos.forEach((p) => { - if (fids.includes(p.fileid)) { - processed.add(p); - } - }); - } - this.photosDone = photosDone.size; - })); - const n = this.photosDone; - this.added(Array.from(processed)); - showInfo(this.n('memories', '{n} item added to album', '{n} items added to album', n, { n })); + async updateAlbums(albumsToAddTo: IAlbum[], albumsToRemoveFrom: IAlbum[] = []) { + if (this.processing) return; + this.processing = true; + this.processed = new Set(); + this.totalOperations = this.photos.length * (albumsToAddTo.length + albumsToRemoveFrom.length); + + await Promise.all(albumsToAddTo.map((album) => this.processAlbum(album, 'add'))); + await Promise.all(albumsToRemoveFrom.map((album) => this.processAlbum(album, 'remove'))); + const n = this.processed.size; + this.added(Array.from(this.processed)); + showInfo(this.n('memories', '{n} processed', '{n} processed', n, { n })); this.close(); }, }, diff --git a/src/components/modal/AlbumPicker.vue b/src/components/modal/AlbumPicker.vue index 632a4ad7..8ebdc00f 100644 --- a/src/components/modal/AlbumPicker.vue +++ b/src/components/modal/AlbumPicker.vue @@ -196,7 +196,7 @@ export default defineComponent({ }, submit() { - this.$emit('select', Array.from(this.selectedAlbums)); + this.$emit('select', Array.from(this.selectedAlbums), Array.from(this.unselectedAlbums)); } }, }); From 67da46f6ddb909218c3c9b2e76f0e357c07bf93f Mon Sep 17 00:00:00 2001 From: Alexander Saltykov Date: Fri, 28 Jul 2023 08:59:19 +0300 Subject: [PATCH 7/8] Adds additional button to menu Signed-off-by: Alexander Saltykov --- src/components/AlbumsList.vue | 2 +- src/components/viewer/Viewer.vue | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/components/AlbumsList.vue b/src/components/AlbumsList.vue index 094744ec..be8b3c34 100644 --- a/src/components/AlbumsList.vue +++ b/src/components/AlbumsList.vue @@ -128,7 +128,7 @@ e.preventDefault(); }, - async addToAlbum() { + addToAlbum() { (this.$refs.addToAlbumModal).open([{ fileid: this.photo.id, basename: this.photo.name, diff --git a/src/components/viewer/Viewer.vue b/src/components/viewer/Viewer.vue index de62d3f2..89f46280 100644 --- a/src/components/viewer/Viewer.vue +++ b/src/components/viewer/Viewer.vue @@ -136,6 +136,17 @@ + + {{ t('memories', 'Add to album') }} + +
@@ -151,6 +162,7 @@
+ @@ -179,6 +191,7 @@ import 'photoswipe/style.css'; import PsImage from './PsImage'; import PsVideo from './PsVideo'; import PsLivePhoto from './PsLivePhoto'; +import AddToAlbumModal from '../modal/AddToAlbumModal.vue'; import ShareIcon from 'vue-material-design-icons/ShareVariant.vue'; import DeleteIcon from 'vue-material-design-icons/TrashCanOutline.vue'; @@ -192,6 +205,7 @@ import SlideshowIcon from 'vue-material-design-icons/PlayBox.vue'; import EditFileIcon from 'vue-material-design-icons/FileEdit.vue'; import AlbumRemoveIcon from 'vue-material-design-icons/BookRemove.vue'; import LivePhotoIcon from 'vue-material-design-icons/MotionPlayOutline.vue'; +import AlbumIcon from 'vue-material-design-icons/ImageAlbum.vue'; const SLIDESHOW_MS = 5000; const BODY_HAS_VIEWER = 'has-viewer'; @@ -216,6 +230,8 @@ export default defineComponent({ EditFileIcon, AlbumRemoveIcon, LivePhotoIcon, + AlbumIcon, + AddToAlbumModal, }, mixins: [UserConfig], @@ -1123,6 +1139,12 @@ export default defineComponent({ editMetadata() { globalThis.editMetadata([globalThis.currentViewerPhoto]); }, + + addToAlbum() { + if (this.currentPhoto) { + (this.$refs.addToAlbumModal).open([this.currentPhoto]); + } + }, }, }); From 9c883b5584493e4e34a0b8e1067da8793d97b89f Mon Sep 17 00:00:00 2001 From: Alexander Saltykov Date: Mon, 31 Jul 2023 18:04:27 +0300 Subject: [PATCH 8/8] Moves AlbumsList component to metadata tab, changes album picker behaviour (click to add, click checkmark icon to select) Signed-off-by: Alexander Saltykov --- src/App.vue | 24 ---------------- src/components/AlbumsList.vue | 41 +++++++++++++++++++--------- src/components/Metadata.vue | 6 ++++ src/components/modal/AlbumPicker.vue | 31 ++++++++++++++++++--- 4 files changed, 61 insertions(+), 41 deletions(-) diff --git a/src/App.vue b/src/App.vue index 2785667f..c2e61124 100644 --- a/src/App.vue +++ b/src/App.vue @@ -79,7 +79,6 @@ import Settings from './components/Settings.vue'; import FirstStart from './components/FirstStart.vue'; import Viewer from './components/viewer/Viewer.vue'; import Metadata from './components/Metadata.vue'; -import AlbumsList from './components/AlbumsList.vue'; import Sidebar from './components/Sidebar.vue'; import EditMetadataModal from './components/modal/EditMetadataModal.vue'; import NodeShareModal from './components/modal/NodeShareModal.vue'; @@ -145,7 +144,6 @@ export default defineComponent({ data: () => ({ navItems: [] as NavItem[], metadataComponent: null as any, - albumsListComponent: null as any, settingsOpen: false, }), @@ -275,28 +273,6 @@ export default defineComponent({ }, }) ); - // Register sidebar albums tab - globalThis.OCA?.Files?.Sidebar?.registerTab( - new globalThis.OCA.Files.Sidebar.Tab({ - id: 'memories-albums', - name: this.t('memories', 'Albums'), - icon: 'icon-details', - - mount(el, fileInfo, context) { - this.albumsListComponent?.$destroy?.(); - this.albumsListComponent = new Vue(AlbumsList as any); - this.albumsListComponent.$mount(el); - this.albumsListComponent.update(fileInfo); - }, - update(fileInfo) { - this.albumsListComponent.update(fileInfo); - }, - destroy() { - this.albumsListComponent?.$destroy?.(); - this.albumsListComponent = null; - }, - }) - ); // Check for native interface if (this.native) { diff --git a/src/components/AlbumsList.vue b/src/components/AlbumsList.vue index be8b3c34..8352f1f7 100644 --- a/src/components/AlbumsList.vue +++ b/src/components/AlbumsList.vue @@ -3,6 +3,17 @@
+ + + {{ t('memories', 'Add to album') }} + {{ t('memories', 'No albums') }}
- - - {{ t('memories', 'Add to album') }} - @@ -61,9 +61,19 @@ Plus, AddToAlbumModal, }, + + props: { + fileid: { + type: Number, + required: true, + }, + filename: { + type: String, + required: true, + }, + }, data: () => ({ - fileid: -1, photo: {} as { id: number, name: string }, albums: [] as IAlbum[], loadingAlbums: false, @@ -71,12 +81,13 @@ mounted() { subscribe('files:file:updated', this.handleFileUpdated); + this.update({ id: this.$props.fileid, name: this.$props.filename }); }, beforeDestroy() { unsubscribe('files:file:updated', this.handleFileUpdated); }, - + methods: { update(photo: { id: number, name: string }){ this.photo = photo; @@ -184,6 +195,10 @@ justify-content: center; display: flex; } + + .new-album-button { + margin: auto; + } \ No newline at end of file diff --git a/src/components/Metadata.vue b/src/components/Metadata.vue index e24efdca..1453b9e8 100644 --- a/src/components/Metadata.vue +++ b/src/components/Metadata.vue @@ -38,6 +38,8 @@