[web] Collection APIs refactoring (#6272)
This commit is contained in:
@@ -29,7 +29,7 @@ import {
|
||||
isArchivedCollection,
|
||||
isPinnedCollection,
|
||||
} from "ente-gallery/services/magic-metadata";
|
||||
import type { Collection } from "ente-media/collection";
|
||||
import { CollectionOrder, type Collection } from "ente-media/collection";
|
||||
import { ItemVisibility } from "ente-media/file-metadata";
|
||||
import {
|
||||
GalleryItemsHeaderAdapter,
|
||||
@@ -134,7 +134,7 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
const { showMiniDialog, onGenericError } = useBaseContext();
|
||||
const { showLoadingBar, hideLoadingBar } = usePhotosAppContext();
|
||||
const { syncWithRemote } = useContext(GalleryContext);
|
||||
const overFlowMenuIconRef = useRef<SVGSVGElement>(null);
|
||||
const overflowMenuIconRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
const { show: showSortOrderMenu, props: sortOrderMenuVisibilityProps } =
|
||||
useModalVisibility();
|
||||
@@ -277,9 +277,13 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
setActiveCollectionID(ALL_SECTION);
|
||||
});
|
||||
|
||||
const pinAlbum = wrap(() => changeCollectionOrder(activeCollection, 1));
|
||||
const pinAlbum = wrap(() =>
|
||||
changeCollectionOrder(activeCollection, CollectionOrder.pinned),
|
||||
);
|
||||
|
||||
const unpinAlbum = wrap(() => changeCollectionOrder(activeCollection, 0));
|
||||
const unpinAlbum = wrap(() =>
|
||||
changeCollectionOrder(activeCollection, CollectionOrder.default),
|
||||
);
|
||||
|
||||
const hideAlbum = wrap(async () => {
|
||||
await changeCollectionVisibility(
|
||||
@@ -506,13 +510,13 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
|
||||
<OverflowMenu
|
||||
ariaID="collection-options"
|
||||
triggerButtonIcon={<MoreHorizIcon ref={overFlowMenuIconRef} />}
|
||||
triggerButtonIcon={<MoreHorizIcon ref={overflowMenuIconRef} />}
|
||||
>
|
||||
{...menuOptions}
|
||||
</OverflowMenu>
|
||||
<CollectionSortOrderMenu
|
||||
{...sortOrderMenuVisibilityProps}
|
||||
overFlowMenuIconRef={overFlowMenuIconRef}
|
||||
overflowMenuIconRef={overflowMenuIconRef}
|
||||
onAscClick={changeSortOrderAsc}
|
||||
onDescClick={changeSortOrderDesc}
|
||||
/>
|
||||
@@ -693,7 +697,7 @@ const DownloadOption: React.FC<
|
||||
interface CollectionSortOrderMenuProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
overFlowMenuIconRef: React.RefObject<SVGSVGElement>;
|
||||
overflowMenuIconRef: React.RefObject<SVGSVGElement>;
|
||||
onAscClick: () => void;
|
||||
onDescClick: () => void;
|
||||
}
|
||||
@@ -701,24 +705,24 @@ interface CollectionSortOrderMenuProps {
|
||||
const CollectionSortOrderMenu: React.FC<CollectionSortOrderMenuProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
overFlowMenuIconRef,
|
||||
overflowMenuIconRef,
|
||||
onAscClick,
|
||||
onDescClick,
|
||||
}) => {
|
||||
const handleAscClick = () => {
|
||||
onClose();
|
||||
onAscClick();
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleDescClick = () => {
|
||||
onClose();
|
||||
onDescClick();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Menu
|
||||
id="collection-files-sort"
|
||||
anchorEl={overFlowMenuIconRef.current}
|
||||
anchorEl={overflowMenuIconRef.current}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
slotProps={{
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { ensureLocalUser } from "ente-accounts/services/user";
|
||||
import { encryptMetadataJSON, sharedCryptoWorker } from "ente-base/crypto";
|
||||
import { sharedCryptoWorker } from "ente-base/crypto";
|
||||
import { isDevBuild } from "ente-base/env";
|
||||
import log from "ente-base/log";
|
||||
import { apiURL } from "ente-base/origins";
|
||||
import { ensureMasterKeyFromSession } from "ente-base/session";
|
||||
import { UpdateMagicMetadataRequest } from "ente-gallery/services/file";
|
||||
import { updateMagicMetadata } from "ente-gallery/services/magic-metadata";
|
||||
import {
|
||||
Collection,
|
||||
CollectionMagicMetadata,
|
||||
CollectionMagicMetadataProps,
|
||||
CollectionPublicMagicMetadata,
|
||||
CollectionSubType,
|
||||
type CollectionType,
|
||||
EncryptedCollection,
|
||||
@@ -45,11 +42,7 @@ import HTTPService from "ente-shared/network/HTTPService";
|
||||
import { getData } from "ente-shared/storage/localStorage";
|
||||
import { getToken } from "ente-shared/storage/localStorage/helpers";
|
||||
import { batch } from "ente-utils/array";
|
||||
import {
|
||||
changeCollectionSubType,
|
||||
isQuickLinkCollection,
|
||||
isValidMoveTarget,
|
||||
} from "utils/collection";
|
||||
import { isValidMoveTarget } from "utils/collection";
|
||||
|
||||
const uncategorizedCollectionName = "Uncategorized";
|
||||
const defaultHiddenCollectionName = ".hidden";
|
||||
@@ -349,159 +342,10 @@ export const deleteCollection = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const updateCollectionMagicMetadata = async (
|
||||
collection: Collection,
|
||||
updatedMagicMetadata: CollectionMagicMetadata,
|
||||
) => {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { encryptedData, decryptionHeader } = await encryptMetadataJSON(
|
||||
updatedMagicMetadata.data,
|
||||
collection.key,
|
||||
);
|
||||
|
||||
const reqBody: UpdateMagicMetadataRequest = {
|
||||
id: collection.id,
|
||||
magicMetadata: {
|
||||
version: updatedMagicMetadata.version,
|
||||
count: updatedMagicMetadata.count,
|
||||
data: encryptedData,
|
||||
header: decryptionHeader,
|
||||
},
|
||||
};
|
||||
|
||||
await HTTPService.put(
|
||||
await apiURL("/collections/magic-metadata"),
|
||||
reqBody,
|
||||
null,
|
||||
{ "X-Auth-Token": token },
|
||||
);
|
||||
const updatedCollection: Collection = {
|
||||
...collection,
|
||||
magicMetadata: {
|
||||
...updatedMagicMetadata,
|
||||
version: updatedMagicMetadata.version + 1,
|
||||
},
|
||||
};
|
||||
return updatedCollection;
|
||||
};
|
||||
|
||||
export const updateSharedCollectionMagicMetadata = async (
|
||||
collection: Collection,
|
||||
updatedMagicMetadata: CollectionMagicMetadata,
|
||||
) => {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { encryptedData, decryptionHeader } = await encryptMetadataJSON(
|
||||
updatedMagicMetadata.data,
|
||||
collection.key,
|
||||
);
|
||||
const reqBody: UpdateMagicMetadataRequest = {
|
||||
id: collection.id,
|
||||
magicMetadata: {
|
||||
version: updatedMagicMetadata.version,
|
||||
count: updatedMagicMetadata.count,
|
||||
data: encryptedData,
|
||||
header: decryptionHeader,
|
||||
},
|
||||
};
|
||||
|
||||
await HTTPService.put(
|
||||
await apiURL("/collections/sharee-magic-metadata"),
|
||||
reqBody,
|
||||
null,
|
||||
{ "X-Auth-Token": token },
|
||||
);
|
||||
const updatedCollection: Collection = {
|
||||
...collection,
|
||||
magicMetadata: {
|
||||
...updatedMagicMetadata,
|
||||
version: updatedMagicMetadata.version + 1,
|
||||
},
|
||||
};
|
||||
return updatedCollection;
|
||||
};
|
||||
|
||||
export const updatePublicCollectionMagicMetadata = async (
|
||||
collection: Collection,
|
||||
updatedPublicMagicMetadata: CollectionPublicMagicMetadata,
|
||||
) => {
|
||||
const token = getToken();
|
||||
if (!token) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { encryptedData, decryptionHeader } = await encryptMetadataJSON(
|
||||
updatedPublicMagicMetadata.data,
|
||||
collection.key,
|
||||
);
|
||||
const reqBody: UpdateMagicMetadataRequest = {
|
||||
id: collection.id,
|
||||
magicMetadata: {
|
||||
version: updatedPublicMagicMetadata.version,
|
||||
count: updatedPublicMagicMetadata.count,
|
||||
data: encryptedData,
|
||||
header: decryptionHeader,
|
||||
},
|
||||
};
|
||||
|
||||
await HTTPService.put(
|
||||
await apiURL("/collections/public-magic-metadata"),
|
||||
reqBody,
|
||||
null,
|
||||
{ "X-Auth-Token": token },
|
||||
);
|
||||
const updatedCollection: Collection = {
|
||||
...collection,
|
||||
pubMagicMetadata: {
|
||||
...updatedPublicMagicMetadata,
|
||||
version: updatedPublicMagicMetadata.version + 1,
|
||||
},
|
||||
};
|
||||
return updatedCollection;
|
||||
};
|
||||
|
||||
export const renameCollection = async (
|
||||
collection: Collection,
|
||||
newCollectionName: string,
|
||||
) =>
|
||||
enableC2()
|
||||
? renameCollection2(await collection1To2(collection), newCollectionName)
|
||||
: renameCollection1(collection, newCollectionName);
|
||||
|
||||
const renameCollection1 = async (
|
||||
collection: Collection,
|
||||
newCollectionName: string,
|
||||
) => {
|
||||
if (isQuickLinkCollection(collection)) {
|
||||
// Convert quick link collection to normal collection on rename
|
||||
await changeCollectionSubType(collection, CollectionSubType.default);
|
||||
}
|
||||
const token = getToken();
|
||||
const cryptoWorker = await sharedCryptoWorker();
|
||||
const { encryptedData: encryptedName, nonce: nameDecryptionNonce } =
|
||||
await cryptoWorker.encryptBox(
|
||||
new TextEncoder().encode(newCollectionName),
|
||||
collection.key,
|
||||
);
|
||||
const collectionRenameRequest = {
|
||||
collectionID: collection.id,
|
||||
encryptedName,
|
||||
nameDecryptionNonce,
|
||||
};
|
||||
await HTTPService.post(
|
||||
await apiURL("/collections/rename"),
|
||||
collectionRenameRequest,
|
||||
null,
|
||||
{ "X-Auth-Token": token },
|
||||
);
|
||||
};
|
||||
) => renameCollection2(await collection1To2(collection), newCollectionName);
|
||||
|
||||
/**
|
||||
* Return the user's own favorites collection, if any.
|
||||
|
||||
@@ -2,11 +2,9 @@ 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 { updateMagicMetadata } from "ente-gallery/services/magic-metadata";
|
||||
import {
|
||||
type Collection,
|
||||
CollectionMagicMetadataProps,
|
||||
CollectionPublicMagicMetadataProps,
|
||||
type CollectionOrder,
|
||||
CollectionSubType,
|
||||
} from "ente-media/collection";
|
||||
import { EnteFile } from "ente-media/file";
|
||||
@@ -15,11 +13,15 @@ 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,
|
||||
@@ -35,9 +37,6 @@ import {
|
||||
createAlbum,
|
||||
removeFromCollection,
|
||||
unhideToCollection,
|
||||
updateCollectionMagicMetadata,
|
||||
updatePublicCollectionMagicMetadata,
|
||||
updateSharedCollectionMagicMetadata,
|
||||
} from "services/collectionService";
|
||||
import {
|
||||
SetFilesDownloadProgressAttributes,
|
||||
@@ -185,105 +184,17 @@ async function createCollectionDownloadFolder(
|
||||
export const changeCollectionVisibility = async (
|
||||
collection: Collection,
|
||||
visibility: ItemVisibility,
|
||||
) => {
|
||||
try {
|
||||
const updatedMagicMetadataProps: CollectionMagicMetadataProps = {
|
||||
visibility,
|
||||
};
|
||||
) => updateCollectionVisibility(await collection1To2(collection), visibility);
|
||||
|
||||
const user: User = getData("user");
|
||||
if (collection.owner.id === user.id) {
|
||||
const updatedMagicMetadata = await updateMagicMetadata(
|
||||
updatedMagicMetadataProps,
|
||||
collection.magicMetadata,
|
||||
collection.key,
|
||||
);
|
||||
|
||||
await updateCollectionMagicMetadata(
|
||||
collection,
|
||||
updatedMagicMetadata,
|
||||
);
|
||||
} else {
|
||||
const updatedMagicMetadata = await updateMagicMetadata(
|
||||
updatedMagicMetadataProps,
|
||||
collection.sharedMagicMetadata,
|
||||
collection.key,
|
||||
);
|
||||
await updateSharedCollectionMagicMetadata(
|
||||
collection,
|
||||
updatedMagicMetadata,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
log.error("change collection visibility failed", e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
export const changeCollectionOrder = async (
|
||||
collection: Collection,
|
||||
order: CollectionOrder,
|
||||
) => updateCollectionOrder(await collection1To2(collection), order);
|
||||
|
||||
export const changeCollectionSortOrder = async (
|
||||
collection: Collection,
|
||||
asc: boolean,
|
||||
) => {
|
||||
try {
|
||||
const updatedPublicMagicMetadataProps: CollectionPublicMagicMetadataProps =
|
||||
{ asc };
|
||||
|
||||
const updatedPubMagicMetadata = await updateMagicMetadata(
|
||||
updatedPublicMagicMetadataProps,
|
||||
collection.pubMagicMetadata,
|
||||
collection.key,
|
||||
);
|
||||
|
||||
await updatePublicCollectionMagicMetadata(
|
||||
collection,
|
||||
updatedPubMagicMetadata,
|
||||
);
|
||||
} catch (e) {
|
||||
log.error("change collection sort order failed", e);
|
||||
}
|
||||
};
|
||||
|
||||
export const changeCollectionOrder = async (
|
||||
collection: Collection,
|
||||
order: number,
|
||||
) => {
|
||||
try {
|
||||
const updatedMagicMetadataProps: CollectionMagicMetadataProps = {
|
||||
order,
|
||||
};
|
||||
|
||||
const updatedMagicMetadata = await updateMagicMetadata(
|
||||
updatedMagicMetadataProps,
|
||||
collection.magicMetadata,
|
||||
collection.key,
|
||||
);
|
||||
|
||||
await updateCollectionMagicMetadata(collection, updatedMagicMetadata);
|
||||
} catch (e) {
|
||||
log.error("change collection order failed", e);
|
||||
}
|
||||
};
|
||||
|
||||
export const changeCollectionSubType = async (
|
||||
collection: Collection,
|
||||
subType: CollectionSubType,
|
||||
) => {
|
||||
try {
|
||||
const updatedMagicMetadataProps: CollectionMagicMetadataProps = {
|
||||
subType: subType,
|
||||
};
|
||||
|
||||
const updatedMagicMetadata = await updateMagicMetadata(
|
||||
updatedMagicMetadataProps,
|
||||
collection.magicMetadata,
|
||||
collection.key,
|
||||
);
|
||||
await updateCollectionMagicMetadata(collection, updatedMagicMetadata);
|
||||
} catch (e) {
|
||||
log.error("change collection subType failed", e);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
) => updateCollectionSortOrder(await collection1To2(collection), asc);
|
||||
|
||||
export const getUserOwnedCollections = (collections: Collection[]) => {
|
||||
const user: User = getData("user");
|
||||
@@ -293,7 +204,7 @@ export const getUserOwnedCollections = (collections: Collection[]) => {
|
||||
return collections.filter((collection) => collection.owner.id === user.id);
|
||||
};
|
||||
|
||||
export const isQuickLinkCollection = (collection: Collection) =>
|
||||
const isQuickLinkCollection = (collection: Collection) =>
|
||||
collection.magicMetadata?.data.subType == CollectionSubType.quicklink;
|
||||
|
||||
export function isIncomingViewerShare(collection: Collection, user: User) {
|
||||
|
||||
@@ -118,11 +118,16 @@ interface OverflowMenuOptionProps {
|
||||
export const OverflowMenuOption: React.FC<
|
||||
React.PropsWithChildren<OverflowMenuOptionProps>
|
||||
> = ({ onClick, color = "primary", startIcon, endIcon, children }) => {
|
||||
const menuContext = useContext(OverflowMenuContext)!;
|
||||
const menuContext = useContext(OverflowMenuContext);
|
||||
|
||||
const handleClick = () => {
|
||||
onClick();
|
||||
menuContext.close();
|
||||
// We might've already been closed as a result of our containing menu
|
||||
// getting closed. An example of this is the "Sort by" option in the
|
||||
// album options overflow menu, where the `onClick` above will result in
|
||||
// `onClose` being called on our parent menu, so `menuContext` will be
|
||||
// undefined when we get here.
|
||||
menuContext?.close();
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -108,7 +108,9 @@ export interface Collection2 {
|
||||
pubMagicMetadata?: MagicMetadata<CollectionPublicMagicMetadataData>;
|
||||
/**
|
||||
* Private mutable metadata associated with the collection that is only
|
||||
* visible to the current user, if they're not the owner.
|
||||
* visible to the current user if they're not the owner of the collection.
|
||||
*
|
||||
* Sometimes also referred to as "shareeMagicMetadata".
|
||||
*
|
||||
* This is metadata associated with each "share", and is only visible to
|
||||
* (and editable by) the user with which the collection has been shared, not
|
||||
@@ -387,10 +389,14 @@ export const RemoteCollection = z.looseObject({
|
||||
owner: RemoteCollectionUser,
|
||||
encryptedKey: z.string(),
|
||||
/**
|
||||
* Remote will set this to a blank string for albums which have been shared
|
||||
* with the user (the decryption pipeline for those doesn't use the nonce).
|
||||
* The nonce to use when decrypting the {@link encryptedKey} when the album
|
||||
* is owned by the user.
|
||||
*
|
||||
* Not set for shared albums (the decryption for uses the keypair instead).
|
||||
*
|
||||
* Remote might set this to blank to indicate absence.
|
||||
*/
|
||||
keyDecryptionNonce: z.string(),
|
||||
keyDecryptionNonce: z.string().nullish().transform(nullToUndefined),
|
||||
/**
|
||||
* Expected to be present (along with {@link nameDecryptionNonce}), but it
|
||||
* is still optional since it might not be present if {@link name} is present.
|
||||
@@ -631,6 +637,29 @@ export const CollectionSubType = {
|
||||
export type CollectionSubType =
|
||||
(typeof CollectionSubType)[keyof typeof CollectionSubType];
|
||||
|
||||
/**
|
||||
* Ordering of the collection - Whether it is pinned or not.
|
||||
*/
|
||||
export const CollectionOrder = {
|
||||
/**
|
||||
* The default / normal value. No special semantics, behaves "unpinned" and
|
||||
* will retain its natural sort position.
|
||||
*/
|
||||
default: 0,
|
||||
/**
|
||||
* The collection is "pinned" by moving to the beginning of the sort order.
|
||||
*
|
||||
* Multiple collections can be pinned, in which case they'll be sorted
|
||||
* amongst themselves under the otherwise applicable sort order.
|
||||
*
|
||||
* -- [pinned collections] -- [other collections] --
|
||||
*/
|
||||
pinned: 1,
|
||||
} as const;
|
||||
|
||||
export type CollectionOrder =
|
||||
(typeof CollectionOrder)[keyof typeof CollectionOrder];
|
||||
|
||||
/**
|
||||
* Mutable private metadata associated with a {@link Collection}.
|
||||
*
|
||||
@@ -640,6 +669,12 @@ export type CollectionSubType =
|
||||
* See: [Note: Private magic metadata is called magic metadata on remote]
|
||||
*/
|
||||
export interface CollectionPrivateMagicMetadataData {
|
||||
/**
|
||||
* The subtype of the collection type (if applicable).
|
||||
*
|
||||
* Expected to be one of {@link CollectionSubType}.
|
||||
*/
|
||||
subType?: number;
|
||||
/**
|
||||
* The (owner specific) visibility of the collection.
|
||||
*
|
||||
@@ -654,20 +689,10 @@ export interface CollectionPrivateMagicMetadataData {
|
||||
* instead of the expected enum.
|
||||
*/
|
||||
visibility?: number;
|
||||
/**
|
||||
* The subtype of the collection type (if applicable).
|
||||
*
|
||||
* Expected to be one of {@link CollectionSubType}.
|
||||
*/
|
||||
subType?: number;
|
||||
/**
|
||||
* An overrride to the sort ordering used for the collection.
|
||||
*
|
||||
* - For pinned collections, this will be set to `1`. Pinned collections
|
||||
* will be moved to the beginning of the sort order.
|
||||
*
|
||||
* - Otherwise, the collection is a normal (unpinned) collection, and will
|
||||
* retain its natural sort position.
|
||||
* Expected to be one of {@link CollectionOrder}.
|
||||
*/
|
||||
order?: number;
|
||||
}
|
||||
@@ -678,8 +703,8 @@ export interface CollectionPrivateMagicMetadataData {
|
||||
* See: [Note: Use looseObject for metadata Zod schemas]
|
||||
*/
|
||||
const CollectionPrivateMagicMetadataData = z.looseObject({
|
||||
visibility: z.number().nullish().transform(nullToUndefined),
|
||||
subType: z.number().nullish().transform(nullToUndefined),
|
||||
visibility: z.number().nullish().transform(nullToUndefined),
|
||||
order: z.number().nullish().transform(nullToUndefined),
|
||||
});
|
||||
|
||||
@@ -693,10 +718,14 @@ const CollectionPrivateMagicMetadataData = z.looseObject({
|
||||
*/
|
||||
export interface CollectionPublicMagicMetadataData {
|
||||
/**
|
||||
* The ordering of the files within the collection.
|
||||
*
|
||||
* The default is desc ("Newest first").
|
||||
*
|
||||
* If true, then the files within the collection are sorted in ascending
|
||||
* order of their time ("Oldest first").
|
||||
*
|
||||
* The default is desc ("Newest first").
|
||||
* To reset to the default, set this to false.
|
||||
*/
|
||||
asc?: boolean;
|
||||
/**
|
||||
|
||||
@@ -17,7 +17,10 @@ import {
|
||||
type Collection,
|
||||
type Collection2,
|
||||
type CollectionNewParticipantRole,
|
||||
type CollectionOrder,
|
||||
type CollectionPrivateMagicMetadataData,
|
||||
type CollectionPublicMagicMetadataData,
|
||||
type CollectionShareeMagicMetadataData,
|
||||
type CollectionType,
|
||||
type PublicURL,
|
||||
} from "ente-media/collection";
|
||||
@@ -176,9 +179,9 @@ export const decryptCollectionKey = async (
|
||||
const { owner, encryptedKey, keyDecryptionNonce } = collection;
|
||||
if (owner.id == ensureLocalUser().id) {
|
||||
// The collection key of collections owned by the user is encrypted with
|
||||
// the user's master key.
|
||||
// the user's master key. The nonce will be present in such cases.
|
||||
return decryptBox(
|
||||
{ encryptedData: encryptedKey, nonce: keyDecryptionNonce },
|
||||
{ encryptedData: encryptedKey, nonce: keyDecryptionNonce! },
|
||||
await ensureMasterKeyFromSession(),
|
||||
);
|
||||
} else {
|
||||
@@ -436,15 +439,70 @@ const postCollectionsRename = async (renameRequest: RenameRequest) =>
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Change the visibility (normal, archived, hidden) of a collection on remote.
|
||||
*
|
||||
* Remote only, does not modify local state.
|
||||
*
|
||||
* This function works with both collections owned by the user, and collections
|
||||
* shared with the user.
|
||||
*
|
||||
* @param collection The collection whose visibility we want to change.
|
||||
*
|
||||
* @param visibility The new visibility (normal, archived, hidden).
|
||||
*/
|
||||
export const updateCollectionVisibility = async (
|
||||
collection: Collection2,
|
||||
visibility: ItemVisibility,
|
||||
) =>
|
||||
collection.owner.id == ensureLocalUser().id
|
||||
? updateCollectionPrivateMagicMetadata(collection, { visibility })
|
||||
: updateCollectionShareeMagicMetadata(collection, { visibility });
|
||||
|
||||
/**
|
||||
* Change the pinned state of a collection on remote.
|
||||
*
|
||||
* Remote only, does not modify local state.
|
||||
*
|
||||
* This function works only for collections owned by the user.
|
||||
*
|
||||
* @param collection The collection whose order we want to change.
|
||||
*
|
||||
* @param order Whether on not the collection is pinned.
|
||||
*/
|
||||
export const updateCollectionOrder = async (
|
||||
collection: Collection2,
|
||||
order: CollectionOrder,
|
||||
) => updateCollectionPrivateMagicMetadata(collection, { order });
|
||||
|
||||
/**
|
||||
* Change the sort order of the files with a collection on remote.
|
||||
*
|
||||
* Remote only, does not modify local state.
|
||||
*
|
||||
* This function works only for collections owned by the user.
|
||||
*
|
||||
* @param collection The collection whose file sort order we want to change.
|
||||
*
|
||||
* @param asc If true, then the files are sorted ascending (oldest first).
|
||||
* Otherwise they are sorted descending (newest first).
|
||||
*/
|
||||
export const updateCollectionSortOrder = async (
|
||||
collection: Collection2,
|
||||
asc: boolean,
|
||||
) => updateCollectionPublicMagicMetadata(collection, { asc });
|
||||
|
||||
/**
|
||||
* Update the private magic metadata contents of a collection on remote.
|
||||
*
|
||||
* Remote only, does not modify local state.
|
||||
*
|
||||
* @param collection The collection whose magic metadata we want to update. In
|
||||
* particular, the existing magic metadata of this collection is used both to
|
||||
* obtain the current magic metadata version, and the existing contents on top
|
||||
* of which the updates are applied.
|
||||
* @param collection The collection whose magic metadata we want to update.
|
||||
*
|
||||
* The existing magic metadata of this collection is used both to obtain the
|
||||
* current magic metadata version, and the existing contents on top of which the
|
||||
* updates are applied, so it is imperative that both these values are up to
|
||||
* sync with remote otherwise the update will fail.
|
||||
*
|
||||
* @param updates A non-empty subset of
|
||||
* {@link CollectionPrivateMagicMetadataData} entries.
|
||||
@@ -501,6 +559,80 @@ const putCollectionsMagicMetadata = async (
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Update the public magic metadata contents of a collection on remote.
|
||||
*
|
||||
* Remote only, does not modify local state.
|
||||
*
|
||||
* This is a variant of {@link updateCollectionPrivateMagicMetadata} that works
|
||||
* with the {@link pubMagicMetadata} of a collection.
|
||||
*/
|
||||
const updateCollectionPublicMagicMetadata = async (
|
||||
{ id, key, pubMagicMetadata }: Collection2,
|
||||
updates: CollectionPublicMagicMetadataData,
|
||||
) =>
|
||||
putCollectionsPublicMagicMetadata({
|
||||
id,
|
||||
magicMetadata: await encryptMagicMetadata(
|
||||
createMagicMetadata(
|
||||
{ ...pubMagicMetadata?.data, ...updates },
|
||||
pubMagicMetadata?.version,
|
||||
),
|
||||
key,
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* Update the public magic metadata of a single collection on remote.
|
||||
*/
|
||||
const putCollectionsPublicMagicMetadata = async (
|
||||
updateRequest: UpdateCollectionMagicMetadataRequest,
|
||||
) =>
|
||||
ensureOk(
|
||||
await fetch(await apiURL("/collections/public-magic-metadata"), {
|
||||
method: "PUT",
|
||||
headers: await authenticatedRequestHeaders(),
|
||||
body: JSON.stringify(updateRequest),
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Update the per-sharee magic metadata contents of a collection on remote.
|
||||
*
|
||||
* Remote only, does not modify local state.
|
||||
*
|
||||
* This is a variant of {@link updateCollectionPrivateMagicMetadata} that works
|
||||
* with the {@link sharedMagicMetadata} of a collection.
|
||||
*/
|
||||
const updateCollectionShareeMagicMetadata = async (
|
||||
{ id, key, sharedMagicMetadata }: Collection2,
|
||||
updates: CollectionShareeMagicMetadataData,
|
||||
) =>
|
||||
putCollectionsShareeMagicMetadata({
|
||||
id,
|
||||
magicMetadata: await encryptMagicMetadata(
|
||||
createMagicMetadata(
|
||||
{ ...sharedMagicMetadata?.data, ...updates },
|
||||
sharedMagicMetadata?.version,
|
||||
),
|
||||
key,
|
||||
),
|
||||
});
|
||||
|
||||
/**
|
||||
* Update the sharee magic metadata of a single shared collection on remote.
|
||||
*/
|
||||
const putCollectionsShareeMagicMetadata = async (
|
||||
updateRequest: UpdateCollectionMagicMetadataRequest,
|
||||
) =>
|
||||
ensureOk(
|
||||
await fetch(await apiURL("/collections/sharee-magic-metadata"), {
|
||||
method: "PUT",
|
||||
headers: await authenticatedRequestHeaders(),
|
||||
body: JSON.stringify(updateRequest),
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Share the provided collection with another Ente user.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user