Add fav to viewer
parent
6146b513b2
commit
daf079f101
|
@ -276,6 +276,9 @@ body {
|
|||
z-index: 3000;
|
||||
}
|
||||
}
|
||||
#content-vue.has-viewer {
|
||||
z-index: 3000;
|
||||
}
|
||||
|
||||
// Patch viewer to remove the title and
|
||||
// make the image fill the entire screen
|
||||
|
|
|
@ -44,7 +44,6 @@
|
|||
|
||||
<OnThisDay
|
||||
v-if="$route.name === 'timeline'"
|
||||
:viewerManager="viewerManager"
|
||||
:key="config_timelinePath"
|
||||
@load="scrollerManager.adjust()"
|
||||
>
|
||||
|
@ -121,35 +120,46 @@
|
|||
@delete="deleteFromViewWithAnimation"
|
||||
@updateLoading="updateLoading"
|
||||
/>
|
||||
|
||||
<Viewer
|
||||
ref="viewer"
|
||||
@deleted="deleteFromViewWithAnimation"
|
||||
@fetchDay="fetchDay"
|
||||
@updateLoading="updateLoading"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Mixins, Watch } from "vue-property-decorator";
|
||||
import GlobalMixin from "../mixins/GlobalMixin";
|
||||
import UserConfig from "../mixins/UserConfig";
|
||||
|
||||
import axios from "@nextcloud/axios";
|
||||
import { showError } from "@nextcloud/dialogs";
|
||||
import { subscribe, unsubscribe } from "@nextcloud/event-bus";
|
||||
import { generateUrl } from "@nextcloud/router";
|
||||
import { NcEmptyContent } from "@nextcloud/vue";
|
||||
import PeopleIcon from "vue-material-design-icons/AccountMultiple.vue";
|
||||
import CheckCircle from "vue-material-design-icons/CheckCircle.vue";
|
||||
import ImageMultipleIcon from "vue-material-design-icons/ImageMultiple.vue";
|
||||
import ArchiveIcon from "vue-material-design-icons/PackageDown.vue";
|
||||
import { Component, Mixins, Watch } from "vue-property-decorator";
|
||||
import GlobalMixin from "../mixins/GlobalMixin";
|
||||
import UserConfig from "../mixins/UserConfig";
|
||||
import * as dav from "../services/DavRequests";
|
||||
|
||||
import { getLayout } from "../services/Layout";
|
||||
import * as utils from "../services/Utils";
|
||||
import { ViewerManager } from "../services/Viewer";
|
||||
import { IDay, IFolder, IHeadRow, IPhoto, IRow, IRowType } from "../types";
|
||||
import Folder from "./frame/Folder.vue";
|
||||
import Photo from "./frame/Photo.vue";
|
||||
import Tag from "./frame/Tag.vue";
|
||||
import ScrollerManager from "./ScrollerManager.vue";
|
||||
import SelectionManager from "./SelectionManager.vue";
|
||||
import Viewer from "./Viewer.vue";
|
||||
import OnThisDay from "./top-matter/OnThisDay.vue";
|
||||
import TopMatter from "./top-matter/TopMatter.vue";
|
||||
|
||||
import * as dav from "../services/DavRequests";
|
||||
import * as utils from "../services/Utils";
|
||||
|
||||
import PeopleIcon from "vue-material-design-icons/AccountMultiple.vue";
|
||||
import CheckCircle from "vue-material-design-icons/CheckCircle.vue";
|
||||
import ImageMultipleIcon from "vue-material-design-icons/ImageMultiple.vue";
|
||||
import ArchiveIcon from "vue-material-design-icons/PackageDown.vue";
|
||||
|
||||
const SCROLL_LOAD_DELAY = 100; // Delay in loading data when scrolling
|
||||
const DESKTOP_ROW_HEIGHT = 200; // Height of row on desktop
|
||||
const MOBILE_ROW_HEIGHT = 120; // Approx row height on mobile
|
||||
|
@ -163,6 +173,7 @@ const MOBILE_ROW_HEIGHT = 120; // Approx row height on mobile
|
|||
OnThisDay,
|
||||
SelectionManager,
|
||||
ScrollerManager,
|
||||
Viewer,
|
||||
NcEmptyContent,
|
||||
|
||||
CheckCircle,
|
||||
|
@ -212,12 +223,6 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
/** Scroller manager component */
|
||||
private scrollerManager!: ScrollerManager & any;
|
||||
|
||||
/** Nextcloud viewer proxy */
|
||||
private viewerManager = new ViewerManager({
|
||||
ondelete: this.deleteFromViewWithAnimation,
|
||||
fetchDay: this.fetchDay,
|
||||
});
|
||||
|
||||
mounted() {
|
||||
this.selectionManager = this.$refs.selectionManager;
|
||||
this.scrollerManager = this.$refs.scrollerManager;
|
||||
|
@ -1140,7 +1145,7 @@ export default class Timeline extends Mixins(GlobalMixin, UserConfig) {
|
|||
// selection mode
|
||||
this.selectionManager.selectPhoto(photo);
|
||||
} else {
|
||||
this.viewerManager.open(photo, this.list);
|
||||
(<any>this.$refs.viewer).open(photo, this.list);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,369 @@
|
|||
<template>
|
||||
<div class="memories_viewer outer">
|
||||
<div class="inner" ref="inner">
|
||||
<div class="top-bar" v-if="photoswipe">
|
||||
<NcActions :inline="2" container=".memories_viewer .pswp">
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Delete')"
|
||||
@click="deleteCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
{{ t("memories", "Delete") }}
|
||||
<template #icon> <DeleteIcon :size="24" /> </template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Favorite')"
|
||||
@click="favoriteCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
{{ t("memories", "Favorite") }}
|
||||
<template #icon>
|
||||
<StarIcon v-if="isFavorite()" :size="24" />
|
||||
<StarOutlineIcon v-else :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Emit, Mixins } from "vue-property-decorator";
|
||||
|
||||
import GlobalMixin from "../mixins/GlobalMixin";
|
||||
import { IDay, IPhoto, IRow, IRowType } from "../types";
|
||||
|
||||
import { NcActions, NcActionButton } from "@nextcloud/vue";
|
||||
|
||||
import * as dav from "../services/DavRequests";
|
||||
import * as utils from "../services/Utils";
|
||||
import { getPreviewUrl } from "../services/FileUtils";
|
||||
|
||||
import PhotoSwipe, { PhotoSwipeOptions } from "photoswipe";
|
||||
import "photoswipe/style.css";
|
||||
|
||||
import DeleteIcon from "vue-material-design-icons/Delete.vue";
|
||||
import StarIcon from "vue-material-design-icons/Star.vue";
|
||||
import StarOutlineIcon from "vue-material-design-icons/StarOutline.vue";
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
NcActions,
|
||||
NcActionButton,
|
||||
DeleteIcon,
|
||||
StarIcon,
|
||||
StarOutlineIcon,
|
||||
},
|
||||
})
|
||||
export default class Viewer extends Mixins(GlobalMixin) {
|
||||
@Emit("deleted") deleted(photos: IPhoto[]) {}
|
||||
@Emit("fetchDay") fetchDay(dayId: number) {}
|
||||
@Emit("updateLoading") updateLoading(delta: number) {}
|
||||
|
||||
/** Base dialog */
|
||||
private photoswipe: PhotoSwipe | null = null;
|
||||
|
||||
private list: IPhoto[] = [];
|
||||
private days = new Map<number, IDay>();
|
||||
private dayIds: number[] = [];
|
||||
|
||||
private globalCount = 0;
|
||||
private globalAnchor = -1;
|
||||
|
||||
private getBaseBox(args: PhotoSwipeOptions) {
|
||||
this.photoswipe = new PhotoSwipe({
|
||||
counter: true,
|
||||
zoom: false,
|
||||
loop: false,
|
||||
bgOpacity: 1,
|
||||
appendToEl: this.$refs.inner as HTMLElement,
|
||||
...args,
|
||||
});
|
||||
|
||||
// Make sure buttons are styled properly
|
||||
this.photoswipe.addFilter("uiElement", (element, data) => {
|
||||
// add button-vue class if button
|
||||
if (element.classList.contains("pswp__button")) {
|
||||
element.classList.add("button-vue");
|
||||
}
|
||||
return element;
|
||||
});
|
||||
|
||||
// Total number of photos in this view
|
||||
this.photoswipe.addFilter("numItems", (numItems) => {
|
||||
return this.globalCount;
|
||||
});
|
||||
|
||||
// Put viewer over everything else
|
||||
const contentElem = document.getElementById("content-vue");
|
||||
const navElem = document.getElementById("app-navigation-vue");
|
||||
const klass = "has-viewer";
|
||||
this.photoswipe.on("beforeOpen", () => {
|
||||
contentElem.classList.add(klass);
|
||||
navElem.style.zIndex = "0";
|
||||
});
|
||||
this.photoswipe.on("destroy", () => {
|
||||
contentElem.classList.remove(klass);
|
||||
navElem.style.zIndex = "";
|
||||
|
||||
// reset everything
|
||||
this.photoswipe = null;
|
||||
this.list = [];
|
||||
this.days.clear();
|
||||
this.dayIds = [];
|
||||
this.globalCount = 0;
|
||||
this.globalAnchor = -1;
|
||||
});
|
||||
|
||||
return this.photoswipe;
|
||||
}
|
||||
|
||||
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);
|
||||
// }
|
||||
|
||||
this.list = [...anchorPhoto.d.detail];
|
||||
let startIndex = -1;
|
||||
|
||||
for (const r of rows) {
|
||||
if (r.type === IRowType.HEAD) {
|
||||
if (r.day.dayid == anchorPhoto.d.dayid) {
|
||||
startIndex = r.day.detail.findIndex(
|
||||
(p) => p.fileid === anchorPhoto.fileid
|
||||
);
|
||||
this.globalAnchor = this.globalCount;
|
||||
}
|
||||
|
||||
this.globalCount += r.day.count;
|
||||
this.days.set(r.day.dayid, r.day);
|
||||
this.dayIds.push(r.day.dayid);
|
||||
}
|
||||
}
|
||||
|
||||
this.getBaseBox({
|
||||
index: this.globalAnchor + startIndex,
|
||||
});
|
||||
|
||||
this.photoswipe.addFilter("itemData", (itemData, index) => {
|
||||
// Get photo object from list
|
||||
let idx = index - this.globalAnchor;
|
||||
if (idx < 0) {
|
||||
// Load previous day
|
||||
const firstDayId = this.list[0].d.dayid;
|
||||
const firstDayIdx = utils.binarySearch(this.dayIds, firstDayId);
|
||||
if (firstDayIdx === 0) {
|
||||
// No previous day
|
||||
return {};
|
||||
}
|
||||
const prevDayId = this.dayIds[firstDayIdx - 1];
|
||||
const prevDay = this.days.get(prevDayId);
|
||||
if (!prevDay.detail) {
|
||||
console.error("[BUG] No detail for previous day");
|
||||
return {};
|
||||
}
|
||||
this.list.unshift(...prevDay.detail);
|
||||
this.globalAnchor -= prevDay.count;
|
||||
} else if (idx >= this.list.length) {
|
||||
// Load next day
|
||||
const lastDayId = this.list[this.list.length - 1].d.dayid;
|
||||
const lastDayIdx = utils.binarySearch(this.dayIds, lastDayId);
|
||||
if (lastDayIdx === this.dayIds.length - 1) {
|
||||
// No next day
|
||||
return {};
|
||||
}
|
||||
const nextDayId = this.dayIds[lastDayIdx + 1];
|
||||
const nextDay = this.days.get(nextDayId);
|
||||
if (!nextDay.detail) {
|
||||
console.error("[BUG] No detail for next day");
|
||||
return {};
|
||||
}
|
||||
this.list.push(...nextDay.detail);
|
||||
}
|
||||
|
||||
idx = index - this.globalAnchor;
|
||||
const photo = this.list[idx];
|
||||
|
||||
// Something went really wrong
|
||||
if (!photo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Preload next and previous 3 days
|
||||
const dayIdx = utils.binarySearch(this.dayIds, photo.d.dayid);
|
||||
const preload = (idx: number) => {
|
||||
if (
|
||||
idx > 0 &&
|
||||
idx < this.dayIds.length &&
|
||||
!this.days.get(this.dayIds[idx]).detail
|
||||
) {
|
||||
this.fetchDay(this.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,
|
||||
};
|
||||
});
|
||||
|
||||
this.photoswipe.addFilter("thumbEl", (thumbEl, data, index) => {
|
||||
const photo = this.list[index - this.globalAnchor];
|
||||
return this.thumbElem(photo) || thumbEl;
|
||||
});
|
||||
|
||||
this.photoswipe.init();
|
||||
}
|
||||
|
||||
private thumbElem(photo: IPhoto) {
|
||||
if (!photo) return;
|
||||
return document.getElementById(
|
||||
`memories-photo-${photo.key || photo.fileid}`
|
||||
);
|
||||
}
|
||||
|
||||
private async deleteCurrent() {
|
||||
const idx = this.photoswipe.currIndex - this.globalAnchor;
|
||||
|
||||
// Delete with WebDAV
|
||||
try {
|
||||
this.updateLoading(1);
|
||||
for await (const p of dav.deletePhotos([this.list[idx]])) {
|
||||
if (!p[0]) return;
|
||||
}
|
||||
} finally {
|
||||
this.updateLoading(-1);
|
||||
}
|
||||
|
||||
const spliced = this.list.splice(idx, 1);
|
||||
this.globalCount--;
|
||||
for (let i = idx - 3; i <= idx + 3; i++) {
|
||||
this.photoswipe.refreshSlideContent(i + this.globalAnchor);
|
||||
}
|
||||
this.deleted(spliced);
|
||||
}
|
||||
|
||||
private isFavorite() {
|
||||
const idx = this.photoswipe.currIndex - this.globalAnchor;
|
||||
return Boolean(this.list[idx].flag & this.c.FLAG_IS_FAVORITE);
|
||||
}
|
||||
|
||||
private async favoriteCurrent() {
|
||||
const idx = this.photoswipe.currIndex - this.globalAnchor;
|
||||
const photo = this.list[idx];
|
||||
const val = !this.isFavorite();
|
||||
try {
|
||||
this.updateLoading(1);
|
||||
for await (const p of dav.favoritePhotos([photo], val)) {
|
||||
if (!p[0]) return;
|
||||
}
|
||||
} finally {
|
||||
this.updateLoading(-1);
|
||||
}
|
||||
|
||||
// Set flag on success
|
||||
if (val) {
|
||||
photo.flag |= this.c.FLAG_IS_FAVORITE;
|
||||
} else {
|
||||
photo.flag &= ~this.c.FLAG_IS_FAVORITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.outer {
|
||||
z-index: 3000;
|
||||
width: 100vw;
|
||||
height: 30vh;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
z-index: 100001;
|
||||
position: fixed;
|
||||
top: 8px;
|
||||
right: 50px;
|
||||
|
||||
:deep .button-vue--icon-only {
|
||||
color: white;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -47,7 +47,6 @@ import { NcActions, NcActionButton } from "@nextcloud/vue";
|
|||
|
||||
import * as utils from "../../services/Utils";
|
||||
import * as dav from "../../services/DavRequests";
|
||||
import { ViewerManager } from "../../services/Viewer";
|
||||
import { IPhoto } from "../../types";
|
||||
import { getPreviewUrl } from "../../services/FileUtils";
|
||||
|
||||
|
@ -83,14 +82,6 @@ export default class OnThisDay extends Mixins(GlobalMixin) {
|
|||
private hasLeft = false;
|
||||
private scrollStack: number[] = [];
|
||||
|
||||
/**
|
||||
* Nextcloud viewer proxy
|
||||
* Can't use the timeline instance because these photos
|
||||
* might not be in view, so can't delete them
|
||||
*/
|
||||
@Prop()
|
||||
private viewerManager!: ViewerManager;
|
||||
|
||||
mounted() {
|
||||
const inner = this.$refs.inner as HTMLElement;
|
||||
inner.addEventListener("scroll", this.onScroll.bind(this), {
|
||||
|
|
|
@ -1,259 +0,0 @@
|
|||
import Vue from "vue";
|
||||
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 { getPreviewUrl } from "./FileUtils";
|
||||
import * as dav from "./DavRequests";
|
||||
import * as utils from "./Utils";
|
||||
|
||||
import PhotoSwipe, { PhotoSwipeOptions } from "photoswipe";
|
||||
import "photoswipe/style.css";
|
||||
|
||||
import DeleteIcon from "vue-material-design-icons/Delete.vue";
|
||||
|
||||
// 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 {
|
||||
/** Delete click callback */
|
||||
private deleteClick!: () => void;
|
||||
|
||||
constructor(private opts: opts_t) {}
|
||||
|
||||
private getVueBtn(typ: any) {
|
||||
const btn = new (Vue.extend(typ))({
|
||||
propsData: {
|
||||
size: 24,
|
||||
},
|
||||
});
|
||||
btn.$mount();
|
||||
return btn.$el;
|
||||
}
|
||||
|
||||
private getBaseBox(args: PhotoSwipeOptions) {
|
||||
const photoswipe = new PhotoSwipe({
|
||||
counter: true,
|
||||
loop: false,
|
||||
...args,
|
||||
});
|
||||
|
||||
photoswipe.addFilter("uiElement", (element, data) => {
|
||||
// add button-vue class if button
|
||||
if (element.classList.contains("pswp__button")) {
|
||||
element.classList.add("button-vue");
|
||||
}
|
||||
return element;
|
||||
});
|
||||
|
||||
photoswipe.on("uiRegister", () => {
|
||||
photoswipe.ui.registerElement({
|
||||
name: "delete-button",
|
||||
ariaLabel: "Delete",
|
||||
order: 9,
|
||||
isButton: true,
|
||||
html: this.getVueBtn(DeleteIcon).outerHTML,
|
||||
onClick: () => this.deleteClick(),
|
||||
});
|
||||
});
|
||||
|
||||
return photoswipe;
|
||||
}
|
||||
|
||||
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);
|
||||
// }
|
||||
|
||||
const list = [...anchorPhoto.d.detail];
|
||||
const days = new Map<number, IDay>();
|
||||
const dayIds = [];
|
||||
|
||||
let globalCount = 0;
|
||||
let globalAnchor = -1;
|
||||
let startIndex = -1;
|
||||
|
||||
for (const r of rows) {
|
||||
if (r.type === IRowType.HEAD) {
|
||||
if (r.day.dayid == anchorPhoto.d.dayid) {
|
||||
startIndex = r.day.detail.findIndex(
|
||||
(p) => p.fileid === anchorPhoto.fileid
|
||||
);
|
||||
globalAnchor = globalCount;
|
||||
}
|
||||
|
||||
globalCount += r.day.count;
|
||||
days.set(r.day.dayid, r.day);
|
||||
dayIds.push(r.day.dayid);
|
||||
}
|
||||
}
|
||||
|
||||
const photoswipe = this.getBaseBox({
|
||||
index: globalAnchor + startIndex,
|
||||
});
|
||||
|
||||
photoswipe.addFilter("numItems", (numItems) => {
|
||||
return globalCount;
|
||||
});
|
||||
|
||||
photoswipe.addFilter("itemData", (itemData, index) => {
|
||||
// Get photo object from list
|
||||
let idx = index - globalAnchor;
|
||||
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);
|
||||
globalAnchor -= 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;
|
||||
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]);
|
||||
}
|
||||
};
|
||||
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,
|
||||
};
|
||||
});
|
||||
|
||||
photoswipe.addFilter("thumbEl", (thumbEl, data, index) => {
|
||||
const photo = list[index - globalAnchor];
|
||||
return this.thumbElem(photo) || thumbEl;
|
||||
});
|
||||
|
||||
this.deleteClick = () => {
|
||||
const idx = photoswipe.currIndex - globalAnchor;
|
||||
const spliced = list.splice(idx, 1);
|
||||
globalCount--;
|
||||
for (let i = idx - 3; i <= idx + 3; i++) {
|
||||
photoswipe.refreshSlideContent(i + globalAnchor);
|
||||
}
|
||||
this.opts.ondelete(spliced);
|
||||
};
|
||||
|
||||
photoswipe.init();
|
||||
}
|
||||
|
||||
private thumbElem(photo: IPhoto) {
|
||||
if (!photo) return;
|
||||
return document.getElementById(
|
||||
`memories-photo-${photo.key || photo.fileid}`
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue