viewer: initial commit

pull/175/head
Varun Patil 2022-11-05 14:14:38 -07:00 committed by Varun Patil
parent f86e255d60
commit 8a130c3efa
7 changed files with 233 additions and 77 deletions

14
package-lock.json generated
View File

@ -17,6 +17,7 @@
"justified-layout": "^4.1.0",
"moment": "^2.29.4",
"path-posix": "^1.0.0",
"photoswipe": "^5.3.3",
"reflect-metadata": "^0.1.13",
"vue": "^2.7.10",
"vue-class-component": "^7.2.6",
@ -6673,6 +6674,14 @@
"node": ">=0.12"
}
},
"node_modules/photoswipe": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.3.3.tgz",
"integrity": "sha512-BUuulwZwkYFKADSe5xf0dd+wf6dws34ZvqP8R3oYHepRauOXoQHvw600sw1HlWd8K0S3LRCS4jxyR5fTuI383Q==",
"engines": {
"node": ">= 0.12.0"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@ -14840,6 +14849,11 @@
"sha.js": "^2.4.8"
}
},
"photoswipe": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.3.3.tgz",
"integrity": "sha512-BUuulwZwkYFKADSe5xf0dd+wf6dws34ZvqP8R3oYHepRauOXoQHvw600sw1HlWd8K0S3LRCS4jxyR5fTuI383Q=="
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",

View File

@ -37,6 +37,7 @@
"justified-layout": "^4.1.0",
"moment": "^2.29.4",
"path-posix": "^1.0.0",
"photoswipe": "^5.3.3",
"reflect-metadata": "^0.1.13",
"vue": "^2.7.10",
"vue-class-component": "^7.2.6",

View File

@ -213,11 +213,10 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
private scrollerManager!: ScrollerManager & any;
/** Nextcloud viewer proxy */
private viewerManager = new ViewerManager(
this.deleteFromViewWithAnimation.bind(this),
this.updateLoading.bind(this),
this.$route
);
private viewerManager = new ViewerManager({
ondelete: this.deleteFromViewWithAnimation,
fetchDay: this.fetchDay,
});
mounted() {
this.selectionManager = this.$refs.selectionManager;
@ -1141,7 +1140,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
// selection mode
this.selectionManager.selectPhoto(photo);
} else {
this.viewerManager.open(photo);
this.viewerManager.open(photo, this.list);
}
}

View File

@ -1,5 +1,6 @@
<template>
<div
:id="`memories-photo-${data.key || data.fileid}`"
class="p-outer fill-block"
:class="{
selected: data.flag & c.FLAG_SELECTED,

View File

@ -200,7 +200,7 @@ export default class OnThisDay extends Mixins(GlobalMixin) {
click(year: IYear) {
const allPhotos = this.years.flatMap((y) => y.photos);
this.viewerManager.open(year.preview, allPhotos);
// this.viewerManager.open(year.preview, allPhotos);
}
}
</script>

View File

@ -94,6 +94,10 @@ export function hashCode(str: string): number {
* @param key Key to use for comparison
*/
export function binarySearch(arr: any, elem: any, key?: string) {
const desc = key
? arr[0][key] > arr[arr.length - 1][key]
: arr[0] > arr[arr.length - 1];
let minIndex = 0;
let maxIndex = arr.length - 1;
let currentIndex: number;
@ -103,9 +107,12 @@ export function binarySearch(arr: any, elem: any, key?: string) {
currentIndex = ((minIndex + maxIndex) / 2) | 0;
currentElement = key ? arr[currentIndex][key] : arr[currentIndex];
if (currentElement < elem) {
const e1 = desc ? elem : currentElement;
const e2 = desc ? currentElement : elem;
if (e1 < e2) {
minIndex = currentIndex + 1;
} else if (currentElement > elem) {
} else if (e1 > e2) {
maxIndex = currentIndex - 1;
} else {
return currentIndex;

View File

@ -1,94 +1,228 @@
import { IFileInfo, IPhoto } from "../types";
import { IDay, IFileInfo, IPhoto, IRow, IRowType } from "../types";
import { showError } from "@nextcloud/dialogs";
import { subscribe } from "@nextcloud/event-bus";
import { translate as t, translatePlural as n } from "@nextcloud/l10n";
import { Route } from "vue-router";
import * as dav from "./DavRequests";
import PhotoSwipe from "photoswipe";
import "photoswipe/style.css";
import { getPreviewUrl } from "./FileUtils";
import * as utils from "./Utils";
// Key to store sidebar state
const SIDEBAR_KEY = "memories:sidebar-open";
// Options
type opts_t = {
ondelete: (photos: IPhoto[]) => void;
fetchDay: (dayId: number) => void;
};
export class ViewerManager {
/** Map from fileid to Photo */
private photoMap = new Map<number, IPhoto>();
constructor(
ondelete: (photos: IPhoto[]) => void,
private updateLoading: (delta: number) => void,
private $route: Route
) {
constructor(private opts: opts_t) {
subscribe("files:file:deleted", ({ fileid }: { fileid: number }) => {
const photo = this.photoMap.get(fileid);
ondelete([photo]);
opts.ondelete([photo]);
});
}
public async open(photo: IPhoto, list?: IPhoto[]) {
list = list || photo.d?.detail;
if (!list?.length) return;
public async open(anchorPhoto: IPhoto, rows?: IRow[]) {
// list = list || photo.d?.detail;
// if (!list?.length) return;
// // Repopulate map
// this.photoMap.clear();
// for (const p of list) {
// this.photoMap.set(p.fileid, p);
// }
// // Get file infos
// let fileInfos: IFileInfo[];
// try {
// this.updateLoading(1);
// fileInfos = await dav.getFiles(list);
// } 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, p] of list.entries()) {
// itemPositions[p.fileid] = 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;
// }
// // Check viewer > 2.0.0
// const viewerVersion: string = globalThis.OCA.Viewer.version;
// const viewerMajor = Number(viewerVersion.split(".")[0]);
// // Open Nextcloud viewer
// globalThis.OCA.Viewer.open({
// fileInfo: fInfo,
// path: viewerMajor < 2 ? fInfo.filename : undefined, // Only specify path upto Nextcloud 24
// 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);
// }
// Repopulate map
this.photoMap.clear();
for (const p of list) {
this.photoMap.set(p.fileid, p);
const list = [...anchorPhoto.d.detail];
const days = new Map<number, IDay>();
const dayIds = [];
let globalCount = 0;
let globalAnchor = -1;
let localAnchor = -1;
for (const r of rows) {
if (r.type === IRowType.HEAD) {
if (r.day.dayid == anchorPhoto.d.dayid) {
localAnchor = r.day.detail.findIndex(
(p) => p.fileid === anchorPhoto.fileid
);
globalAnchor = globalCount + localAnchor;
}
// Get file infos
let fileInfos: IFileInfo[];
try {
this.updateLoading(1);
fileInfos = await dav.getFiles(list);
} catch (e) {
console.error("Failed to load fileInfos", e);
showError("Failed to load fileInfos");
return;
} finally {
this.updateLoading(-1);
globalCount += r.day.count;
days.set(r.day.dayid, r.day);
dayIds.push(r.day.dayid);
}
if (fileInfos.length === 0) {
return;
}
// Fix sorting of the fileInfos
const itemPositions = {};
for (const [index, p] of list.entries()) {
itemPositions[p.fileid] = index;
}
fileInfos.sort(function (a, b) {
return itemPositions[a.fileid] - itemPositions[b.fileid];
const lightbox = new PhotoSwipe({
// arrowPrev: false,
// arrowNext: false,
// zoom: false,
// close: false,
counter: true,
loop: false,
index: globalAnchor,
});
// 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;
lightbox.addFilter("uiElement", (element, data) => {
// add button-vue class if button
if (element.classList.contains("pswp__button")) {
element.classList.add("button-vue");
}
// Check viewer > 2.0.0
const viewerVersion: string = globalThis.OCA.Viewer.version;
const viewerMajor = Number(viewerVersion.split(".")[0]);
// Open Nextcloud viewer
globalThis.OCA.Viewer.open({
fileInfo: fInfo,
path: viewerMajor < 2 ? fInfo.filename : undefined, // Only specify path upto Nextcloud 24
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();
},
return element;
});
// Restore sidebar state
if (localStorage.getItem(SIDEBAR_KEY) === "1") {
globalThis.OCA.Files.Sidebar.open(fInfo.filename);
lightbox.addFilter("numItems", (numItems) => {
return globalCount;
});
lightbox.addFilter("itemData", (itemData, index) => {
console.log(index);
// Get photo object from list
let idx = index - globalAnchor + localAnchor;
if (idx < 0) {
// Load previous day
const firstDayId = list[0].d.dayid;
const firstDayIdx = utils.binarySearch(dayIds, firstDayId);
if (firstDayIdx === 0) {
// No previous day
return {};
}
const prevDayId = dayIds[firstDayIdx - 1];
const prevDay = days.get(prevDayId);
if (!prevDay.detail) {
console.error("[BUG] No detail for previous day");
return {};
}
list.unshift(...prevDay.detail);
localAnchor += prevDay.count;
} else if (idx >= list.length) {
// Load next day
const lastDayId = list[list.length - 1].d.dayid;
const lastDayIdx = utils.binarySearch(dayIds, lastDayId);
if (lastDayIdx === dayIds.length - 1) {
// No next day
return {};
}
const nextDayId = dayIds[lastDayIdx + 1];
const nextDay = days.get(nextDayId);
if (!nextDay.detail) {
console.error("[BUG] No detail for next day");
return {};
}
list.push(...nextDay.detail);
}
idx = index - globalAnchor + localAnchor;
const photo = list[idx];
// Something went really wrong
if (!photo) {
return {};
}
// Preload next and previous 3 days
const dayIdx = utils.binarySearch(dayIds, photo.d.dayid);
const preload = (idx: number) => {
if (idx > 0 && idx < dayIds.length && !days.get(dayIds[idx]).detail) {
this.opts.fetchDay(dayIds[idx]);
console.log("Preload", dayIds[idx]);
}
};
preload(dayIdx - 1);
preload(dayIdx - 2);
preload(dayIdx - 3);
preload(dayIdx + 1);
preload(dayIdx + 2);
preload(dayIdx + 3);
// Get thumb image
const thumbSrc: string =
this.thumbElem(photo)?.querySelector("img")?.getAttribute("src") ||
getPreviewUrl(photo, false, 256);
// Get full image
return {
src: getPreviewUrl(photo, false, 256),
msrc: thumbSrc,
width: photo.w || undefined,
height: photo.h || undefined,
thumbCropped: true,
};
});
lightbox.addFilter("thumbEl", (thumbEl, data, index) => {
const photo = list[index - globalAnchor + localAnchor];
return this.thumbElem(photo) || thumbEl;
});
lightbox.init();
}
private thumbElem(photo: IPhoto) {
if (!photo) return;
return document.getElementById(
`memories-photo-${photo.key || photo.fileid}`
);
}
}