wip: checkpoint

This commit is contained in:
Manav Rathi
2024-10-19 11:03:44 +05:30
parent 9f9c060694
commit f718c20362
12 changed files with 445 additions and 390 deletions

View File

@@ -13,6 +13,10 @@ import type {
CollectionSummary,
CollectionSummaryType,
} from "@/new/photos/services/collection/ui";
import {
isArchivedCollection,
isPinnedCollection,
} from "@/new/photos/services/magic-metadata";
import { AppContext } from "@/new/photos/types/context";
import { HorizontalFlex } from "@ente/shared/components/Container";
import OverflowMenu, {
@@ -54,7 +58,6 @@ import {
HIDDEN_ITEMS_SECTION,
isHiddenCollection,
} from "utils/collection";
import { isArchivedCollection, isPinnedCollection } from "utils/magicMetadata";
interface CollectionHeaderProps {
collectionSummary: CollectionSummary;

View File

@@ -10,6 +10,11 @@ import { savedLogs } from "@/base/log-web";
import { customAPIHost } from "@/base/origins";
import { downloadString } from "@/base/utils/web";
import { downloadAppDialogAttributes } from "@/new/photos/components/utils/download";
import {
ARCHIVE_SECTION,
DUMMY_UNCATEGORIZED_COLLECTION,
TRASH_SECTION,
} from "@/new/photos/services/collection";
import type { CollectionSummaries } from "@/new/photos/services/collection/ui";
import { AppContext, useAppContext } from "@/new/photos/types/context";
import { initiateEmail, openURL } from "@/new/photos/utils/web";
@@ -71,11 +76,6 @@ import {
isSubscriptionCancelled,
isSubscriptionPastDue,
} from "utils/billing";
import {
ARCHIVE_SECTION,
DUMMY_UNCATEGORIZED_COLLECTION,
TRASH_SECTION,
} from "utils/collection";
import { isFamilyAdmin, isPartOfFamily } from "utils/user/family";
import { testUpload } from "../../../tests/upload.test";
import { MemberSubscriptionManage } from "../MemberSubscriptionManage";

View File

@@ -2,6 +2,11 @@ import { SelectionBar } from "@/base/components/Navbar";
import type { Collection } from "@/media/collection";
import type { CollectionSelectorAttributes } from "@/new/photos/components/CollectionSelector";
import type { GalleryBarMode } from "@/new/photos/components/gallery/BarImpl";
import {
ALL_SECTION,
ARCHIVE_SECTION,
TRASH_SECTION,
} from "@/new/photos/services/collection";
import { AppContext } from "@/new/photos/types/context";
import { FluidContainer } from "@ente/shared/components/Container";
import ClockIcon from "@mui/icons-material/AccessTime";
@@ -20,12 +25,7 @@ import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
import { Box, IconButton, Stack, Tooltip } from "@mui/material";
import { t } from "i18next";
import { useContext } from "react";
import {
ALL_SECTION,
ARCHIVE_SECTION,
COLLECTION_OPS_TYPE,
TRASH_SECTION,
} from "utils/collection";
import { COLLECTION_OPS_TYPE } from "utils/collection";
import { FILE_OPS_TYPE } from "utils/file";
import { formatNumber } from "utils/number/format";
import { getTrashFilesMessage } from "utils/ui";

View File

@@ -62,7 +62,6 @@ import {
clearKeys,
getKey,
} from "@ente/shared/storage/sessionStorage";
import type { User } from "@ente/shared/user/types";
import ArrowBack from "@mui/icons-material/ArrowBack";
import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined";
import MenuIcon from "@mui/icons-material/Menu";
@@ -150,7 +149,7 @@ import {
getUniqueFiles,
handleFileOps,
} from "utils/file";
import { isArchivedFile } from "utils/magicMetadata";
import { isArchivedFile } from "@/new/photos/services/magic-metadata";
import { getSessionExpiredMessage } from "utils/ui";
import { getLocalFamilyData } from "utils/user/family";
@@ -873,62 +872,6 @@ export default function Gallery() {
};
};
const setDerivativeState = (
user: User,
collections: Collection[],
hiddenCollections: Collection[],
files: EnteFile[],
trashedFiles: EnteFile[],
hiddenFiles: EnteFile[],
) => {
let favItemIds = new Set<number>();
for (const collection of collections) {
if (collection.type === CollectionType.favorites) {
favItemIds = new Set(
files
.filter((file) => file.collectionID === collection.id)
.map((file): number => file.id),
);
break;
}
}
setFavItemIds(favItemIds);
const archivedCollections = getArchivedCollections(collections);
setArchivedCollections(archivedCollections);
const defaultHiddenCollectionIDs =
getDefaultHiddenCollectionIDs(hiddenCollections);
setDefaultHiddenCollectionIDs(defaultHiddenCollectionIDs);
const hiddenFileIds = new Set<number>(hiddenFiles.map((f) => f.id));
setHiddenFileIds(hiddenFileIds);
const collectionSummaries = getCollectionSummaries(
user,
collections,
files,
);
const sectionSummaries = getSectionSummaries(
files,
trashedFiles,
archivedCollections,
);
const hiddenCollectionSummaries = getCollectionSummaries(
user,
hiddenCollections,
hiddenFiles,
);
const hiddenItemsSummaries = getHiddenItemsSummary(
hiddenFiles,
hiddenCollections,
);
hiddenCollectionSummaries.set(
HIDDEN_ITEMS_SECTION,
hiddenItemsSummaries,
);
setCollectionSummaries(
mergeMaps(collectionSummaries, sectionSummaries),
);
setHiddenCollectionSummaries(hiddenCollectionSummaries);
};
const setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator =
(folderName, collectionID, isHidden) => {
const id = filesDownloadProgressAttributesList?.length ?? 0;

View File

@@ -31,6 +31,12 @@ import {
CollectionsSortBy,
} from "@/new/photos/services/collection/ui";
import { getLocalFiles, sortFiles } from "@/new/photos/services/files";
import {
isArchivedCollection,
isArchivedFile,
isPinnedCollection,
updateMagicMetadata,
} from "@/new/photos/services/magic-metadata";
import { batch } from "@/utils/array";
import { CustomError } from "@ente/shared/error";
import HTTPService from "@ente/shared/network/HTTPService";
@@ -60,12 +66,6 @@ import {
isValidMoveTarget,
} from "utils/collection";
import { getUniqueFiles, groupFilesBasedOnCollectionID } from "utils/file";
import {
isArchivedCollection,
isArchivedFile,
isPinnedCollection,
updateMagicMetadata,
} from "utils/magicMetadata";
import { UpdateMagicMetadataRequest } from "./fileService";
import { getPublicKey } from "./userService";
@@ -1107,156 +1107,6 @@ function compareCollectionsLatestFile(first: EnteFile, second: EnteFile) {
}
}
export function getCollectionSummaries(
user: User,
collections: Collection[],
files: EnteFile[],
): CollectionSummaries {
const collectionSummaries: CollectionSummaries = new Map();
const collectionLatestFiles = getCollectionLatestFiles(files);
const collectionCoverFiles = getCollectionCoverFiles(files, collections);
const collectionFilesCount = getCollectionsFileCount(files);
let hasUncategorizedCollection = false;
for (const collection of collections) {
if (
!hasUncategorizedCollection &&
collection.type === CollectionType.uncategorized
) {
hasUncategorizedCollection = true;
}
let type: CollectionSummaryType;
if (isIncomingShare(collection, user)) {
if (isIncomingCollabShare(collection, user)) {
type = "incomingShareCollaborator";
} else {
type = "incomingShareViewer";
}
} else if (isOutgoingShare(collection, user)) {
type = "outgoingShare";
} else if (isSharedOnlyViaLink(collection)) {
type = "sharedOnlyViaLink";
} else if (isArchivedCollection(collection)) {
type = "archived";
} else if (isDefaultHiddenCollection(collection)) {
type = "defaultHidden";
} else if (isPinnedCollection(collection)) {
type = "pinned";
} else {
// Directly use the collection type
// TODO: The constants can be aligned once collection type goes from
// an enum to an union.
switch (collection.type) {
case CollectionType.folder:
type = "folder";
break;
case CollectionType.favorites:
type = "favorites";
break;
case CollectionType.album:
type = "album";
break;
case CollectionType.uncategorized:
type = "uncategorized";
break;
}
}
let CollectionSummaryItemName: string;
if (type == "uncategorized") {
CollectionSummaryItemName = t("section_uncategorized");
} else if (type == "favorites") {
CollectionSummaryItemName = t("favorites");
} else {
CollectionSummaryItemName = collection.name;
}
collectionSummaries.set(collection.id, {
id: collection.id,
name: CollectionSummaryItemName,
latestFile: collectionLatestFiles.get(collection.id),
coverFile: collectionCoverFiles.get(collection.id),
fileCount: collectionFilesCount.get(collection.id) ?? 0,
updationTime: collection.updationTime,
type: type,
order: collection.magicMetadata?.data?.order ?? 0,
});
}
if (!hasUncategorizedCollection) {
collectionSummaries.set(
DUMMY_UNCATEGORIZED_COLLECTION,
getDummyUncategorizedCollectionSummary(),
);
}
return collectionSummaries;
}
function getCollectionsFileCount(files: EnteFile[]): Map<number, number> {
const collectionIDToFileMap = groupFilesBasedOnCollectionID(files);
const collectionFilesCount = new Map<number, number>();
for (const [id, files] of collectionIDToFileMap) {
collectionFilesCount.set(id, files.length);
}
return collectionFilesCount;
}
export function getSectionSummaries(
files: EnteFile[],
trashedFiles: EnteFile[],
archivedCollections: Set<number>,
): CollectionSummaries {
const collectionSummaries: CollectionSummaries = new Map();
collectionSummaries.set(
ALL_SECTION,
getAllSectionSummary(files, archivedCollections),
);
collectionSummaries.set(
TRASH_SECTION,
getTrashedCollectionSummary(trashedFiles),
);
collectionSummaries.set(ARCHIVE_SECTION, getArchivedSectionSummary(files));
return collectionSummaries;
}
function getAllSectionSummary(
files: EnteFile[],
archivedCollections: Set<number>,
): CollectionSummary {
const allSectionFiles = getAllSectionVisibleFiles(
files,
archivedCollections,
);
return {
id: ALL_SECTION,
name: t("section_all"),
type: "all",
coverFile: allSectionFiles?.[0],
latestFile: allSectionFiles?.[0],
fileCount: allSectionFiles?.length || 0,
updationTime: allSectionFiles?.[0]?.updationTime,
};
}
function getAllSectionVisibleFiles(
files: EnteFile[],
archivedCollections: Set<number>,
): EnteFile[] {
const allSectionVisibleFiles = getUniqueFiles(
files.filter((file) => {
if (
isArchivedFile(file) ||
archivedCollections.has(file.collectionID)
) {
return false;
}
return true;
}),
);
return allSectionVisibleFiles;
}
export function getDummyUncategorizedCollectionSummary(): CollectionSummary {
return {
id: DUMMY_UNCATEGORIZED_COLLECTION,
@@ -1269,47 +1119,7 @@ export function getDummyUncategorizedCollectionSummary(): CollectionSummary {
};
}
export function getArchivedSectionSummary(
files: EnteFile[],
): CollectionSummary {
const archivedFiles = getUniqueFiles(
files.filter((file) => isArchivedFile(file)),
);
return {
id: ARCHIVE_SECTION,
name: t("section_archive"),
type: "archive",
coverFile: null,
latestFile: archivedFiles?.[0],
fileCount: archivedFiles?.length,
updationTime: archivedFiles?.[0]?.updationTime,
};
}
export function getHiddenItemsSummary(
hiddenFiles: EnteFile[],
hiddenCollections: Collection[],
): CollectionSummary {
const defaultHiddenCollectionIds = new Set(
hiddenCollections
.filter((collection) => isDefaultHiddenCollection(collection))
.map((collection) => collection.id),
);
const hiddenItems = getUniqueFiles(
hiddenFiles.filter((file) =>
defaultHiddenCollectionIds.has(file.collectionID),
),
);
return {
id: HIDDEN_ITEMS_SECTION,
name: t("hidden_items"),
type: "hiddenItems",
coverFile: hiddenItems?.[0],
latestFile: hiddenItems?.[0],
fileCount: hiddenItems?.length,
updationTime: hiddenItems?.[0]?.updationTime,
};
}
export function getTrashedCollectionSummary(
trashedFiles: EnteFile[],

View File

@@ -26,6 +26,10 @@ import { FileType, type FileTypeInfo } from "@/media/file-type";
import { encodeLivePhoto } from "@/media/live-photo";
import { extractExif } from "@/new/photos/services/exif";
import * as ffmpeg from "@/new/photos/services/ffmpeg";
import {
getNonEmptyMagicMetadataProps,
updateMagicMetadata,
} from "@/new/photos/services/magic-metadata";
import type { UploadItem } from "@/new/photos/services/upload/types";
import {
RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
@@ -40,10 +44,6 @@ import {
PublicUploadProps,
type LivePhotoAssets,
} from "services/upload/uploadManager";
import {
getNonEmptyMagicMetadataProps,
updateMagicMetadata,
} from "utils/magicMetadata";
import * as convert from "xml-js";
import { tryParseEpochMicrosecondsFromFileName } from "./date";
import publicUploadHttpClient from "./publicUploadHttpClient";

View File

@@ -10,7 +10,12 @@ import {
} from "@/media/collection";
import { EnteFile } from "@/media/file";
import { ItemVisibility } from "@/media/file-metadata";
import { getDefaultHiddenCollectionIDs, isDefaultHiddenCollection, isIncomingShare } from "@/new/photos/services/collection";
import { getAllLocalFiles, getLocalFiles } from "@/new/photos/services/files";
import {
isArchivedCollection,
updateMagicMetadata,
} from "@/new/photos/services/magic-metadata";
import { safeDirectoryName } from "@/new/photos/utils/native-fs";
import { CustomError } from "@ente/shared/error";
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
@@ -33,13 +38,8 @@ import {
} from "services/collectionService";
import { SetFilesDownloadProgressAttributes } from "types/gallery";
import { downloadFilesWithProgress } from "utils/file";
import { isArchivedCollection, updateMagicMetadata } from "utils/magicMetadata";
export const ARCHIVE_SECTION = -1;
export const TRASH_SECTION = -2;
export const DUMMY_UNCATEGORIZED_COLLECTION = -3;
export const HIDDEN_ITEMS_SECTION = -4;
export const ALL_SECTION = 0;
export enum COLLECTION_OPS_TYPE {
ADD,
@@ -336,14 +336,6 @@ export const getArchivedCollections = (collections: Collection[]) => {
);
};
export const getDefaultHiddenCollectionIDs = (collections: Collection[]) => {
return new Set<number>(
collections
.filter(isDefaultHiddenCollection)
.map((collection) => collection.id),
);
};
export const getUserOwnedCollections = (collections: Collection[]) => {
const user: User = getData(LS_KEYS.USER);
if (!user?.id) {
@@ -352,37 +344,19 @@ export const getUserOwnedCollections = (collections: Collection[]) => {
return collections.filter((collection) => collection.owner.id === user.id);
};
export const isDefaultHiddenCollection = (collection: Collection) =>
collection.magicMetadata?.data.subType === SUB_TYPE.DEFAULT_HIDDEN;
export const isHiddenCollection = (collection: Collection) =>
collection.magicMetadata?.data.visibility === ItemVisibility.hidden;
export const isQuickLinkCollection = (collection: Collection) =>
collection.magicMetadata?.data.subType === SUB_TYPE.QUICK_LINK_COLLECTION;
export function isOutgoingShare(collection: Collection, user: User): boolean {
return collection.owner.id === user.id && collection.sharees?.length > 0;
}
export function isIncomingShare(collection: Collection, user: User) {
return collection.owner.id !== user.id;
}
export function isIncomingViewerShare(collection: Collection, user: User) {
const sharee = collection.sharees?.find((sharee) => sharee.id === user.id);
return sharee?.role === COLLECTION_ROLE.VIEWER;
}
export function isIncomingCollabShare(collection: Collection, user: User) {
const sharee = collection.sharees?.find((sharee) => sharee.id === user.id);
return sharee?.role === COLLECTION_ROLE.COLLABORATOR;
}
export function isSharedOnlyViaLink(collection: Collection) {
return collection.publicURLs?.length && !collection.sharees?.length;
}
export function isValidMoveTarget(
sourceCollectionID: number,
targetCollection: Collection,

View File

@@ -17,6 +17,10 @@ import { FileType } from "@/media/file-type";
import { decodeLivePhoto } from "@/media/live-photo";
import DownloadManager from "@/new/photos/services/download";
import { updateExifIfNeededAndPossible } from "@/new/photos/services/exif-update";
import {
isArchivedFile,
updateMagicMetadata,
} from "@/new/photos/services/magic-metadata";
import { detectFileTypeInfo } from "@/new/photos/utils/detect-type";
import { safeFileName } from "@/new/photos/utils/native-fs";
import { writeStream } from "@/new/photos/utils/native-stream";
@@ -39,7 +43,6 @@ import {
SetFilesDownloadProgressAttributes,
SetFilesDownloadProgressAttributesCreator,
} from "types/gallery";
import { isArchivedFile, updateMagicMetadata } from "utils/magicMetadata";
export enum FILE_OPS_TYPE {
DOWNLOAD,
@@ -260,20 +263,6 @@ export async function getFileFromURL(fileURL: string, name: string) {
return fileFile;
}
export function getUniqueFiles(files: EnteFile[]) {
const idSet = new Set<number>();
const uniqueFiles = files.filter((file) => {
if (!idSet.has(file.id)) {
idSet.add(file.id);
return true;
} else {
return false;
}
});
return uniqueFiles;
}
export async function downloadFilesWithProgress(
files: EnteFile[],
downloadDirPath: string,

View File

@@ -0,0 +1,370 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/**
* @file code that really belongs to pages/gallery.tsx itself (or related
* files), but it written here in a separate file so that we can write in this
* package that has TypeScript strict mode enabled.
*
* Once the original gallery.tsx is strict mode, this code can be inlined back
* there.
*/
import { CollectionType, type Collection } from "@/media/collection";
import type { EnteFile } from "@/media/file";
import type { User } from "@ente/shared/user/types";
import { t } from "i18next";
import React, { useReducer } from "react";
import {
ALL_SECTION,
ARCHIVE_SECTION,
DUMMY_UNCATEGORIZED_COLLECTION,
getDefaultHiddenCollectionIDs,
HIDDEN_ITEMS_SECTION,
isDefaultHiddenCollection,
isIncomingCollabShare,
isIncomingShare,
TRASH_SECTION,
} from "../../services/collection";
import type {
CollectionSummaries,
CollectionSummary,
CollectionSummaryType,
} from "../../services/collection/ui";
import {
isArchivedCollection,
isArchivedFile,
isPinnedCollection,
} from "../../services/magic-metadata";
import type { Person } from "../../services/ml/people";
/**
* Derived UI state backing the gallery.
*
* This might be different from the actual different from the actual underlying
* state since there might be unsynced data (hidden or deleted that have not yet
* been synced with remote) that should be temporarily taken into account for
* the UI state until the operation completes.
*/
export interface GalleryState {
filteredData: EnteFile[];
/**
* The currently selected person, if any.
*
* Whenever this is present, it is guaranteed to be one of the items from
* within {@link people}.
*/
activePerson: Person | undefined;
/**
* The list of people to show.
*/
people: Person[] | undefined;
}
// TODO: dummy actions for gradual migration to reducers
export type GalleryAction =
| {
type: "set";
filteredData: EnteFile[];
galleryPeopleState:
| { activePerson: Person | undefined; people: Person[] }
| undefined;
}
| { type: "dummy" };
const initialGalleryState: GalleryState = {
filteredData: [],
activePerson: undefined,
people: [],
};
const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
state,
action,
) => {
switch (action.type) {
case "dummy":
return state;
case "set":
return {
...state,
filteredData: action.filteredData,
activePerson: action.galleryPeopleState?.activePerson,
people: action.galleryPeopleState?.people,
};
}
};
export const useGalleryReducer = () =>
useReducer(galleryReducer, initialGalleryState);
export const setDerivativeState = (
user: User,
collections: Collection[],
hiddenCollections: Collection[],
files: EnteFile[],
trashedFiles: EnteFile[],
hiddenFiles: EnteFile[],
) => {
let favItemIds = new Set<number>();
for (const collection of collections) {
if (collection.type === CollectionType.favorites) {
favItemIds = new Set(
files
.filter((file) => file.collectionID === collection.id)
.map((file): number => file.id),
);
break;
}
}
setFavItemIds(favItemIds);
const archivedCollections = getArchivedCollections(collections);
setArchivedCollections(archivedCollections);
const defaultHiddenCollectionIDs =
getDefaultHiddenCollectionIDs(hiddenCollections);
setDefaultHiddenCollectionIDs(defaultHiddenCollectionIDs);
const hiddenFileIds = new Set<number>(hiddenFiles.map((f) => f.id));
setHiddenFileIds(hiddenFileIds);
const collectionSummaries = getCollectionSummaries(
user,
collections,
files,
);
const sectionSummaries = getSectionSummaries(
files,
trashedFiles,
archivedCollections,
);
const hiddenCollectionSummaries = getCollectionSummaries(
user,
hiddenCollections,
hiddenFiles,
);
const hiddenItemsSummaries = getHiddenItemsSummary(
hiddenFiles,
hiddenCollections,
);
hiddenCollectionSummaries.set(HIDDEN_ITEMS_SECTION, hiddenItemsSummaries);
setCollectionSummaries(mergeMaps(collectionSummaries, sectionSummaries));
setHiddenCollectionSummaries(hiddenCollectionSummaries);
};
export function getUniqueFiles(files: EnteFile[]) {
const idSet = new Set<number>();
const uniqueFiles = files.filter((file) => {
if (!idSet.has(file.id)) {
idSet.add(file.id);
return true;
} else {
return false;
}
});
return uniqueFiles;
}
export const getArchivedCollections = (collections: Collection[]) => {
return new Set<number>(
collections
.filter(isArchivedCollection)
.map((collection) => collection.id),
);
};
export function getCollectionSummaries(
user: User,
collections: Collection[],
files: EnteFile[],
): CollectionSummaries {
const collectionSummaries: CollectionSummaries = new Map();
const collectionLatestFiles = getCollectionLatestFiles(files);
const collectionCoverFiles = getCollectionCoverFiles(files, collections);
const collectionFilesCount = getCollectionsFileCount(files);
let hasUncategorizedCollection = false;
for (const collection of collections) {
if (
!hasUncategorizedCollection &&
collection.type === CollectionType.uncategorized
) {
hasUncategorizedCollection = true;
}
let type: CollectionSummaryType;
if (isIncomingShare(collection, user)) {
if (isIncomingCollabShare(collection, user)) {
type = "incomingShareCollaborator";
} else {
type = "incomingShareViewer";
}
} else if (isOutgoingShare(collection, user)) {
type = "outgoingShare";
} else if (isSharedOnlyViaLink(collection)) {
type = "sharedOnlyViaLink";
} else if (isArchivedCollection(collection)) {
type = "archived";
} else if (isDefaultHiddenCollection(collection)) {
type = "defaultHidden";
} else if (isPinnedCollection(collection)) {
type = "pinned";
} else {
// Directly use the collection type
// TODO: The constants can be aligned once collection type goes from
// an enum to an union.
switch (collection.type) {
case CollectionType.folder:
type = "folder";
break;
case CollectionType.favorites:
type = "favorites";
break;
case CollectionType.album:
type = "album";
break;
case CollectionType.uncategorized:
type = "uncategorized";
break;
}
}
let CollectionSummaryItemName: string;
if (type == "uncategorized") {
CollectionSummaryItemName = t("section_uncategorized");
} else if (type == "favorites") {
CollectionSummaryItemName = t("favorites");
} else {
CollectionSummaryItemName = collection.name;
}
collectionSummaries.set(collection.id, {
id: collection.id,
name: CollectionSummaryItemName,
latestFile: collectionLatestFiles.get(collection.id),
coverFile: collectionCoverFiles.get(collection.id),
fileCount: collectionFilesCount.get(collection.id) ?? 0,
updationTime: collection.updationTime,
type: type,
order: collection.magicMetadata?.data?.order ?? 0,
});
}
if (!hasUncategorizedCollection) {
collectionSummaries.set(
DUMMY_UNCATEGORIZED_COLLECTION,
getDummyUncategorizedCollectionSummary(),
);
}
return collectionSummaries;
}
export function isOutgoingShare(collection: Collection, user: User): boolean {
return collection.owner.id === user.id && collection.sharees?.length > 0;
}
export function isSharedOnlyViaLink(collection: Collection) {
return collection.publicURLs?.length && !collection.sharees?.length;
}
export function getHiddenItemsSummary(
hiddenFiles: EnteFile[],
hiddenCollections: Collection[],
): CollectionSummary {
const defaultHiddenCollectionIds = new Set(
hiddenCollections
.filter((collection) => isDefaultHiddenCollection(collection))
.map((collection) => collection.id),
);
const hiddenItems = getUniqueFiles(
hiddenFiles.filter((file) =>
defaultHiddenCollectionIds.has(file.collectionID),
),
);
return {
id: HIDDEN_ITEMS_SECTION,
name: t("hidden_items"),
type: "hiddenItems",
coverFile: hiddenItems?.[0],
latestFile: hiddenItems?.[0],
fileCount: hiddenItems?.length,
updationTime: hiddenItems?.[0]?.updationTime,
};
}
export function getSectionSummaries(
files: EnteFile[],
trashedFiles: EnteFile[],
archivedCollections: Set<number>,
): CollectionSummaries {
const collectionSummaries: CollectionSummaries = new Map();
collectionSummaries.set(
ALL_SECTION,
getAllSectionSummary(files, archivedCollections),
);
collectionSummaries.set(
TRASH_SECTION,
getTrashedCollectionSummary(trashedFiles),
);
collectionSummaries.set(ARCHIVE_SECTION, getArchivedSectionSummary(files));
return collectionSummaries;
}
export function getArchivedSectionSummary(
files: EnteFile[],
): CollectionSummary {
const archivedFiles = getUniqueFiles(
files.filter((file) => isArchivedFile(file)),
);
return {
id: ARCHIVE_SECTION,
name: t("section_archive"),
type: "archive",
coverFile: null,
latestFile: archivedFiles?.[0],
fileCount: archivedFiles?.length,
updationTime: archivedFiles?.[0]?.updationTime,
};
}
function getAllSectionSummary(
files: EnteFile[],
archivedCollections: Set<number>,
): CollectionSummary {
const allSectionFiles = getAllSectionVisibleFiles(
files,
archivedCollections,
);
return {
id: ALL_SECTION,
name: t("section_all"),
type: "all",
coverFile: allSectionFiles?.[0],
latestFile: allSectionFiles?.[0],
fileCount: allSectionFiles?.length || 0,
updationTime: allSectionFiles?.[0]?.updationTime,
};
}
function getCollectionsFileCount(files: EnteFile[]): Map<number, number> {
const collectionIDToFileMap = groupFilesBasedOnCollectionID(files);
const collectionFilesCount = new Map<number, number>();
for (const [id, files] of collectionIDToFileMap) {
collectionFilesCount.set(id, files.length);
}
return collectionFilesCount;
}
function getAllSectionVisibleFiles(
files: EnteFile[],
archivedCollections: Set<number>,
): EnteFile[] {
const allSectionVisibleFiles = getUniqueFiles(
files.filter((file) => {
if (
isArchivedFile(file) ||
archivedCollections.has(file.collectionID)
) {
return false;
}
return true;
}),
);
return allSectionVisibleFiles;
}

View File

@@ -1,72 +0,0 @@
/**
* @file code that really belongs to pages/gallery.tsx itself (or related
* files), but it written here in a separate file so that we can write in this
* package that has TypeScript strict mode enabled.
*
* Once the original gallery.tsx is strict mode, this code can be inlined back
* there.
*/
import type { EnteFile } from "@/media/file";
import React, { useReducer } from "react";
import type { Person } from "../../services/ml/people";
/**
* Derived UI state backing the gallery.
*
* This might be different from the actual different from the actual underlying
* state since there might be unsynced data (hidden or deleted that have not yet
* been synced with remote) that should be temporarily taken into account for
* the UI state until the operation completes.
*/
export interface GalleryState {
filteredData: EnteFile[];
/**
* The currently selected person, if any.
*
* Whenever this is present, it is guaranteed to be one of the items from
* within {@link people}.
*/
activePerson: Person | undefined;
/**
* The list of people to show.
*/
people: Person[] | undefined;
}
// TODO: dummy actions for gradual migration to reducers
export type GalleryAction =
| {
type: "set";
filteredData: EnteFile[];
galleryPeopleState:
| { activePerson: Person | undefined; people: Person[] }
| undefined;
}
| { type: "dummy" };
const initialGalleryState: GalleryState = {
filteredData: [],
activePerson: undefined,
people: [],
};
const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
state,
action,
) => {
switch (action.type) {
case "dummy":
return state;
case "set":
return {
...state,
filteredData: action.filteredData,
activePerson: action.galleryPeopleState?.activePerson,
people: action.galleryPeopleState?.people,
};
}
};
export const useGalleryReducer = () =>
useReducer(galleryReducer, initialGalleryState);

View File

@@ -0,0 +1,30 @@
import { COLLECTION_ROLE, SUB_TYPE, type Collection } from "@/media/collection";
import type { User } from "@ente/shared/user/types";
export const ARCHIVE_SECTION = -1;
export const TRASH_SECTION = -2;
export const DUMMY_UNCATEGORIZED_COLLECTION = -3;
export const HIDDEN_ITEMS_SECTION = -4;
export const ALL_SECTION = 0;
export const getDefaultHiddenCollectionIDs = (collections: Collection[]) => {
return new Set<number>(
collections
.filter(isDefaultHiddenCollection)
.map((collection) => collection.id),
);
};
export const isDefaultHiddenCollection = (collection: Collection) =>
// TODO: Need to audit the types
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
collection.magicMetadata?.data.subType === SUB_TYPE.DEFAULT_HIDDEN;
export function isIncomingShare(collection: Collection, user: User) {
return collection.owner.id !== user.id;
}
export function isIncomingCollabShare(collection: Collection, user: User) {
const sharee = collection.sharees?.find((sharee) => sharee.id === user.id);
return sharee?.role === COLLECTION_ROLE.COLLABORATOR;
}

View File

@@ -1,6 +1,9 @@
// TODO: Review this file
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
import { sharedCryptoWorker } from "@/base/crypto";
import type { Collection } from "@/media/collection";
import { EnteFile, MagicMetadataCore } from "@/media/file";
import type { EnteFile, MagicMetadataCore } from "@/media/file";
import { ItemVisibility } from "@/media/file-metadata";
export function isArchivedFile(item: EnteFile): boolean {
@@ -57,6 +60,7 @@ export async function updateMagicMetadata<T>(
originalMagicMetadata.data = await cryptoWorker.decryptMetadataJSON({
encryptedDataB64: originalMagicMetadata.data,
decryptionHeaderB64: originalMagicMetadata.header,
// @ts-expect-error TODO: Need to use zod here.
keyB64: decryptionKey,
});
}
@@ -73,6 +77,7 @@ export async function updateMagicMetadata<T>(
const magicMetadata = {
...originalMagicMetadata,
data: nonEmptyMagicMetadataProps,
// @ts-expect-error TODO review this file
count: Object.keys(nonEmptyMagicMetadataProps).length,
};
@@ -82,7 +87,9 @@ export async function updateMagicMetadata<T>(
export const getNewMagicMetadata = <T>(): MagicMetadataCore<T> => {
return {
version: 1,
// @ts-expect-error TODO review this file
data: null,
// @ts-expect-error TODO review this file
header: null,
count: 0,
};
@@ -90,6 +97,7 @@ export const getNewMagicMetadata = <T>(): MagicMetadataCore<T> => {
export const getNonEmptyMagicMetadataProps = <T>(magicMetadataProps: T): T => {
return Object.fromEntries(
// @ts-expect-error TODO review this file
Object.entries(magicMetadataProps).filter(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
([_, v]) => v !== null && v !== undefined,