Swap
This commit is contained in:
@@ -40,6 +40,10 @@ import {
|
||||
HIDDEN_ITEMS_SECTION,
|
||||
isHiddenCollection,
|
||||
leaveSharedCollection,
|
||||
renameCollection,
|
||||
updateCollectionOrder,
|
||||
updateCollectionSortOrder,
|
||||
updateCollectionVisibility,
|
||||
} from "ente-new/photos/services/collection";
|
||||
import type {
|
||||
CollectionSummary,
|
||||
@@ -57,9 +61,6 @@ import { Trans } from "react-i18next";
|
||||
import * as CollectionAPI from "services/collectionService";
|
||||
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
|
||||
import {
|
||||
changeCollectionOrder,
|
||||
changeCollectionSortOrder,
|
||||
changeCollectionVisibility,
|
||||
downloadCollectionHelper,
|
||||
downloadDefaultHiddenCollectionHelper,
|
||||
} from "utils/collection";
|
||||
@@ -166,10 +167,10 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
[showLoadingBar, hideLoadingBar, onGenericError, syncWithRemote],
|
||||
);
|
||||
|
||||
const renameCollection = useCallback(
|
||||
const handleRenameCollection = useCallback(
|
||||
async (newName: string) => {
|
||||
if (activeCollection.name !== newName) {
|
||||
await CollectionAPI.renameCollection(activeCollection, newName);
|
||||
await renameCollection(activeCollection, newName);
|
||||
void syncWithRemote(false, true);
|
||||
}
|
||||
},
|
||||
@@ -254,11 +255,11 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
void _downloadCollection().catch(onGenericError);
|
||||
|
||||
const archiveAlbum = wrap(() =>
|
||||
changeCollectionVisibility(activeCollection, ItemVisibility.archived),
|
||||
updateCollectionVisibility(activeCollection, ItemVisibility.archived),
|
||||
);
|
||||
|
||||
const unarchiveAlbum = wrap(() =>
|
||||
changeCollectionVisibility(activeCollection, ItemVisibility.visible),
|
||||
updateCollectionVisibility(activeCollection, ItemVisibility.visible),
|
||||
);
|
||||
|
||||
const confirmLeaveSharedAlbum = () =>
|
||||
@@ -278,15 +279,15 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
});
|
||||
|
||||
const pinAlbum = wrap(() =>
|
||||
changeCollectionOrder(activeCollection, CollectionOrder.pinned),
|
||||
updateCollectionOrder(activeCollection, CollectionOrder.pinned),
|
||||
);
|
||||
|
||||
const unpinAlbum = wrap(() =>
|
||||
changeCollectionOrder(activeCollection, CollectionOrder.default),
|
||||
updateCollectionOrder(activeCollection, CollectionOrder.default),
|
||||
);
|
||||
|
||||
const hideAlbum = wrap(async () => {
|
||||
await changeCollectionVisibility(
|
||||
await updateCollectionVisibility(
|
||||
activeCollection,
|
||||
ItemVisibility.hidden,
|
||||
);
|
||||
@@ -294,7 +295,7 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
});
|
||||
|
||||
const unhideAlbum = wrap(async () => {
|
||||
await changeCollectionVisibility(
|
||||
await updateCollectionVisibility(
|
||||
activeCollection,
|
||||
ItemVisibility.visible,
|
||||
);
|
||||
@@ -302,11 +303,11 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
});
|
||||
|
||||
const changeSortOrderAsc = wrap(() =>
|
||||
changeCollectionSortOrder(activeCollection, true),
|
||||
updateCollectionSortOrder(activeCollection, true),
|
||||
);
|
||||
|
||||
const changeSortOrderDesc = wrap(() =>
|
||||
changeCollectionSortOrder(activeCollection, false),
|
||||
updateCollectionSortOrder(activeCollection, false),
|
||||
);
|
||||
|
||||
let menuOptions: React.ReactNode[] = [];
|
||||
@@ -527,7 +528,7 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
initialValue={activeCollection?.name}
|
||||
submitButtonColor="primary"
|
||||
submitButtonTitle={t("rename")}
|
||||
onSubmit={renameCollection}
|
||||
onSubmit={handleRenameCollection}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -12,11 +12,9 @@ import { EnteFile } from "ente-media/file";
|
||||
import { ItemVisibility } from "ente-media/file-metadata";
|
||||
import {
|
||||
addToCollection,
|
||||
collection1To2,
|
||||
createCollection2,
|
||||
isDefaultHiddenCollection,
|
||||
moveToCollection,
|
||||
renameCollection2,
|
||||
} from "ente-new/photos/services/collection";
|
||||
import type { CollectionSummary } from "ente-new/photos/services/collection/ui";
|
||||
import {
|
||||
@@ -252,11 +250,6 @@ export const deleteCollection = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const renameCollection = async (
|
||||
collection: Collection,
|
||||
newCollectionName: string,
|
||||
) => renameCollection2(await collection1To2(collection), newCollectionName);
|
||||
|
||||
/**
|
||||
* Return the user's own favorites collection, if any.
|
||||
*/
|
||||
|
||||
@@ -2,26 +2,17 @@ import type { User } from "ente-accounts/services/user";
|
||||
import { ensureElectron } from "ente-base/electron";
|
||||
import { joinPath } from "ente-base/file-name";
|
||||
import log from "ente-base/log";
|
||||
import {
|
||||
type Collection,
|
||||
type CollectionOrder,
|
||||
CollectionSubType,
|
||||
} from "ente-media/collection";
|
||||
import { type Collection, CollectionSubType } from "ente-media/collection";
|
||||
import { EnteFile } from "ente-media/file";
|
||||
import { ItemVisibility } from "ente-media/file-metadata";
|
||||
import {
|
||||
DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME,
|
||||
HIDDEN_ITEMS_SECTION,
|
||||
addToCollection,
|
||||
collection1To2,
|
||||
findDefaultHiddenCollectionIDs,
|
||||
isHiddenCollection,
|
||||
isIncomingShare,
|
||||
moveToCollection,
|
||||
restoreToCollection,
|
||||
updateCollectionOrder,
|
||||
updateCollectionSortOrder,
|
||||
updateCollectionVisibility,
|
||||
} from "ente-new/photos/services/collection";
|
||||
import {
|
||||
getAllLocalCollections,
|
||||
@@ -181,21 +172,6 @@ async function createCollectionDownloadFolder(
|
||||
return collectionDownloadPath;
|
||||
}
|
||||
|
||||
export const changeCollectionVisibility = async (
|
||||
collection: Collection,
|
||||
visibility: ItemVisibility,
|
||||
) => updateCollectionVisibility(await collection1To2(collection), visibility);
|
||||
|
||||
export const changeCollectionOrder = async (
|
||||
collection: Collection,
|
||||
order: CollectionOrder,
|
||||
) => updateCollectionOrder(await collection1To2(collection), order);
|
||||
|
||||
export const changeCollectionSortOrder = async (
|
||||
collection: Collection,
|
||||
asc: boolean,
|
||||
) => updateCollectionSortOrder(await collection1To2(collection), asc);
|
||||
|
||||
export const getUserOwnedCollections = (collections: Collection[]) => {
|
||||
const user: User = getData("user");
|
||||
if (!user?.id) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { decryptBoxBytes } from "ente-base/crypto";
|
||||
import { type MagicMetadataCore } from "ente-media/file";
|
||||
import {
|
||||
nullishToEmpty,
|
||||
nullishToFalse,
|
||||
@@ -23,11 +22,8 @@ import {
|
||||
*
|
||||
* A collection can be owned by the user (in whose context this code is
|
||||
* running), or might be a collection that is shared with them.
|
||||
*
|
||||
* TODO: This type supercedes {@link Collection}. Once migration is done, rename
|
||||
* this to drop the "2" suffix.
|
||||
*/
|
||||
export interface Collection2 {
|
||||
export interface Collection {
|
||||
/**
|
||||
* The collection's globally unique ID.
|
||||
*
|
||||
@@ -431,22 +427,6 @@ export const RemoteCollection = z.looseObject({
|
||||
|
||||
export type RemoteCollection = z.infer<typeof RemoteCollection>;
|
||||
|
||||
export interface Collection {
|
||||
id: number;
|
||||
owner: CollectionUser;
|
||||
type: CollectionType;
|
||||
sharees: CollectionUser[];
|
||||
publicURLs?: PublicURL[];
|
||||
updationTime: number;
|
||||
key: string;
|
||||
name: string;
|
||||
magicMetadata: MagicMetadataCore<CollectionPrivateMagicMetadataData>;
|
||||
pubMagicMetadata: MagicMetadataCore<CollectionPublicMagicMetadataData>;
|
||||
sharedMagicMetadata: MagicMetadataCore<CollectionShareeMagicMetadataData>;
|
||||
// TODO(C2): Gradual conversion to new structure.
|
||||
c2?: Collection2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a remote collection using the provided {@link collectionKey}.
|
||||
*
|
||||
@@ -456,8 +436,6 @@ export interface Collection {
|
||||
* encrypted fields in {@link collection}.
|
||||
*
|
||||
* @returns A decrypted collection.
|
||||
*
|
||||
* TODO(C2): For legacy compat, it returns the older structure.
|
||||
*/
|
||||
export const decryptRemoteCollection = async (
|
||||
collection: RemoteCollection,
|
||||
@@ -495,7 +473,7 @@ export const decryptRemoteCollection = async (
|
||||
),
|
||||
);
|
||||
|
||||
let magicMetadata: Collection2["magicMetadata"];
|
||||
let magicMetadata: Collection["magicMetadata"];
|
||||
if (encryptedMagicMetadata) {
|
||||
const genericMM = await decryptMagicMetadata(
|
||||
encryptedMagicMetadata,
|
||||
@@ -505,7 +483,7 @@ export const decryptRemoteCollection = async (
|
||||
magicMetadata = { ...genericMM, data };
|
||||
}
|
||||
|
||||
let pubMagicMetadata: Collection2["pubMagicMetadata"];
|
||||
let pubMagicMetadata: Collection["pubMagicMetadata"];
|
||||
if (encryptedPubMagicMetadata) {
|
||||
const genericMM = await decryptMagicMetadata(
|
||||
encryptedPubMagicMetadata,
|
||||
@@ -515,7 +493,7 @@ export const decryptRemoteCollection = async (
|
||||
pubMagicMetadata = { ...genericMM, data };
|
||||
}
|
||||
|
||||
let sharedMagicMetadata: Collection2["sharedMagicMetadata"];
|
||||
let sharedMagicMetadata: Collection["sharedMagicMetadata"];
|
||||
if (encryptedSharedMagicMetadata) {
|
||||
const genericMM = await decryptMagicMetadata(
|
||||
encryptedSharedMagicMetadata,
|
||||
@@ -525,8 +503,7 @@ export const decryptRemoteCollection = async (
|
||||
sharedMagicMetadata = { ...genericMM, data };
|
||||
}
|
||||
|
||||
// return {
|
||||
const c2 = {
|
||||
return {
|
||||
...rest,
|
||||
key: collectionKey,
|
||||
owner: parseRemoteCollectionUser(owner),
|
||||
@@ -536,36 +513,6 @@ export const decryptRemoteCollection = async (
|
||||
pubMagicMetadata,
|
||||
sharedMagicMetadata,
|
||||
};
|
||||
|
||||
// Temporary scaffolding for the migration.
|
||||
return {
|
||||
...collection,
|
||||
// See: [Note: strict mode migration]
|
||||
c2: c2 as Collection2,
|
||||
key: collectionKey,
|
||||
name,
|
||||
type: collection.type as CollectionType,
|
||||
// Some temporary scaffolding to impersonate types.
|
||||
//
|
||||
// See: [Note: strict mode migration]
|
||||
sharees: sharees as CollectionUser[],
|
||||
publicURLs: collection.publicURLs as PublicURL[],
|
||||
magicMetadata: (magicMetadata
|
||||
? { ...magicMetadata, header: collection.magicMetadata!.header }
|
||||
: undefined)!,
|
||||
pubMagicMetadata: (pubMagicMetadata
|
||||
? {
|
||||
...pubMagicMetadata,
|
||||
header: collection.pubMagicMetadata!.header,
|
||||
}
|
||||
: undefined)!,
|
||||
sharedMagicMetadata: (sharedMagicMetadata
|
||||
? {
|
||||
...sharedMagicMetadata,
|
||||
header: collection.sharedMagicMetadata!.header,
|
||||
}
|
||||
: undefined)!,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
RemoteCollection,
|
||||
RemotePublicURL,
|
||||
type Collection,
|
||||
type Collection2,
|
||||
type CollectionNewParticipantRole,
|
||||
type CollectionOrder,
|
||||
type CollectionPrivateMagicMetadataData,
|
||||
@@ -58,8 +57,6 @@ export const ALL_SECTION = 0;
|
||||
* See also: [Note: Multiple "default" hidden collections].
|
||||
*/
|
||||
export const isDefaultHiddenCollection = (collection: Collection) =>
|
||||
// TODO: Need to audit the types
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
collection.magicMetadata?.data.subType == CollectionSubType.defaultHidden;
|
||||
|
||||
/**
|
||||
@@ -85,8 +82,6 @@ export const isIncomingShare = (collection: Collection, user: User) =>
|
||||
collection.owner.id !== user.id;
|
||||
|
||||
export const isHiddenCollection = (collection: Collection) =>
|
||||
// TODO: Need to audit the types
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
collection.magicMetadata?.data.visibility === ItemVisibility.hidden;
|
||||
|
||||
export const DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME = "Hidden";
|
||||
@@ -152,26 +147,6 @@ export const createCollection2 = async (
|
||||
const decryptRemoteKeyAndCollection = async (collection: RemoteCollection) =>
|
||||
decryptRemoteCollection(collection, await decryptCollectionKey(collection));
|
||||
|
||||
// TODO(C2): Temporary method to convert to the newer type.
|
||||
export const collection1To2 = async (c1: Collection): Promise<Collection2> => {
|
||||
const collection = RemoteCollection.parse({
|
||||
...c1,
|
||||
magicMetadata: undefined,
|
||||
pubMagicMetadata: undefined,
|
||||
sharedMagicMetadata: undefined,
|
||||
});
|
||||
const c2 = await decryptRemoteCollection(
|
||||
collection,
|
||||
await decryptCollectionKey(collection),
|
||||
);
|
||||
return {
|
||||
...c2,
|
||||
magicMetadata: c1.magicMetadata,
|
||||
pubMagicMetadata: c1.pubMagicMetadata,
|
||||
sharedMagicMetadata: c1.sharedMagicMetadata,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the decrypted collection key (as a base64 string) for the given
|
||||
* {@link RemoteCollection}.
|
||||
@@ -505,8 +480,8 @@ export const deleteFromTrash = async (fileIDs: number[]) => {
|
||||
*
|
||||
* @param newName The new name of the collection
|
||||
*/
|
||||
export const renameCollection2 = async (
|
||||
collection: Collection2,
|
||||
export const renameCollection = async (
|
||||
collection: Collection,
|
||||
newName: string,
|
||||
) => {
|
||||
if (collection.magicMetadata?.data.subType == CollectionSubType.quicklink) {
|
||||
@@ -552,7 +527,7 @@ const postCollectionsRename = async (renameRequest: RenameRequest) =>
|
||||
* @param visibility The new visibility (normal, archived, hidden).
|
||||
*/
|
||||
export const updateCollectionVisibility = async (
|
||||
collection: Collection2,
|
||||
collection: Collection,
|
||||
visibility: ItemVisibility,
|
||||
) =>
|
||||
collection.owner.id == ensureLocalUser().id
|
||||
@@ -571,7 +546,7 @@ export const updateCollectionVisibility = async (
|
||||
* @param order Whether on not the collection is pinned.
|
||||
*/
|
||||
export const updateCollectionOrder = async (
|
||||
collection: Collection2,
|
||||
collection: Collection,
|
||||
order: CollectionOrder,
|
||||
) => updateCollectionPrivateMagicMetadata(collection, { order });
|
||||
|
||||
@@ -588,7 +563,7 @@ export const updateCollectionOrder = async (
|
||||
* Otherwise they are sorted descending (newest first).
|
||||
*/
|
||||
export const updateCollectionSortOrder = async (
|
||||
collection: Collection2,
|
||||
collection: Collection,
|
||||
asc: boolean,
|
||||
) => updateCollectionPublicMagicMetadata(collection, { asc });
|
||||
|
||||
@@ -610,7 +585,7 @@ export const updateCollectionSortOrder = async (
|
||||
* See: [Note: Magic metadata data cannot have nullish values]
|
||||
*/
|
||||
export const updateCollectionPrivateMagicMetadata = async (
|
||||
{ id, key, magicMetadata }: Collection2,
|
||||
{ id, key, magicMetadata }: Collection,
|
||||
updates: CollectionPrivateMagicMetadataData,
|
||||
) =>
|
||||
putCollectionsMagicMetadata({
|
||||
@@ -668,7 +643,7 @@ const putCollectionsMagicMetadata = async (
|
||||
* with the {@link pubMagicMetadata} of a collection.
|
||||
*/
|
||||
const updateCollectionPublicMagicMetadata = async (
|
||||
{ id, key, pubMagicMetadata }: Collection2,
|
||||
{ id, key, pubMagicMetadata }: Collection,
|
||||
updates: CollectionPublicMagicMetadataData,
|
||||
) =>
|
||||
putCollectionsPublicMagicMetadata({
|
||||
@@ -705,7 +680,7 @@ const putCollectionsPublicMagicMetadata = async (
|
||||
* with the {@link sharedMagicMetadata} of a collection.
|
||||
*/
|
||||
const updateCollectionShareeMagicMetadata = async (
|
||||
{ id, key, sharedMagicMetadata }: Collection2,
|
||||
{ id, key, sharedMagicMetadata }: Collection,
|
||||
updates: CollectionShareeMagicMetadataData,
|
||||
) =>
|
||||
putCollectionsShareeMagicMetadata({
|
||||
|
||||
@@ -24,23 +24,20 @@ import {
|
||||
CollectionPrivateMagicMetadataData,
|
||||
CollectionPublicMagicMetadataData,
|
||||
CollectionShareeMagicMetadataData,
|
||||
ignore,
|
||||
RemoteCollectionUser,
|
||||
RemotePublicURL,
|
||||
type Collection2,
|
||||
type Collection,
|
||||
} from "ente-media/collection";
|
||||
import { RemoteMagicMetadata } from "ente-media/magic-metadata";
|
||||
import localForage from "ente-shared/storage/localForage";
|
||||
import { nullishToEmpty } from "ente-utils/transform";
|
||||
import { nullishToEmpty, nullToUndefined } from "ente-utils/transform";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
/**
|
||||
* Zod schema for a {@link Collection} saved in our local persistence.
|
||||
*
|
||||
* This is similar to {@link RemoteCollection}, but has significant differences
|
||||
* too in that it contains the decrypted fields, and some minor refinements.
|
||||
* This is similar to {@link RemoteCollection}, but has both significant
|
||||
* differences in that it contains the decrypted fields, and some minor tweaks.
|
||||
*/
|
||||
// TODO(C2): Use me
|
||||
const LocalCollection = z.looseObject({
|
||||
id: z.number(),
|
||||
owner: RemoteCollectionUser,
|
||||
@@ -50,30 +47,30 @@ const LocalCollection = z.looseObject({
|
||||
sharees: z.array(RemoteCollectionUser).nullish().transform(nullishToEmpty),
|
||||
publicURLs: z.array(RemotePublicURL).nullish().transform(nullishToEmpty),
|
||||
updationTime: z.number(),
|
||||
magicMetadata: RemoteMagicMetadata.nullish().transform((mm) => {
|
||||
if (!mm) return undefined;
|
||||
// Old code used to save the header, however it's unnecessary so we drop
|
||||
// it on the next read. New code will not save it, so eventually this
|
||||
// special case can be removed. Note added Jun 2025 (tag: Migration).
|
||||
const { header, ...rest } = mm;
|
||||
ignore(header);
|
||||
const data = CollectionPrivateMagicMetadataData.parse(rest.data);
|
||||
return { ...rest, data };
|
||||
}),
|
||||
pubMagicMetadata: RemoteMagicMetadata.nullish().transform((mm) => {
|
||||
if (!mm) return undefined;
|
||||
const { header, ...rest } = mm;
|
||||
ignore(header);
|
||||
const data = CollectionPublicMagicMetadataData.parse(rest.data);
|
||||
return { ...rest, data };
|
||||
}),
|
||||
sharedMagicMetadata: RemoteMagicMetadata.nullish().transform((mm) => {
|
||||
if (!mm) return undefined;
|
||||
const { header, ...rest } = mm;
|
||||
ignore(header);
|
||||
const data = CollectionShareeMagicMetadataData.parse(rest.data);
|
||||
return { ...rest, data };
|
||||
}),
|
||||
magicMetadata: z
|
||||
.object({
|
||||
version: z.number(),
|
||||
count: z.number(),
|
||||
data: CollectionPrivateMagicMetadataData,
|
||||
})
|
||||
.nullish()
|
||||
.transform(nullToUndefined),
|
||||
pubMagicMetadata: z
|
||||
.object({
|
||||
version: z.number(),
|
||||
count: z.number(),
|
||||
data: CollectionPublicMagicMetadataData,
|
||||
})
|
||||
.nullish()
|
||||
.transform(nullToUndefined),
|
||||
sharedMagicMetadata: z
|
||||
.object({
|
||||
version: z.number(),
|
||||
count: z.number(),
|
||||
data: CollectionShareeMagicMetadataData,
|
||||
})
|
||||
.nullish()
|
||||
.transform(nullToUndefined),
|
||||
});
|
||||
|
||||
const LocalCollections = z.array(LocalCollection);
|
||||
@@ -85,7 +82,7 @@ const LocalCollections = z.array(LocalCollection);
|
||||
*
|
||||
* Use {@link saveCollections} to update the database.
|
||||
*/
|
||||
export const savedCollections = async (): Promise<Collection2[]> =>
|
||||
export const savedCollections = async (): Promise<Collection[]> =>
|
||||
LocalCollections.parse(await localForage.getItem("collections"));
|
||||
|
||||
/**
|
||||
@@ -95,5 +92,5 @@ export const savedCollections = async (): Promise<Collection2[]> =>
|
||||
* collections (the split between normal and hidden is not at the database level
|
||||
* but is a filter when they are accessed).
|
||||
*/
|
||||
export const saveCollections = (collections: Collection2[]) =>
|
||||
export const saveCollections = (collections: Collection[]) =>
|
||||
localForage.setItem("collections", collections);
|
||||
|
||||
Reference in New Issue
Block a user