diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index 21f513c1..3e133962 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -71,8 +71,7 @@ :day="item.day" :key="photo.fileid" @select="selectionManager.selectPhoto" - @delete="deleteFromViewWithAnimation" - @clickImg="clickPhoto" /> + @click="clickPhoto(photo)" /> @@ -103,6 +102,7 @@ import { NcEmptyContent } from '@nextcloud/vue'; import GlobalMixin from '../mixins/GlobalMixin'; import moment from 'moment'; +import { ViewerManager } from "../services/Viewer"; import * as dav from "../services/DavRequests"; import * as utils from "../services/Utils"; import justifiedLayout from "justified-layout"; @@ -179,6 +179,11 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) { /** Scroller manager component */ private scrollerManager!: ScrollerManager & any; + /** Nextcloud viewer proxy */ + private viewerManager = new ViewerManager( + this.deleteFromViewWithAnimation.bind(this), + this.updateLoading.bind(this)); + mounted() { this.selectionManager = this.$refs.selectionManager; this.scrollerManager = this.$refs.scrollerManager; @@ -893,11 +898,13 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) { } /** Clicking on photo */ - clickPhoto(photoComponent: any) { + clickPhoto(photo: IPhoto) { + if (photo.flag & this.c.FLAG_PLACEHOLDER) return; + if (this.selection.size > 0) { // selection mode - photoComponent.toggleSelect(); + this.selectionManager.selectPhoto(photo); } else { - photoComponent.openFile(); + this.viewerManager.open(photo); } } diff --git a/src/components/frame/Photo.vue b/src/components/frame/Photo.vue index fd5da80d..27660789 100644 --- a/src/components/frame/Photo.vue +++ b/src/components/frame/Photo.vue @@ -15,7 +15,7 @@
import { Component, Prop, Emit, Mixins } from 'vue-property-decorator'; import { IDay, IPhoto } from "../../types"; -import { subscribe, unsubscribe } from '@nextcloud/event-bus'; -import { showError } from '@nextcloud/dialogs' import { getPreviewUrl } from "../../services/FileUtils"; -import * as dav from "../../services/DavRequests"; import errorsvg from "../../assets/error.svg"; import GlobalMixin from '../../mixins/GlobalMixin'; @@ -58,9 +55,8 @@ export default class Photo extends Mixins(GlobalMixin) { @Prop() data: IPhoto; @Prop() day: IDay; - @Emit('delete') emitDelete(remPhotos: IPhoto[]) {} @Emit('select') emitSelect(data: IPhoto) {} - @Emit('clickImg') emitClickImg(component: any) {} + @Emit('click') emitClick() {} /** Get src for image to show */ src() { @@ -92,88 +88,8 @@ export default class Photo extends Mixins(GlobalMixin) { clearTimeout(this.touchTimer); } - /** Pass to parent */ - click() { - this.emitClickImg(this); - } - - /** Open viewer */ - async openFile() { - // Check if this is a placeholder - if (this.data.flag & this.c.FLAG_PLACEHOLDER) { - return; - } - - // Check if already loaded fileInfos or load - let fileInfos = this.day.fileInfos; - if (!fileInfos) { - const ids = this.day.detail.map(p => p.fileid); - try { - fileInfos = await dav.getFiles(ids); - } catch (e) { - console.error('Failed to load fileInfos', e); - } - if (fileInfos.length === 0) { - return; - } - - // Fix sorting of the fileInfos - const itemPositions = {}; - for (const [index, id] of ids.entries()) { - itemPositions[id] = index; - } - fileInfos.sort(function (a, b) { - return itemPositions[a.fileid] - itemPositions[b.fileid]; - }); - - // Store in day with a original copy - this.day.fileInfos = fileInfos; - this.day.origFileIds = new Set(fileInfos.map(f => f.fileid)); - } - - // Get this photo in the fileInfos - const photo = fileInfos.find(d => Number(d.fileid) === Number(this.data.fileid)); - if (!photo) { - showError(this.t('memories', 'Cannot find this photo anymore!')); - return; - } - - // Key to store sidebar state - const SIDEBAR_KEY = 'memories:sidebar-open'; - - // Subscribe to delete events - const deleteHandler = ({ fileid }) => { - const photo = this.day.detail.find(p => p.fileid === fileid); - this.emitDelete([photo]); - }; - subscribe('files:file:deleted', deleteHandler); - - // Open viewer - globalThis.OCA.Viewer.open({ - path: photo.filename, // path - list: fileInfos, // file list - canLoop: false, // don't loop - onClose: () => { // on viewer close - if (globalThis.OCA.Files.Sidebar.file) { - localStorage.setItem(SIDEBAR_KEY, '1'); - } else { - localStorage.removeItem(SIDEBAR_KEY); - } - globalThis.OCA.Files.Sidebar.close(); - unsubscribe('files:file:deleted', deleteHandler); - }, - }); - - // Restore sidebar state - if (localStorage.getItem(SIDEBAR_KEY) === '1') { - globalThis.OCA.Files.Sidebar.open(photo.filename); - } - } - toggleSelect() { - if (this.data.flag & this.c.FLAG_PLACEHOLDER) { - return; - } + if (this.data.flag & this.c.FLAG_PLACEHOLDER) return; this.emitSelect(this.data); } diff --git a/src/services/Viewer.ts b/src/services/Viewer.ts new file mode 100644 index 00000000..9fddd61e --- /dev/null +++ b/src/services/Viewer.ts @@ -0,0 +1,87 @@ +import { IFileInfo, IPhoto } from "../types"; +import { showError } from '@nextcloud/dialogs' +import { subscribe } from '@nextcloud/event-bus'; +import { translate as t, translatePlural as n } from '@nextcloud/l10n' +import * as dav from "./DavRequests"; + +// Key to store sidebar state +const SIDEBAR_KEY = 'memories:sidebar-open'; + +export class ViewerManager { + /** Map from fileid to Photo */ + private photoMap = new Map(); + + constructor( + ondelete: (photos: IPhoto[]) => void, + private updateLoading: (delta: number) => void, + ) { + subscribe('files:file:deleted', ({ fileid }: { fileid: number }) => { + const photo = this.photoMap.get(fileid); + ondelete([photo]); + }); + } + + public async open(photo: IPhoto) { + const day = photo.d; + if (!day) return; + + // Repopulate map + this.photoMap.clear(); + for (const p of day.detail) { + this.photoMap.set(p.fileid, p); + } + + // Get file infos + let fileInfos: IFileInfo[]; + const ids = day.detail.map(p => p.fileid); + try { + this.updateLoading(1); + fileInfos = await dav.getFiles(ids); + } catch (e) { + console.error('Failed to load fileInfos', e); + showError('Failed to load fileInfos'); + return; + } finally { + this.updateLoading(-1); + } + if (fileInfos.length === 0) { + return; + } + + // Fix sorting of the fileInfos + const itemPositions = {}; + for (const [index, id] of ids.entries()) { + itemPositions[id] = index; + } + fileInfos.sort(function (a, b) { + return itemPositions[a.fileid] - itemPositions[b.fileid]; + }); + + // Get this photo in the fileInfos + const fInfo = fileInfos.find(d => Number(d.fileid) === photo.fileid); + if (!fInfo) { + showError(t('memories', 'Cannot find this photo anymore!')); + return; + } + + // Open Nextcloud viewer + globalThis.OCA.Viewer.open({ + path: fInfo.filename, // path + list: fileInfos, // file list + canLoop: false, // don't loop + onClose: () => { // on viewer close + if (globalThis.OCA.Files.Sidebar.file) { + localStorage.setItem(SIDEBAR_KEY, '1'); + } else { + localStorage.removeItem(SIDEBAR_KEY); + } + globalThis.OCA.Files.Sidebar.close(); + }, + }); + + // Restore sidebar state + if (localStorage.getItem(SIDEBAR_KEY) === '1') { + globalThis.OCA.Files.Sidebar.open(fInfo.filename); + } + } +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 039f1248..8150f3a8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -24,10 +24,6 @@ export type IDay = { rows?: IRow[]; /** List of photos for this day */ detail?: IPhoto[]; - /** WebDAV fileInfos, fetched before viewer open */ - fileInfos?: IFileInfo[]; - /** Original fileIds from fileInfos */ - origFileIds?: Set; } export type IPhoto = {