From e79050a3b686a3fd134fd4e3c74f6d8c4f65d255 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 21 Feb 2025 10:10:43 +0530 Subject: [PATCH] ann2 --- .../gallery/components/viewer/FileViewer.tsx | 34 ++++++++--- .../gallery/components/viewer/photoswipe.ts | 61 ++++++++++++++++--- 2 files changed, 76 insertions(+), 19 deletions(-) diff --git a/web/packages/gallery/components/viewer/FileViewer.tsx b/web/packages/gallery/components/viewer/FileViewer.tsx index 6efcaf9a83..07756ec357 100644 --- a/web/packages/gallery/components/viewer/FileViewer.tsx +++ b/web/packages/gallery/components/viewer/FileViewer.tsx @@ -23,7 +23,10 @@ import type { EnteFile } from "@/media/file.js"; import { Button, styled } from "@mui/material"; import { useCallback, useEffect, useRef, useState } from "react"; import { fileInfoExifForFile, type FileInfoExif } from "./data-source"; -import { FileViewerPhotoSwipe } from "./photoswipe"; +import { + FileViewerPhotoSwipe, + type FileViewerAnnotatedFile, +} from "./photoswipe"; export type FileViewerProps = ModalVisibilityProps & { /** @@ -76,6 +79,7 @@ export type FileViewerProps = ModalVisibilityProps & { const FileViewer: React.FC = ({ open, onClose, + user, files, initialIndex, disableDownload, @@ -93,11 +97,11 @@ const FileViewer: React.FC = ({ // This is not guaranteed, or even intended, to be in sync with the active // file shown within the file viewer. All that this guarantees is this will // refer to the file on which the last user initiated action was performed. - const [activeFile, setActiveFile] = useState( - undefined, - ); + const [activeAnnotatedFile, setActiveAnnotatedFile] = useState< + FileViewerAnnotatedFile | undefined + >(undefined); // With semantics similar to activeFile, this is the exif data associated - // with the activeFile, if any. + // with the activeAnnotatedFile, if any. const [activeFileExif, setActiveFileExif] = useState< FileInfoExif | undefined >(undefined); @@ -105,11 +109,22 @@ const FileViewer: React.FC = ({ const { show: showFileInfo, props: fileInfoVisibilityProps } = useModalVisibility(); - const handleViewInfo = useCallback( + const handleAnnotate = useCallback( (file: EnteFile) => { - setActiveFile(file); + const fileID = file.id; + const isOwnFile = file.ownerID == user?.id; + return { fileID, isOwnFile }; + }, + [user], + ); + + const handleViewInfo = useCallback( + (annotatedFile: FileViewerAnnotatedFile) => { + setActiveAnnotatedFile(annotatedFile); setActiveFileExif( - fileInfoExifForFile(file, (exif) => setActiveFileExif(exif)), + fileInfoExifForFile(annotatedFile.file, (exif) => + setActiveFileExif(exif), + ), ); showFileInfo(); }, @@ -139,6 +154,7 @@ const FileViewer: React.FC = ({ initialIndex, disableDownload, onClose, + onAnnotate: handleAnnotate, onViewInfo: handleViewInfo, }); pswpRef.current = pswp; @@ -169,7 +185,7 @@ const FileViewer: React.FC = ({ void; + onViewInfo: (annotatedFile: FileViewerAnnotatedFile) => void; } & Pick; /** * Derived data for a file that is needed to display the file viewer controls * etc associated with the file. * - * This is recomputed each time the slide changes. + * This is recomputed on-demand each time the slide changes. */ -interface FileViewerFileAnnotation { +export interface FileViewerFileAnnotation { + /** + * The id of the file whose annotation this is. + */ + fileID: number; /** * `true` if this file is owned by the logged in user (if any). */ isOwnFile: boolean; } +/** + * A file and its annotation, in a nice cosy box. + */ +export interface FileViewerAnnotatedFile { + file: EnteFile; + annotation: FileViewerFileAnnotation; +} + /** * A wrapper over {@link PhotoSwipe} to tailor its interface for use by our file * viewer. @@ -115,9 +127,14 @@ export class FileViewerPhotoSwipe { /** * Derived data about the currently displayed file. * - * This is recomputed each time the slide changes. + * This is recomputed on-demand (by using the {@link onAnnotate} callback) + * each time the slide changes, and cached until the next slide change. + * + * Instead of accessing this property directly, code should funnel through + * the `activeFileAnnotation` helper function defined in the constructor + * scope. */ - // private activeFileAnnotation; + private activeFileAnnotation: FileViewerFileAnnotation | undefined; constructor({ files, @@ -129,6 +146,7 @@ export class FileViewerPhotoSwipe { }: FileViewerPhotoSwipeOptions) { this.files = files; this.opts = { disableDownload }; + this.lastActivityDate = new Date(); const pswp = new PhotoSwipe({ // Opaque background. @@ -185,10 +203,35 @@ export class FileViewerPhotoSwipe { errorMsg: "This file could not be previewed", }); + this.pswp = pswp; + // Helper routines to obtain the file at `currIndex`. + const currentFile = () => this.files[pswp.currIndex]!; - const withCurrentFile = (cb: (file: EnteFile) => void) => () => - cb(currentFile()); + + const currentAnnotatedFile = () => { + const file = currentFile(); + let annotation = this.activeFileAnnotation; + if (annotation?.fileID != file.id) { + annotation = onAnnotate(file); + this.activeFileAnnotation = annotation; + } + return { + file, + // The above condition implies that annotation can never be + // undefined, but it doesn't seem to be enough to convince + // TypeScript. Writing the condition in a more unnatural way + // `(annotation && annotation?.fileID == file.id)` works, but + // instead we use a non-null assertion here. + annotation: annotation!, + }; + }; + + const currentFileAnnotation = () => currentAnnotatedFile().annotation; + + const withCurrentAnnotatedFile = + (cb: (af: AnnotatedFile) => void) => () => + cb(currentFileAnnotation()); // Provide data about slides to PhotoSwipe via callbacks // https://photoswipe.com/data-sources/#dynamically-generated-data @@ -363,7 +406,7 @@ export class FileViewerPhotoSwipe { order: 15, isButton: true, html: createPSRegisterElementIconHTML("info"), - onClick: withCurrentFile(onViewInfo), + onClick: withCurrentAnnotatedFile(onViewInfo), }); }); @@ -380,8 +423,6 @@ export class FileViewerPhotoSwipe { // the class "pswp". pswp.init(); - this.pswp = pswp; - this.autoHideCheckIntervalId = setInterval(() => { this.autoHideIfInactive(); }, 1000);