refactor: viewerManager
parent
156e0fc779
commit
1f0132b097
|
@ -71,8 +71,7 @@
|
||||||
:day="item.day"
|
:day="item.day"
|
||||||
:key="photo.fileid"
|
:key="photo.fileid"
|
||||||
@select="selectionManager.selectPhoto"
|
@select="selectionManager.selectPhoto"
|
||||||
@delete="deleteFromViewWithAnimation"
|
@click="clickPhoto(photo)" />
|
||||||
@clickImg="clickPhoto" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
@ -103,6 +102,7 @@ import { NcEmptyContent } from '@nextcloud/vue';
|
||||||
import GlobalMixin from '../mixins/GlobalMixin';
|
import GlobalMixin from '../mixins/GlobalMixin';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { ViewerManager } from "../services/Viewer";
|
||||||
import * as dav from "../services/DavRequests";
|
import * as dav from "../services/DavRequests";
|
||||||
import * as utils from "../services/Utils";
|
import * as utils from "../services/Utils";
|
||||||
import justifiedLayout from "justified-layout";
|
import justifiedLayout from "justified-layout";
|
||||||
|
@ -179,6 +179,11 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
/** Scroller manager component */
|
/** Scroller manager component */
|
||||||
private scrollerManager!: ScrollerManager & any;
|
private scrollerManager!: ScrollerManager & any;
|
||||||
|
|
||||||
|
/** Nextcloud viewer proxy */
|
||||||
|
private viewerManager = new ViewerManager(
|
||||||
|
this.deleteFromViewWithAnimation.bind(this),
|
||||||
|
this.updateLoading.bind(this));
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.selectionManager = this.$refs.selectionManager;
|
this.selectionManager = this.$refs.selectionManager;
|
||||||
this.scrollerManager = this.$refs.scrollerManager;
|
this.scrollerManager = this.$refs.scrollerManager;
|
||||||
|
@ -893,11 +898,13 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clicking on photo */
|
/** Clicking on photo */
|
||||||
clickPhoto(photoComponent: any) {
|
clickPhoto(photo: IPhoto) {
|
||||||
|
if (photo.flag & this.c.FLAG_PLACEHOLDER) return;
|
||||||
|
|
||||||
if (this.selection.size > 0) { // selection mode
|
if (this.selection.size > 0) { // selection mode
|
||||||
photoComponent.toggleSelect();
|
this.selectionManager.selectPhoto(photo);
|
||||||
} else {
|
} else {
|
||||||
photoComponent.openFile();
|
this.viewerManager.open(photo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<Star :size="20" v-if="data.flag & c.FLAG_IS_FAVORITE" />
|
<Star :size="20" v-if="data.flag & c.FLAG_IS_FAVORITE" />
|
||||||
|
|
||||||
<div class="img-outer fill-block"
|
<div class="img-outer fill-block"
|
||||||
@click="click"
|
@click="emitClick"
|
||||||
@contextmenu="contextmenu"
|
@contextmenu="contextmenu"
|
||||||
@touchstart="touchstart"
|
@touchstart="touchstart"
|
||||||
@touchmove="touchend"
|
@touchmove="touchend"
|
||||||
|
@ -33,11 +33,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Emit, Mixins } from 'vue-property-decorator';
|
import { Component, Prop, Emit, Mixins } from 'vue-property-decorator';
|
||||||
import { IDay, IPhoto } from "../../types";
|
import { IDay, IPhoto } from "../../types";
|
||||||
import { subscribe, unsubscribe } from '@nextcloud/event-bus';
|
|
||||||
|
|
||||||
import { showError } from '@nextcloud/dialogs'
|
|
||||||
import { getPreviewUrl } from "../../services/FileUtils";
|
import { getPreviewUrl } from "../../services/FileUtils";
|
||||||
import * as dav from "../../services/DavRequests";
|
|
||||||
import errorsvg from "../../assets/error.svg";
|
import errorsvg from "../../assets/error.svg";
|
||||||
import GlobalMixin from '../../mixins/GlobalMixin';
|
import GlobalMixin from '../../mixins/GlobalMixin';
|
||||||
|
|
||||||
|
@ -58,9 +55,8 @@ export default class Photo extends Mixins(GlobalMixin) {
|
||||||
@Prop() data: IPhoto;
|
@Prop() data: IPhoto;
|
||||||
@Prop() day: IDay;
|
@Prop() day: IDay;
|
||||||
|
|
||||||
@Emit('delete') emitDelete(remPhotos: IPhoto[]) {}
|
|
||||||
@Emit('select') emitSelect(data: IPhoto) {}
|
@Emit('select') emitSelect(data: IPhoto) {}
|
||||||
@Emit('clickImg') emitClickImg(component: any) {}
|
@Emit('click') emitClick() {}
|
||||||
|
|
||||||
/** Get src for image to show */
|
/** Get src for image to show */
|
||||||
src() {
|
src() {
|
||||||
|
@ -92,88 +88,8 @@ export default class Photo extends Mixins(GlobalMixin) {
|
||||||
clearTimeout(this.touchTimer);
|
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() {
|
toggleSelect() {
|
||||||
if (this.data.flag & this.c.FLAG_PLACEHOLDER) {
|
if (this.data.flag & this.c.FLAG_PLACEHOLDER) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.emitSelect(this.data);
|
this.emitSelect(this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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<number, IPhoto>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,10 +24,6 @@ export type IDay = {
|
||||||
rows?: IRow[];
|
rows?: IRow[];
|
||||||
/** List of photos for this day */
|
/** List of photos for this day */
|
||||||
detail?: IPhoto[];
|
detail?: IPhoto[];
|
||||||
/** WebDAV fileInfos, fetched before viewer open */
|
|
||||||
fileInfos?: IFileInfo[];
|
|
||||||
/** Original fileIds from fileInfos */
|
|
||||||
origFileIds?: Set<number>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IPhoto = {
|
export type IPhoto = {
|
||||||
|
|
Loading…
Reference in New Issue