This commit is contained in:
Manav Rathi
2025-03-07 11:02:42 +05:30
parent df96f42a61
commit ef013473fc
7 changed files with 30 additions and 328 deletions

View File

@@ -3,24 +3,17 @@ import { isSameDay } from "@/base/date";
import { formattedDate } from "@/base/i18n-date";
import log from "@/base/log";
import type { FileInfoProps } from "@/gallery/components/FileInfo";
import {
downloadManager,
type LivePhotoSourceURL,
type LoadedLivePhotoSourceURL,
type RenderableSourceURLs,
} from "@/gallery/services/download";
import { FileViewer } from "@/gallery/components/viewer/FileViewer";
import { type RenderableSourceURLs } from "@/gallery/services/download";
import type { Collection } from "@/media/collection";
import { EnteFile } from "@/media/file";
import { FileType } from "@/media/file-type";
import { FileViewer } from "@/new/photos/components/FileViewerComponents";
import type { GalleryBarMode } from "@/new/photos/components/gallery/reducer";
import { moveToTrash, TRASH_SECTION } from "@/new/photos/services/collection";
import { styled } from "@mui/material";
import { PhotoViewer } from "components/PhotoViewer";
import { t } from "i18next";
import { useRouter } from "next/router";
import { GalleryContext } from "pages/gallery";
import PhotoSwipe from "photoswipe";
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
import AutoSizer from "react-virtualized-auto-sizer";
import {
@@ -182,10 +175,7 @@ const PhotoFrame = ({
}: PhotoFrameProps) => {
const [open, setOpen] = useState(false);
const [currentIndex, setCurrentIndex] = useState<number>(0);
const [fetching, setFetching] = useState<Record<number, boolean>>({});
const [thumbFetching, setThumbFetching] = useState<Record<number, boolean>>(
{},
);
const galleryContext = useContext(GalleryContext);
const [rangeStart, setRangeStart] = useState(null);
const [currentHover, setCurrentHover] = useState(null);
@@ -200,6 +190,7 @@ const PhotoFrame = ({
);
useEffect(() => {
// TODO(PS): Audit
const result = files.map((file) => ({
...file,
w: window.innerWidth,
@@ -208,8 +199,6 @@ const PhotoFrame = ({
timelineDateString: fileTimelineDateString(file),
}));
setDisplayFiles(result);
setFetching({});
setThumbFetching({});
}, [files]);
useEffect(() => {
@@ -334,28 +323,14 @@ const PhotoFrame = ({
if (file.msrc && !forceUpdate) {
return false;
}
// TODO(PS): Audit
updateDisplayFileThumbnail(file, url);
return true;
};
const handleClose = (needUpdate) => {
if (process.env.NEXT_PUBLIC_ENTE_WIP_PS5) {
throw new Error("Not implemented");
} else {
setOpen(false);
needUpdate && onSyncWithRemote();
setIsPhotoSwipeOpen?.(false);
}
};
const onThumbnailClick = (index: number) => () => {
setCurrentIndex(index);
if (process.env.NEXT_PUBLIC_ENTE_WIP_PS5) {
showFileViewer();
} else {
setOpen(true);
setIsPhotoSwipeOpen?.(true);
}
showFileViewer();
};
const handleSelect = handleSelectCreator(
@@ -429,135 +404,7 @@ const PhotoFrame = ({
/>
);
const getSlideData = async (
instance: PhotoSwipe<PhotoSwipe.Options>,
index: number,
item: DisplayFile,
) => {
log.info(
`[${item.id}] getSlideData called for thumbnail: ${!!item.msrc} sourceLoaded: ${!!item.isSourceLoaded} fetching: ${!!fetching[item.id]}`,
);
if (!item.msrc) {
try {
if (thumbFetching[item.id]) {
log.info(`[${item.id}] thumb download already in progress`);
return;
}
log.info(`[${item.id}] doesn't have thumbnail`);
thumbFetching[item.id] = true;
// URL will always be defined (unless an error is thrown) since
// we are not passing the `cachedOnly` option.
const url = await downloadManager.renderableThumbnailURL(item)!;
updateThumbnail(instance, index, item, url, false);
} catch (e) {
log.error("getSlideData failed get msrc url failed", e);
thumbFetching[item.id] = false;
}
}
if (item.isSourceLoaded || item.conversionFailed) {
if (item.isSourceLoaded) {
log.info(`[${item.id}] source already loaded`);
}
if (item.conversionFailed) {
log.info(`[${item.id}] conversion failed`);
}
return;
}
if (fetching[item.id]) {
log.info(`[${item.id}] file download already in progress`);
return;
}
try {
log.info(`[${item.id}] new file src request`);
fetching[item.id] = true;
const srcURLs = await downloadManager.renderableSourceURLs(item);
if (item.metadata.fileType === FileType.livePhoto) {
const srcImgURL = srcURLs.url as LivePhotoSourceURL;
const imageURL = await srcImgURL.image();
const dummyImgSrcUrl: RenderableSourceURLs = {
url: imageURL,
type: "normal",
};
updateSource(instance, index, item, dummyImgSrcUrl, false);
if (!imageURL) {
// no image url, no need to load video
return;
}
const videoURL = await srcImgURL.video();
const loadedLivePhotoSrcURL: RenderableSourceURLs = {
url: { video: videoURL, image: imageURL },
type: "livePhoto",
};
updateSource(
instance,
index,
item,
loadedLivePhotoSrcURL,
true,
);
} else {
updateSource(instance, index, item, srcURLs, false);
}
} catch (e) {
log.error("getSlideData failed get src url failed", e);
fetching[item.id] = false;
// no-op
}
};
const updateThumbnail = (
instance: PhotoSwipe<PhotoSwipe.Options>,
index: number,
item: DisplayFile,
url: string,
forceUpdate?: boolean,
) => {
try {
if (updateThumbURL(index)(item.id, url, forceUpdate)) {
log.info(
`[${item.id}] calling invalidateCurrItems for thumbnail msrc: ${!!item.msrc}`,
);
instance.invalidateCurrItems();
if ((instance as any).isOpen()) {
instance.updateSize(true);
}
}
} catch (e) {
log.error("updating photoswipe after msrc url update failed", e);
// ignore
}
};
const updateSource = (
instance: PhotoSwipe<PhotoSwipe.Options>,
index: number,
item: DisplayFile,
srcURL: RenderableSourceURLs,
overwrite: boolean,
) => {
const file = displayFiles[index];
// This is to prevent outdated call from updating the wrong file.
if (file.id !== item.id) {
log.info(
`Ignoring stale updateSourceURL for display file at index ${index} (file ID ${file.id}, expected ${item.id})`,
);
throw new Error("Update URL file id mismatch");
}
if (file.isSourceLoaded && !overwrite) return;
if (file.conversionFailed) throw new Error("File conversion failed");
updateDisplayFileSource(file, srcURL, enableDownload);
instance.invalidateCurrItems();
if ((instance as any).isOpen()) {
instance.updateSize(true);
}
};
/* TODO(PS):
const forceConvertItem = async (
instance: PhotoSwipe<PhotoSwipe.Options>,
index: number,
@@ -582,35 +429,10 @@ const PhotoFrame = ({
// no-op
}
};
*/
return (
<Container>
{process.env.NEXT_PUBLIC_ENTE_WIP_PS5 && (
<FileViewer
{...fileViewerVisibilityProps}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore TODO(PS): test */
user={galleryContext.user ?? undefined}
files={files}
initialIndex={currentIndex}
disableDownload={!enableDownload}
isInIncomingSharedCollection={isInIncomingSharedCollection}
isInTrashSection={activeCollectionID === TRASH_SECTION}
isInHiddenSection={isInHiddenSection}
onTriggerSyncWithRemote={handleTriggerSyncWithRemote}
onToggleFavorite={handleToggleFavorite}
onDownload={handleDownload}
onDelete={handleDelete}
onSaveEditedImageCopy={handleSaveEditedImageCopy}
{...{
favoriteFileIDs,
fileCollectionIDs,
allCollectionsNameByID,
onSelectCollection,
onSelectPerson,
}}
/>
)}
<AutoSizer>
{({ height, width }) => (
<PhotoList
@@ -626,21 +448,24 @@ const PhotoFrame = ({
/>
)}
</AutoSizer>
<PhotoViewer
isOpen={open}
items={displayFiles}
currentIndex={currentIndex}
onClose={handleClose}
gettingData={getSlideData}
forceConvertItem={forceConvertItem}
isTrashCollection={activeCollectionID === TRASH_SECTION}
<FileViewer
{...fileViewerVisibilityProps}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
/* @ts-ignore TODO(PS): test */
user={galleryContext.user ?? undefined}
files={files}
initialIndex={currentIndex}
disableDownload={!enableDownload}
isInIncomingSharedCollection={isInIncomingSharedCollection}
isInTrashSection={activeCollectionID === TRASH_SECTION}
isInHiddenSection={isInHiddenSection}
enableDownload={enableDownload}
onTriggerSyncWithRemote={handleTriggerSyncWithRemote}
onToggleFavorite={handleToggleFavorite}
onDownload={handleDownload}
onDelete={handleDelete}
onSaveEditedImageCopy={handleSaveEditedImageCopy}
{...{
favoriteFileIDs,
onMarkUnsyncedFavoriteUpdate,
onMarkTempDeleted,
setFilesDownloadProgressAttributesCreator,
fileCollectionIDs,
allCollectionsNameByID,
onSelectCollection,
@@ -671,74 +496,6 @@ const updateDisplayFileThumbnail = (file: DisplayFile, url: string) => {
}
};
const updateDisplayFileSource = (
file: DisplayFile,
srcURLs: RenderableSourceURLs,
enableDownload: boolean,
) => {
const { url } = srcURLs;
const isRenderable = !!url;
file.w = window.innerWidth;
file.h = window.innerHeight;
file.isSourceLoaded =
file.metadata.fileType === FileType.livePhoto
? srcURLs.type === "livePhoto"
: true;
file.canForceConvert = srcURLs.canForceConvert;
file.conversionFailed = !isRenderable;
file.associatedImageURL = (() => {
switch (file.metadata.fileType) {
case FileType.image:
return srcURLs.url as string;
case FileType.livePhoto:
return (srcURLs.url as LoadedLivePhotoSourceURL).image;
default:
return undefined;
}
})();
if (!isRenderable) {
file.isSourceLoaded = true;
return;
}
if (file.metadata.fileType === FileType.video) {
file.html = `
<video controls ${
!enableDownload && 'controlsList="nodownload"'
} onContextMenu="return false;">
<source src="${url}" />
Your browser does not support the video tag.
</video>
`;
} else if (file.metadata.fileType === FileType.livePhoto) {
if (srcURLs.type === "normal") {
file.html = `
<div class = 'pswp-item-container'>
<img id = "live-photo-image-${file.id}" src="${url}" onContextMenu="return false;"/>
</div>
`;
} else {
const { image: imageURL, video: videoURL } =
url as LoadedLivePhotoSourceURL;
file.html = `
<div class = 'pswp-item-container'>
<img id = "live-photo-image-${file.id}" src="${imageURL}" onContextMenu="return false;"/>
<video id = "live-photo-video-${file.id}" loop muted onContextMenu="return false;">
<source src="${videoURL}" />
Your browser does not support the video tag.
</video>
</div>
`;
}
} else if (file.metadata.fileType === FileType.image) {
file.src = url as string;
} else {
log.error(`unknown file type - ${file.metadata.fileType}`);
file.src = url as string;
}
};
/**
* See: [Note: Timeline date string]
*/

View File

@@ -12,13 +12,13 @@ import { useModalVisibility } from "@/base/components/utils/modal";
import { useBaseContext } from "@/base/context";
import log from "@/base/log";
import { FullScreenDropZone } from "@/gallery/components/FullScreenDropZone";
import { resetFileViewerDataSourceOnClose } from "@/gallery/components/viewer/data-source";
import { type Collection } from "@/media/collection";
import { mergeMetadata, type EnteFile } from "@/media/file";
import {
CollectionSelector,
type CollectionSelectorAttributes,
} from "@/new/photos/components/CollectionSelector";
import { resetFileViewerDataSourceOnClose } from "@/new/photos/components/FileViewerComponents-temp";
import { PlanSelector } from "@/new/photos/components/PlanSelector";
import {
SearchBar,
@@ -570,7 +570,6 @@ const Page: React.FC = () => {
);
if (didUpdateNormalFiles || didUpdateHiddenFiles) {
exportService.onLocalFilesUpdated();
// TODO(PS): Use direct one
resetFileViewerDataSourceOnClose();
}
// syncWithRemote is called with the force flag set to true before

View File

@@ -1,18 +1,6 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
// TODO(PS): WIP gallery using upstream photoswipe
//
// Needs (not committed yet):
// yarn workspace gallery add photoswipe@^5.4.4
// mv node_modules/photoswipe packages/new/photos/components/ps5
if (process.env.NEXT_PUBLIC_ENTE_WIP_PS5) {
console.warn("Using WIP upstream photoswipe");
} else {
throw new Error("Whoa");
}
import { isDesktop } from "@/base/app";
import { SpacedRow } from "@/base/components/containers";
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
@@ -248,7 +236,7 @@ export type FileViewerProps = ModalVisibilityProps & {
/**
* A PhotoSwipe based image and video viewer.
*/
const FileViewer: React.FC<FileViewerProps> = ({
export const FileViewer: React.FC<FileViewerProps> = ({
open,
onClose,
user,
@@ -870,8 +858,6 @@ const FileViewer: React.FC<FileViewerProps> = ({
);
};
export default FileViewer;
const Container = styled("div")`
border: 1px solid red;

View File

@@ -6,6 +6,7 @@ import log from "@/base/log";
import type { EnteFile } from "@/media/file";
import { FileType } from "@/media/file-type";
import { t } from "i18next";
import PhotoSwipe from "photoswipe";
import {
fileViewerDidClose,
fileViewerWillOpen,
@@ -18,24 +19,6 @@ import {
import { type FileViewerAnnotatedFile } from "./FileViewer";
import { createPSRegisterElementIconHTML } from "./icons";
// TODO(PS): WIP gallery using upstream photoswipe
//
// Needs (not committed yet):
// yarn workspace gallery add photoswipe@^5.4.4
// mv node_modules/photoswipe packages/new/photos/components/ps5
if (process.env.NEXT_PUBLIC_ENTE_WIP_PS5) {
console.warn("Using WIP upstream photoswipe");
} else {
throw new Error("Whoa");
}
let PhotoSwipe;
if (process.env.NEXT_PUBLIC_ENTE_WIP_PS5) {
// TODO(PS): Comment me before merging into main.
// PhotoSwipe = require("./ps5/dist/photoswipe.esm.js").default;
}
export interface FileViewerPhotoSwipeDelegate {
/**
* Called to obtain the latest list of files.

View File

@@ -1,7 +0,0 @@
// TODO(PS): Temporary trampoline
export const resetFileViewerDataSourceOnClose = async () => {
if (!process.env.NEXT_PUBLIC_ENTE_WIP_PS5) return;
(
await import("@/gallery/components/viewer/data-source")
).resetFileViewerDataSourceOnClose();
};

View File

@@ -9,22 +9,6 @@ import { t } from "i18next";
import { useState } from "react";
import { aboveFileViewerContentZ } from "./utils/z-index";
// TODO(PS)
import dynamic from "next/dynamic";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const FV5 = dynamic(() => import("@/gallery/components/viewer/FileViewer"), {
ssr: false,
});
const FVD = () => <></>;
export const FileViewer: React.FC = (props) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return process.env.NEXT_PUBLIC_ENTE_WIP_PS5 ? <FV5 {...props} /> : <FVD />;
};
type ConfirmDeleteFileDialogProps = ModalVisibilityProps & {
/**
* Called when the user confirms the deletion.

View File

@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { resetFileViewerDataSourceOnClose } from "@/gallery/components/viewer/data-source";
import { isHiddenCollection } from "@/new/photos/services/collection";
import {
getAllLatestCollections,
@@ -9,7 +10,6 @@ import { isMLSupported, mlStatusSync, mlSync } from "@/new/photos/services/ml";
import { searchDataSync } from "@/new/photos/services/search";
import { syncSettings } from "@/new/photos/services/settings";
import { splitByPredicate } from "@/utils/array";
import { resetFileViewerDataSourceOnClose } from "../components/FileViewerComponents-temp";
/**
* Part 1 of {@link sync}. See TODO below for why this is split.
@@ -77,9 +77,9 @@ export const syncFilesAndCollections = async () => {
);
await syncTrash(allCollections, () => {});
if (didUpdateNormalFiles || didUpdateHiddenFiles) {
// TODO:
// TODO: Ok for now since we're only called by deduper, but still needs
// fixing instead of a hidden gotcha.
// exportService.onLocalFilesUpdated();
// TODO(PS): Use direct one
await resetFileViewerDataSourceOnClose();
resetFileViewerDataSourceOnClose();
}
};