refactor
This commit is contained in:
@@ -26,7 +26,6 @@ import {
|
||||
} from "ente-new/photos/components/PlaceholderThumbnails";
|
||||
import { TileBottomTextOverlay } from "ente-new/photos/components/Tiles";
|
||||
import { PseudoCollectionID } from "ente-new/photos/services/collection-summary";
|
||||
import { type EnteTrashFile } from "ente-new/photos/services/trash";
|
||||
import { t } from "i18next";
|
||||
import memoize from "memoize-one";
|
||||
import { GalleryContext } from "pages/gallery";
|
||||
@@ -93,6 +92,19 @@ export interface FileListAnnotatedFile {
|
||||
timelineDateString: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A file augmented with the date when it will be permanently deleted.
|
||||
*
|
||||
* See: [Note: Files in trash pseudo collection have deleteBy]
|
||||
*/
|
||||
export type EnteTrashFile = EnteFile & {
|
||||
/**
|
||||
* Timestamp (epoch microseconds) when the trash item (and its corresponding
|
||||
* {@link EnteFile}) will be permanently deleted.
|
||||
*/
|
||||
deleteBy?: number;
|
||||
};
|
||||
|
||||
export interface FileListProps {
|
||||
/** The height we should occupy (needed since the list is virtualized). */
|
||||
height: number;
|
||||
@@ -1228,6 +1240,11 @@ const FileThumbnail: React.FC<FileThumbnailProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
// See: [Note: Files in trash pseudo collection have deleteBy]
|
||||
const deleteBy =
|
||||
activeCollectionID == PseudoCollectionID.trash &&
|
||||
(file as EnteTrashFile).deleteBy;
|
||||
|
||||
return (
|
||||
<FileThumbnail_
|
||||
key={`thumb-${file.id}}`}
|
||||
@@ -1281,17 +1298,13 @@ const FileThumbnail: React.FC<FileThumbnailProps> = ({
|
||||
<InSelectRangeOverlay />
|
||||
)}
|
||||
|
||||
{activeCollectionID == PseudoCollectionID.trash &&
|
||||
// TODO(RE):
|
||||
(file as EnteTrashFile).deleteBy && (
|
||||
<TileBottomTextOverlay>
|
||||
<Typography variant="small">
|
||||
{formattedDateRelative(
|
||||
(file as EnteTrashFile).deleteBy,
|
||||
)}
|
||||
</Typography>
|
||||
</TileBottomTextOverlay>
|
||||
)}
|
||||
{deleteBy && (
|
||||
<TileBottomTextOverlay>
|
||||
<Typography variant="small">
|
||||
{formattedDateRelative(deleteBy)}
|
||||
</Typography>
|
||||
</TileBottomTextOverlay>
|
||||
)}
|
||||
</FileThumbnail_>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -79,6 +79,7 @@ import {
|
||||
savedCollections,
|
||||
savedHiddenFiles,
|
||||
savedNormalFiles,
|
||||
savedTrashItems,
|
||||
} from "ente-new/photos/services/photos-fdb";
|
||||
import {
|
||||
filterSearchableFiles,
|
||||
@@ -91,7 +92,6 @@ import {
|
||||
preCollectionAndFilesSync,
|
||||
syncCollectionAndFiles,
|
||||
} from "ente-new/photos/services/sync";
|
||||
import { getLocalTrashedFiles } from "ente-new/photos/services/trash";
|
||||
import {
|
||||
initUserDetailsOrTriggerSync,
|
||||
redirectToCustomerPortal,
|
||||
@@ -332,7 +332,7 @@ const Page: React.FC = () => {
|
||||
collections: await savedCollections(),
|
||||
normalFiles: await savedNormalFiles(),
|
||||
hiddenFiles: await savedHiddenFiles(),
|
||||
trashedFiles: await getLocalTrashedFiles(),
|
||||
trashItems: await savedTrashItems(),
|
||||
});
|
||||
await syncWithRemote({ force: true });
|
||||
setIsFirstLoad(false);
|
||||
@@ -557,8 +557,8 @@ const Page: React.FC = () => {
|
||||
dispatch({ type: "setHiddenFiles", files }),
|
||||
onFetchHiddenFiles: (files) =>
|
||||
dispatch({ type: "fetchHiddenFiles", files }),
|
||||
onResetTrashedFiles: (files) =>
|
||||
dispatch({ type: "setTrashedFiles", files }),
|
||||
onSetTrashedItems: (trashItems) =>
|
||||
dispatch({ type: "setTrashItems", trashItems }),
|
||||
});
|
||||
if (didUpdateFiles) {
|
||||
exportService.onLocalFilesUpdated();
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
isHiddenCollection,
|
||||
} from "ente-new/photos/services/collection";
|
||||
import { getLatestVersionFiles } from "ente-new/photos/services/files";
|
||||
import { type EnteTrashFile } from "ente-new/photos/services/trash";
|
||||
import { sortTrashItems, type TrashItem } from "ente-new/photos/services/trash";
|
||||
import { splitByPredicate } from "ente-utils/array";
|
||||
import { includes } from "ente-utils/type-guards";
|
||||
import { t } from "i18next";
|
||||
@@ -146,11 +146,12 @@ export interface GalleryState {
|
||||
*/
|
||||
lastSyncedHiddenFiles: EnteFile[];
|
||||
/**
|
||||
* The user's files that are in trash.
|
||||
* The items in the user's trash.
|
||||
*
|
||||
* The list is sorted so that newer files are first.
|
||||
* The items are sorted in ascending order of their time to deletion. For
|
||||
* more details about the sorting order, see {@link sortTrashItems}.
|
||||
*/
|
||||
trashedFiles: EnteFile[];
|
||||
trashItems: TrashItem[];
|
||||
/**
|
||||
* Latest snapshot of people related state, as reported by
|
||||
* {@link usePeopleStateSnapshot}.
|
||||
@@ -424,7 +425,7 @@ export type GalleryAction =
|
||||
collections: Collection[];
|
||||
normalFiles: EnteFile[];
|
||||
hiddenFiles: EnteFile[];
|
||||
trashedFiles: EnteTrashFile[];
|
||||
trashItems: TrashItem[];
|
||||
}
|
||||
| {
|
||||
type: "setCollections";
|
||||
@@ -438,7 +439,7 @@ export type GalleryAction =
|
||||
| { type: "uploadNormalFile"; file: EnteFile }
|
||||
| { type: "setHiddenFiles"; files: EnteFile[] }
|
||||
| { type: "fetchHiddenFiles"; files: EnteFile[] }
|
||||
| { type: "setTrashedFiles"; files: EnteFile[] }
|
||||
| { type: "setTrashItems"; trashItems: TrashItem[] }
|
||||
| { type: "setPeopleState"; peopleState: PeopleState | undefined }
|
||||
| { type: "markTempDeleted"; files: EnteFile[] }
|
||||
| { type: "clearTempDeleted" }
|
||||
@@ -473,7 +474,7 @@ const initialGalleryState: GalleryState = {
|
||||
hiddenCollections: [],
|
||||
lastSyncedNormalFiles: [],
|
||||
lastSyncedHiddenFiles: [],
|
||||
trashedFiles: [],
|
||||
trashItems: [],
|
||||
peopleState: undefined,
|
||||
normalFiles: [],
|
||||
hiddenFiles: [],
|
||||
@@ -515,6 +516,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
case "mount": {
|
||||
const lastSyncedNormalFiles = sortFiles(action.normalFiles);
|
||||
const lastSyncedHiddenFiles = sortFiles(action.hiddenFiles);
|
||||
const trashItems = sortTrashItems(action.trashItems);
|
||||
|
||||
// During mount there are no unsynced updates, and we can directly
|
||||
// use the provided files.
|
||||
@@ -546,7 +548,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
hiddenCollections,
|
||||
lastSyncedNormalFiles,
|
||||
lastSyncedHiddenFiles,
|
||||
trashedFiles: action.trashedFiles,
|
||||
trashItems,
|
||||
normalFiles,
|
||||
hiddenFiles,
|
||||
archivedCollectionIDs,
|
||||
@@ -568,7 +570,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
action.user,
|
||||
normalCollections,
|
||||
normalFiles,
|
||||
action.trashedFiles,
|
||||
trashItems,
|
||||
archivedFileIDs,
|
||||
),
|
||||
hiddenCollectionSummaries: deriveHiddenCollectionSummaries(
|
||||
@@ -595,7 +597,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
state.user!,
|
||||
normalCollections,
|
||||
state.normalFiles,
|
||||
state.trashedFiles,
|
||||
state.trashItems,
|
||||
archivedFileIDs,
|
||||
);
|
||||
const hiddenCollectionSummaries = deriveHiddenCollectionSummaries(
|
||||
@@ -665,7 +667,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
state.user!,
|
||||
normalCollections,
|
||||
state.normalFiles,
|
||||
state.trashedFiles,
|
||||
state.trashItems,
|
||||
archivedFileIDs,
|
||||
);
|
||||
|
||||
@@ -812,18 +814,21 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
});
|
||||
}
|
||||
|
||||
case "setTrashedFiles":
|
||||
case "setTrashItems": {
|
||||
const trashItems = sortTrashItems(action.trashItems);
|
||||
|
||||
return stateByUpdatingFilteredFiles({
|
||||
...state,
|
||||
trashedFiles: action.files,
|
||||
trashItems,
|
||||
normalCollectionSummaries: deriveNormalCollectionSummaries(
|
||||
state.user!,
|
||||
state.normalCollections,
|
||||
state.normalFiles,
|
||||
action.files,
|
||||
trashItems,
|
||||
state.archivedFileIDs,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
case "setPeopleState": {
|
||||
const peopleState = action.peopleState;
|
||||
@@ -1270,7 +1275,7 @@ const deriveNormalCollectionSummaries = (
|
||||
user: User,
|
||||
normalCollections: Collection[],
|
||||
normalFiles: EnteFile[],
|
||||
trashedFiles: EnteFile[],
|
||||
trashItems: TrashItem[],
|
||||
archivedFileIDs: Set<number>,
|
||||
) => {
|
||||
const normalCollectionSummaries = createCollectionSummaries(
|
||||
@@ -1305,7 +1310,10 @@ const deriveNormalCollectionSummaries = (
|
||||
name: t("section_all"),
|
||||
});
|
||||
normalCollectionSummaries.set(PseudoCollectionID.trash, {
|
||||
...pseudoCollectionOptionsForFiles(trashedFiles),
|
||||
...pseudoCollectionOptionsForLatestFileAndCount(
|
||||
trashItems[0]?.file,
|
||||
trashItems.length,
|
||||
),
|
||||
id: PseudoCollectionID.trash,
|
||||
name: t("section_trash"),
|
||||
type: "trash",
|
||||
@@ -1327,11 +1335,17 @@ const deriveNormalCollectionSummaries = (
|
||||
return normalCollectionSummaries;
|
||||
};
|
||||
|
||||
const pseudoCollectionOptionsForFiles = (files: EnteFile[]) => ({
|
||||
coverFile: files[0],
|
||||
latestFile: files[0],
|
||||
fileCount: files.length,
|
||||
updationTime: files[0]?.updationTime,
|
||||
const pseudoCollectionOptionsForFiles = (files: EnteFile[]) =>
|
||||
pseudoCollectionOptionsForLatestFileAndCount(files[0], files.length);
|
||||
|
||||
const pseudoCollectionOptionsForLatestFileAndCount = (
|
||||
file: EnteFile | undefined,
|
||||
fileCount: number,
|
||||
) => ({
|
||||
coverFile: file,
|
||||
latestFile: file,
|
||||
fileCount,
|
||||
updationTime: file?.updationTime,
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -1738,7 +1752,7 @@ const stateForUpdatedNormalFiles = (
|
||||
state.user!,
|
||||
state.normalCollections,
|
||||
normalFiles,
|
||||
state.trashedFiles,
|
||||
state.trashItems,
|
||||
state.archivedFileIDs,
|
||||
),
|
||||
pendingSearchSuggestions: enqueuePendingSearchSuggestionsIfNeeded(
|
||||
@@ -1788,7 +1802,7 @@ const stateByUpdatingFilteredFiles = (state: GalleryState) => {
|
||||
} else if (state.view?.type == "albums") {
|
||||
const filteredFiles = deriveAlbumsFilteredFiles(
|
||||
state.normalFiles,
|
||||
state.trashedFiles,
|
||||
state.trashItems,
|
||||
state.hiddenFileIDs,
|
||||
state.archivedCollectionIDs,
|
||||
state.archivedFileIDs,
|
||||
@@ -1822,7 +1836,7 @@ const stateByUpdatingFilteredFiles = (state: GalleryState) => {
|
||||
*/
|
||||
const deriveAlbumsFilteredFiles = (
|
||||
normalFiles: GalleryState["normalFiles"],
|
||||
trashedFiles: GalleryState["trashedFiles"],
|
||||
trashItems: GalleryState["trashItems"],
|
||||
hiddenFileIDs: GalleryState["hiddenFileIDs"],
|
||||
archivedCollectionIDs: GalleryState["archivedCollectionIDs"],
|
||||
archivedFileIDs: GalleryState["archivedFileIDs"],
|
||||
@@ -1833,9 +1847,16 @@ const deriveAlbumsFilteredFiles = (
|
||||
const activeCollectionSummaryID = view.activeCollectionSummaryID;
|
||||
|
||||
// Trash is dealt with separately.
|
||||
//
|
||||
// [Note: Files in trash pseudo collection have deleteBy]
|
||||
//
|
||||
// When showing the trash pseudo collection, each file in the files array is
|
||||
// in fact an instance of `EnteTrashFile` - it has an additional (and
|
||||
// optional) `deleteBy` property. The types don't reflect this.
|
||||
|
||||
if (activeCollectionSummaryID == PseudoCollectionID.trash) {
|
||||
return uniqueFilesByID([
|
||||
...trashedFiles,
|
||||
...trashItems.map(({ file, deleteBy }) => ({ ...file, deleteBy })),
|
||||
...normalFiles.filter((file) => tempDeletedFileIDs.has(file.id)),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -38,8 +38,13 @@ export const PseudoCollectionID = {
|
||||
/**
|
||||
* Trash.
|
||||
*
|
||||
* This pseudo-collection contains files that are in the user's trash -
|
||||
* files that have been deleted, but have not yet been deleted permanently.
|
||||
* This pseudo-collection contains items that are in the user's trash. Each
|
||||
* items corresponds to a file that has been deleted, but has not yet been
|
||||
* deleted permanently.
|
||||
*
|
||||
* As a special case, when the trash pseudo collection is being shown, then
|
||||
* the corresponding files array will have {@link EnteTrashFile} items
|
||||
* instead of normal {@link EnteFile} ones.
|
||||
*/
|
||||
trash: -2,
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { searchDataSync } from "ente-new/photos/services/search";
|
||||
import { syncSettings } from "ente-new/photos/services/settings";
|
||||
import { splitByPredicate } from "ente-utils/array";
|
||||
import { pullTrash } from "./trash";
|
||||
import { pullTrash, type TrashItem } from "./trash";
|
||||
|
||||
/**
|
||||
* Called during a full sync, before doing the collection and file sync.
|
||||
@@ -99,9 +99,10 @@ interface SyncCallectionAndFilesOpts {
|
||||
*/
|
||||
onFetchHiddenFiles: (files: EnteFile[]) => void;
|
||||
/**
|
||||
* Called when saved trashed files were replaced by the given {@link files}.
|
||||
* Called when saved trashed items were replaced by the given
|
||||
* {@link trashItems}.
|
||||
*/
|
||||
onResetTrashedFiles: (files: EnteFile[]) => void;
|
||||
onSetTrashedItems: (trashItems: TrashItem[]) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,7 +145,7 @@ export const syncCollectionAndFiles = async (
|
||||
);
|
||||
await pullTrash(
|
||||
collections,
|
||||
opts?.onResetTrashedFiles,
|
||||
opts?.onSetTrashedItems,
|
||||
videoPrunePermanentlyDeletedFileIDsIfNeeded,
|
||||
);
|
||||
if (didUpdateNormalFiles || didUpdateHiddenFiles) {
|
||||
|
||||
@@ -79,15 +79,12 @@ export type RemoteTrashItem = z.infer<typeof RemoteTrashItem>;
|
||||
* @param collections All the (non-deleted) collections that we know about
|
||||
* locally.
|
||||
*
|
||||
* @param onUpdateTrashFiles A callback invoked when the locally persisted trash
|
||||
* @param onSetTrashedItems A callback invoked when the locally persisted trash
|
||||
* items are updated. This can be used for the UI to also update its state.
|
||||
*
|
||||
* This callback can be invoked multiple times during the pull (once for each
|
||||
* batch that gets pulled and processed).
|
||||
*
|
||||
* Each time, it gets passed a list of all the items that are present in trash,
|
||||
* sorted in ascending order of their time to deletion. For more details about
|
||||
* the sorting order, see {@link sortTrashItems}.
|
||||
* batch that gets pulled and processed). Each time, it gets passed a list of
|
||||
* all the items that are present in trash (in an arbitrary order).
|
||||
*
|
||||
* @param onPruneDeletedFileIDs A callback invoked when files that were
|
||||
* previously in trash have now been permanently deleted. This can be used by
|
||||
@@ -99,7 +96,7 @@ export type RemoteTrashItem = z.infer<typeof RemoteTrashItem>;
|
||||
*/
|
||||
export const pullTrash = async (
|
||||
collections: Collection[],
|
||||
onUpdateTrashFiles: ((files: EnteFile[]) => void) | undefined,
|
||||
onSetTrashedItems: ((trashItems: TrashItem[]) => void) | undefined,
|
||||
onPruneDeletedFileIDs: (deletedFileIDs: Set<number>) => Promise<void>,
|
||||
): Promise<void> => {
|
||||
// Data structures:
|
||||
@@ -169,7 +166,7 @@ export const pullTrash = async (
|
||||
}
|
||||
|
||||
const trashItems = [...trashItemsByID.values()];
|
||||
onUpdateTrashFiles?.(getTrashedFiles(trashItems));
|
||||
onSetTrashedItems?.(trashItems);
|
||||
await saveTrashItems(trashItems);
|
||||
await saveTrashLastUpdatedAt(sinceTime);
|
||||
if (deletedFileIDs.size) await onPruneDeletedFileIDs(deletedFileIDs);
|
||||
@@ -214,31 +211,6 @@ const getTrashDiff = async (sinceTime: number) => {
|
||||
return TrashDiffResponse.parse(await res.json());
|
||||
};
|
||||
|
||||
/**
|
||||
* Return all the trash items present locally after sorting them.
|
||||
*/
|
||||
export async function getLocalTrashedFiles() {
|
||||
return getTrashedFiles(await savedTrashItems());
|
||||
}
|
||||
|
||||
/**
|
||||
* A file augmented with the date when it will be permanently deleted.
|
||||
*/
|
||||
export type EnteTrashFile = EnteFile & {
|
||||
/**
|
||||
* Timestamp (epoch microseconds) when the trash item (and its corresponding
|
||||
* {@link EnteFile}) will be permanently deleted.
|
||||
*/
|
||||
deleteBy?: number;
|
||||
};
|
||||
|
||||
const getTrashedFiles = (trashItems: TrashItem[]): EnteTrashFile[] =>
|
||||
sortTrashItems(trashItems).map(({ file, updatedAt, deleteBy }) => ({
|
||||
...file,
|
||||
updationTime: updatedAt,
|
||||
deleteBy,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Sort trash items such that the items which will be permanently deleted
|
||||
* earlier are first.
|
||||
@@ -250,7 +222,7 @@ const getTrashedFiles = (trashItems: TrashItem[]): EnteTrashFile[] =>
|
||||
* the same time to deletion, the ordering is in descending order of the item's
|
||||
* file's modification or creation date.
|
||||
*/
|
||||
const sortTrashItems = (trashItems: TrashItem[]) =>
|
||||
export const sortTrashItems = (trashItems: TrashItem[]) =>
|
||||
trashItems.sort((a, b) => {
|
||||
if (a.deleteBy == b.deleteBy) {
|
||||
const af = a.file;
|
||||
|
||||
Reference in New Issue
Block a user