This commit is contained in:
Manav Rathi
2025-02-21 10:10:43 +05:30
parent 5e4707b695
commit e79050a3b6
2 changed files with 76 additions and 19 deletions

View File

@@ -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<FileViewerProps> = ({
open,
onClose,
user,
files,
initialIndex,
disableDownload,
@@ -93,11 +97,11 @@ const FileViewer: React.FC<FileViewerProps> = ({
// 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<EnteFile | undefined>(
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<FileViewerProps> = ({
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<FileViewerProps> = ({
initialIndex,
disableDownload,
onClose,
onAnnotate: handleAnnotate,
onViewInfo: handleViewInfo,
});
pswpRef.current = pswp;
@@ -169,7 +185,7 @@ const FileViewer: React.FC<FileViewerProps> = ({
<Button>Test</Button>
<FileInfo
{...fileInfoVisibilityProps}
file={activeFile}
file={activeAnnotatedFile.file}
exif={activeFileExif}
onSelectCollection={handleSelectCollection}
onSelectPerson={handleSelectPerson}

View File

@@ -47,22 +47,34 @@ type FileViewerPhotoSwipeOptions = {
/**
* Called when the user activates the info action on a file.
*/
onViewInfo: (file: EnteFile) => void;
onViewInfo: (annotatedFile: FileViewerAnnotatedFile) => void;
} & Pick<FileViewerProps, "files" | "initialIndex" | "disableDownload">;
/**
* 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);