diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx
index 06a4f51175..9c0502d46a 100644
--- a/web/apps/auth/src/pages/auth.tsx
+++ b/web/apps/auth/src/pages/auth.tsx
@@ -16,7 +16,6 @@ import {
} from "@ente/shared/components/OverflowMenu";
import { AUTH_PAGES as PAGES } from "@ente/shared/constants/pages";
import LogoutOutlined from "@mui/icons-material/LogoutOutlined";
-import MoreHoriz from "@mui/icons-material/MoreHoriz";
import {
Button,
ButtonBase,
@@ -155,10 +154,7 @@ const AuthNavbar: React.FC = () => {
- }
- >
+
}
diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx
index ee5787a8de..3077cf341c 100644
--- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx
+++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx
@@ -330,7 +330,7 @@ const CollectionOptions: React.FC = ({
/>
}
>
{collectionSummaryType == "trash" ? (
diff --git a/web/apps/photos/src/components/Export.tsx b/web/apps/photos/src/components/Export.tsx
index 43418b73e6..2f6f4b71d9 100644
--- a/web/apps/photos/src/components/Export.tsx
+++ b/web/apps/photos/src/components/Export.tsx
@@ -18,7 +18,6 @@ import {
} from "@ente/shared/components/OverflowMenu";
import { CustomError } from "@ente/shared/error";
import FolderIcon from "@mui/icons-material/Folder";
-import MoreHoriz from "@mui/icons-material/MoreHoriz";
import {
Box,
Button,
@@ -269,11 +268,7 @@ const DirectoryPathContainer = styled(LinkButton)(
);
const ChangeDirectoryOption: React.FC = ({ onClick }) => (
- }
- >
+
}>
{t("CHANGE_FOLDER")}
diff --git a/web/apps/photos/src/components/PhotoList/index.tsx b/web/apps/photos/src/components/PhotoList/index.tsx
index a6dfaa3a5a..f6a3aeee1f 100644
--- a/web/apps/photos/src/components/PhotoList/index.tsx
+++ b/web/apps/photos/src/components/PhotoList/index.tsx
@@ -325,7 +325,7 @@ export function PhotoList({
timeStampList.push(getEmptyListItem());
}
timeStampList.push(getVacuumItem(timeStampList));
- if (publicCollectionGalleryContext.accessedThroughSharedURL) {
+ if (publicCollectionGalleryContext.credentials) {
if (publicCollectionGalleryContext.photoListFooter) {
timeStampList.push(
getPhotoListFooter(
@@ -396,7 +396,7 @@ export function PhotoList({
if (hasFooter) {
return timeStampList;
}
- if (publicCollectionGalleryContext.accessedThroughSharedURL) {
+ if (publicCollectionGalleryContext.credentials) {
if (publicCollectionGalleryContext.photoListFooter) {
return [
...timeStampList,
@@ -413,7 +413,7 @@ export function PhotoList({
}
});
}, [
- publicCollectionGalleryContext.accessedThroughSharedURL,
+ publicCollectionGalleryContext.credentials,
showAppDownloadBanner,
publicCollectionGalleryContext.photoListFooter,
]);
@@ -521,7 +521,7 @@ export function PhotoList({
const getVacuumItem = (timeStampList) => {
let footerHeight;
- if (publicCollectionGalleryContext.accessedThroughSharedURL) {
+ if (publicCollectionGalleryContext.credentials) {
footerHeight = publicCollectionGalleryContext.referralCode
? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL
: ALBUM_FOOTER_HEIGHT;
diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx
index df4a4ab0a1..f140a3b05c 100644
--- a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx
+++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx
@@ -213,7 +213,7 @@ export const FileInfo: React.FC = ({
title={t("location")}
caption={
!mapEnabled ||
- publicCollectionGalleryContext.accessedThroughSharedURL ? (
+ publicCollectionGalleryContext.credentials ? (
= ({
/>
}
/>
- {!publicCollectionGalleryContext.accessedThroughSharedURL && (
+ {!publicCollectionGalleryContext.credentials && (
= ({
if (
open &&
directlyShowUploadFiles &&
- publicCollectionGalleryContext.accessedThroughSharedURL
+ publicCollectionGalleryContext.credentials
) {
uploadFiles();
onClose();
diff --git a/web/apps/photos/src/components/Upload/Uploader.tsx b/web/apps/photos/src/components/Upload/Uploader.tsx
index 505fd992ae..1c3c17e108 100644
--- a/web/apps/photos/src/components/Upload/Uploader.tsx
+++ b/web/apps/photos/src/components/Upload/Uploader.tsx
@@ -249,7 +249,7 @@ export default function Uploader({
setUploadProgressView,
},
onUploadFile,
- publicCollectionGalleryContext,
+ publicCollectionGalleryContext.credentials,
);
if (uploadManager.isUploadRunning()) {
@@ -288,11 +288,7 @@ export default function Uploader({
setDesktopZipItems(zipItems);
});
}
- }, [
- publicCollectionGalleryContext.accessedThroughSharedURL,
- publicCollectionGalleryContext.token,
- publicCollectionGalleryContext.passwordToken,
- ]);
+ }, [publicCollectionGalleryContext.credentials]);
// Handle selected files when user selects files for upload through the open
// file / open folder selection dialog, or drag-and-drops them.
@@ -417,10 +413,10 @@ export default function Uploader({
props.setLoading(false);
(async () => {
- if (publicCollectionGalleryContext.accessedThroughSharedURL) {
+ if (publicCollectionGalleryContext.credentials) {
const uploaderName = await getPublicCollectionUploaderName(
getPublicCollectionUID(
- publicCollectionGalleryContext.token,
+ publicCollectionGalleryContext.credentials.accessToken,
),
);
uploaderNameRef.current = uploaderName;
@@ -727,7 +723,7 @@ export default function Uploader({
if (!skipSave) {
savePublicCollectionUploaderName(
getPublicCollectionUID(
- publicCollectionGalleryContext.token,
+ publicCollectionGalleryContext.credentials.accessToken,
),
uploaderName,
);
diff --git a/web/apps/photos/src/components/WatchFolder.tsx b/web/apps/photos/src/components/WatchFolder.tsx
index eec2309a24..c80d336412 100644
--- a/web/apps/photos/src/components/WatchFolder.tsx
+++ b/web/apps/photos/src/components/WatchFolder.tsx
@@ -23,7 +23,6 @@ import CheckIcon from "@mui/icons-material/Check";
import DoNotDisturbOutlinedIcon from "@mui/icons-material/DoNotDisturbOutlined";
import FolderCopyOutlinedIcon from "@mui/icons-material/FolderCopyOutlined";
import FolderOpenIcon from "@mui/icons-material/FolderOpen";
-import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
import {
Box,
Button,
@@ -311,14 +310,13 @@ interface EntryOptionsProps {
const EntryOptions: React.FC = ({ confirmStopWatching }) => {
return (
theme.colors.background.elevated2,
},
}}
- ariaControls={"watch-mapping-option"}
- triggerButtonIcon={}
>
setShowNavBar(show);
-
const onGenericError = useCallback((e: unknown) => {
log.error(e);
// The generic error handler is sometimes called in the context of
@@ -172,21 +170,30 @@ export default function App({ Component, pageProps }: AppProps) {
const logout = useCallback(() => void photosLogout(), []);
- const appContext = {
- showNavBar,
- showLoadingBar,
- hideLoadingBar,
- watchFolderView,
- setWatchFolderView,
- watchFolderFiles,
- setWatchFolderFiles,
- setNotificationAttributes,
- themeColor,
- setThemeColor,
- showMiniDialog,
- onGenericError,
- logout,
- };
+ const appContext = useMemo(
+ () => ({
+ showNavBar: (show: boolean) => setShowNavBar(show),
+ showLoadingBar,
+ hideLoadingBar,
+ watchFolderView,
+ setWatchFolderView,
+ watchFolderFiles,
+ setWatchFolderFiles,
+ setNotificationAttributes,
+ themeColor,
+ setThemeColor,
+ showMiniDialog,
+ onGenericError,
+ logout,
+ }),
+ [
+ showLoadingBar,
+ hideLoadingBar,
+ showMiniDialog,
+ onGenericError,
+ logout,
+ ],
+ );
const title = isI18nReady ? t("title_photos") : staticAppTitle;
diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx
index 0a612bac4a..241f31fe15 100644
--- a/web/apps/photos/src/pages/shared-albums.tsx
+++ b/web/apps/photos/src/pages/shared-albums.tsx
@@ -8,7 +8,7 @@ import {
useIsTouchscreen,
} from "@/base/components/utils/hooks";
import { sharedCryptoWorker } from "@/base/crypto";
-import { isHTTP401Error } from "@/base/http";
+import { isHTTP401Error, PublicAlbumsCredentials } from "@/base/http";
import log from "@/base/log";
import { downloadManager } from "@/gallery/services/download";
import { updateShouldDisableCFUploadProxy } from "@/gallery/services/upload";
@@ -44,7 +44,6 @@ import AddPhotoAlternateOutlined from "@mui/icons-material/AddPhotoAlternateOutl
import CloseIcon from "@mui/icons-material/Close";
import DownloadIcon from "@mui/icons-material/Download";
import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined";
-import MoreHoriz from "@mui/icons-material/MoreHoriz";
import type { ButtonProps, IconButtonProps } from "@mui/material";
import { Box, Button, IconButton, Stack, styled, Tooltip } from "@mui/material";
import Typography from "@mui/material/Typography";
@@ -86,9 +85,7 @@ import { downloadSelectedFiles, getSelectedFiles } from "utils/file";
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
export default function PublicCollectionGallery() {
- const token = useRef(null);
- // passwordJWTToken refers to the jwt token which is used for album protected by password.
- const passwordJWTToken = useRef(null);
+ const credentials = useRef();
const collectionKey = useRef(null);
const url = useRef(null);
const referralCode = useRef("");
@@ -242,16 +239,13 @@ export default function PublicCollectionGallery() {
ck.length < 50
? await cryptoWorker.toB64(bs58.decode(ck))
: await cryptoWorker.fromHex(ck);
- token.current = t;
- downloadManager.setPublicAlbumsCredentials({
- accessToken: token.current,
- });
- await updateShouldDisableCFUploadProxy();
collectionKey.current = dck;
url.current = window.location.href;
const localCollection = await getLocalPublicCollection(
collectionKey.current,
);
+ const accessToken = t;
+ let accessTokenJWT: string | undefined;
if (localCollection) {
referralCode.current = await getReferralCode();
const sortAsc: boolean =
@@ -260,20 +254,20 @@ export default function PublicCollectionGallery() {
const isPasswordProtected =
localCollection?.publicURLs?.[0]?.passwordEnabled;
setIsPasswordProtected(isPasswordProtected);
- const collectionUID = getPublicCollectionUID(token.current);
+ const collectionUID = getPublicCollectionUID(accessToken);
const localFiles = await getLocalPublicFiles(collectionUID);
const localPublicFiles = sortFiles(
mergeMetadata(localFiles),
sortAsc,
);
setPublicFiles(localPublicFiles);
- passwordJWTToken.current =
+ accessTokenJWT =
await getLocalPublicCollectionPassword(collectionUID);
- downloadManager.setPublicAlbumsCredentials({
- accessToken: token.current,
- accessTokenJWT: passwordJWTToken.current,
- });
}
+ credentials.current = { accessToken, accessTokenJWT };
+ downloadManager.setPublicAlbumsCredentials(credentials.current);
+ // Update the CF proxy flag, but we don't need to block on it.
+ void updateShouldDisableCFUploadProxy();
await syncWithRemote();
} finally {
if (!redirectingToWebsite) {
@@ -322,12 +316,14 @@ export default function PublicCollectionGallery() {
}, [onAddPhotos]);
const syncWithRemote = async () => {
- const collectionUID = getPublicCollectionUID(token.current);
+ const collectionUID = getPublicCollectionUID(
+ credentials.current.accessToken,
+ );
try {
showLoadingBar();
setLoading(true);
const [collection, userReferralCode] = await getPublicCollection(
- token.current,
+ credentials.current.accessToken,
collectionKey.current,
);
referralCode.current = userReferralCode;
@@ -338,19 +334,22 @@ export default function PublicCollectionGallery() {
setIsPasswordProtected(isPasswordProtected);
setErrorMessage(null);
- // remove outdated password, sharer has disabled the password
- if (!isPasswordProtected && passwordJWTToken.current) {
- passwordJWTToken.current = null;
+ // Remove the locally saved outdated password token if the sharer
+ // has disabled password protection on the link.
+ if (!isPasswordProtected && credentials.current.accessTokenJWT) {
+ credentials.current.accessTokenJWT = undefined;
+ downloadManager.setPublicAlbumsCredentials(credentials.current);
savePublicCollectionPassword(collectionUID, null);
}
+
if (
!isPasswordProtected ||
- (isPasswordProtected && passwordJWTToken.current)
+ (isPasswordProtected && credentials.current.accessTokenJWT)
) {
try {
await syncPublicFiles(
- token.current,
- passwordJWTToken.current,
+ credentials.current.accessToken,
+ credentials.current.accessTokenJWT,
collection,
setPublicFiles,
);
@@ -359,11 +358,15 @@ export default function PublicCollectionGallery() {
if (parsedError.message === CustomError.TOKEN_EXPIRED) {
// passwordToken has expired, sharer has changed the password,
// so,clearing local cache token value to prompt user to re-enter password
- passwordJWTToken.current = null;
+ credentials.current.accessTokenJWT = undefined;
+ downloadManager.setPublicAlbumsCredentials(
+ credentials.current,
+ );
}
}
}
- if (isPasswordProtected && !passwordJWTToken.current) {
+
+ if (isPasswordProtected && !credentials.current.accessTokenJWT) {
await removePublicFiles(collectionUID);
}
} catch (e) {
@@ -399,18 +402,17 @@ export default function PublicCollectionGallery() {
setFieldError,
) => {
try {
- const jwtToken = await verifyPublicAlbumPassword(
+ const accessTokenJWT = await verifyPublicAlbumPassword(
publicCollection.publicURLs[0]!,
password,
- token.current,
+ credentials.current.accessToken,
);
- passwordJWTToken.current = jwtToken;
- downloadManager.setPublicAlbumsCredentials({
- accessToken: token.current,
- accessTokenJWT: passwordJWTToken.current,
- });
- const collectionUID = getPublicCollectionUID(token.current);
- await savePublicCollectionPassword(collectionUID, jwtToken);
+ credentials.current.accessTokenJWT = accessTokenJWT;
+ downloadManager.setPublicAlbumsCredentials(credentials.current);
+ const collectionUID = getPublicCollectionUID(
+ credentials.current.accessToken,
+ );
+ await savePublicCollectionPassword(collectionUID, accessTokenJWT);
} catch (e) {
log.error("Failed to verifyLinkPassword", e);
if (isHTTP401Error(e)) {
@@ -424,41 +426,6 @@ export default function PublicCollectionGallery() {
await syncWithRemote();
};
- if (loading) {
- if (!publicFiles) {
- return (
-
-
-
- );
- }
- } else {
- if (errorMessage) {
- return {errorMessage};
- }
- if (isPasswordProtected && !passwordJWTToken.current) {
- return (
-
-
- {t("password")}
-
- {t("link_password_description")}
-
-
-
-
- );
- }
- if (!publicFiles) {
- return {t("NOT_FOUND")};
- }
- }
-
const clearSelection = () => {
if (!selected?.count) {
return;
@@ -488,17 +455,45 @@ export default function PublicCollectionGallery() {
}
};
+ if (loading && (!publicFiles || !credentials.current)) {
+ return (
+
+
+
+ );
+ } else if (errorMessage) {
+ return {errorMessage};
+ } else if (isPasswordProtected && !credentials.current.accessTokenJWT) {
+ return (
+
+
+ {t("password")}
+
+ {t("link_password_description")}
+
+
+
+
+ );
+ } else if (!publicFiles || !credentials.current) {
+ return {t("NOT_FOUND")};
+ }
+
+ // TODO: memo this (after the dependencies are traceable).
+ const context = {
+ credentials: credentials.current,
+ referralCode: referralCode.current,
+ photoListHeader,
+ photoListFooter,
+ };
+
return (
-
+
= ({
fileCount={publicFiles.length}
/>
{downloadEnabled && (
- }
- >
+
}
onClick={downloadAllFiles}
diff --git a/web/apps/photos/src/services/upload/upload-service.ts b/web/apps/photos/src/services/upload/upload-service.ts
index 7bf38ee3fc..6b0e020243 100644
--- a/web/apps/photos/src/services/upload/upload-service.ts
+++ b/web/apps/photos/src/services/upload/upload-service.ts
@@ -3,6 +3,7 @@ import type { BytesOrB64 } from "@/base/crypto/types";
import { type CryptoWorker } from "@/base/crypto/worker";
import { ensureElectron } from "@/base/electron";
import { basename, nameAndExtension } from "@/base/file-name";
+import type { PublicAlbumsCredentials } from "@/base/http";
import log from "@/base/log";
import { CustomErrorMessage } from "@/base/types/ipc";
import { extractVideoMetadata } from "@/gallery/services/ffmpeg";
@@ -42,10 +43,7 @@ import { mergeUint8Arrays } from "@/utils/array";
import { ensureInteger, ensureNumber } from "@/utils/ensure";
import { CustomError, handleUploadError } from "@ente/shared/error";
import { addToCollection } from "services/collectionService";
-import {
- PublicUploadProps,
- type LivePhotoAssets,
-} from "services/upload/uploadManager";
+import { type LivePhotoAssets } from "services/upload/uploadManager";
import * as convert from "xml-js";
import { tryParseEpochMicrosecondsFromFileName } from "./date";
import publicUploadHttpClient from "./publicUploadHttpClient";
@@ -110,17 +108,17 @@ const multipartChunksPerPart = 5;
class UploadService {
private uploadURLs: UploadURL[] = [];
private pendingUploadCount: number = 0;
- private publicUploadProps: PublicUploadProps = undefined;
+ private publicAlbumsCredentials: PublicAlbumsCredentials | undefined;
private activeUploadURLRefill: Promise | undefined;
- init(publicUploadProps: PublicUploadProps) {
- this.publicUploadProps = publicUploadProps;
+ init(publicAlbumsCredentials: PublicAlbumsCredentials | undefined) {
+ this.publicAlbumsCredentials = publicAlbumsCredentials;
}
logout() {
this.uploadURLs = [];
this.pendingUploadCount = 0;
- this.publicUploadProps = undefined;
+ this.publicAlbumsCredentials = undefined;
this.activeUploadURLRefill = undefined;
}
@@ -153,11 +151,12 @@ class UploadService {
}
async uploadFile(uploadFile: UploadFile) {
- if (this.publicUploadProps.accessedThroughSharedURL) {
+ if (this.publicAlbumsCredentials) {
return publicUploadHttpClient.uploadFile(
uploadFile,
- this.publicUploadProps.token,
- this.publicUploadProps.passwordToken,
+ // TODO: publicAlbumsCredentials
+ this.publicAlbumsCredentials.accessToken,
+ this.publicAlbumsCredentials.accessTokenJWT,
);
} else {
return UploadHttpClient.uploadFile(uploadFile);
@@ -188,16 +187,10 @@ class UploadService {
private async _refillUploadURLs() {
let urls: UploadURL[];
- if (this.publicUploadProps.accessedThroughSharedURL) {
- if (!this.publicUploadProps.token) {
- throw Error(CustomError.TOKEN_MISSING);
- }
+ if (this.publicAlbumsCredentials) {
urls = await publicUploadHttpClient.fetchUploadURLs(
this.pendingUploadCount,
- {
- accessToken: this.publicUploadProps.token,
- accessTokenJWT: this.publicUploadProps.passwordToken,
- },
+ this.publicAlbumsCredentials,
);
} else {
urls = await UploadHttpClient.fetchUploadURLs(
@@ -208,11 +201,12 @@ class UploadService {
}
async fetchMultipartUploadURLs(count: number) {
- if (this.publicUploadProps.accessedThroughSharedURL) {
+ if (this.publicAlbumsCredentials) {
+ // TODO: publicAlbumsCredentials
return await publicUploadHttpClient.fetchMultipartUploadURLs(
count,
- this.publicUploadProps.token,
- this.publicUploadProps.passwordToken,
+ this.publicAlbumsCredentials.accessToken,
+ this.publicAlbumsCredentials.accessTokenJWT,
);
} else {
return await UploadHttpClient.fetchMultipartUploadURLs(count);
diff --git a/web/apps/photos/src/services/upload/uploadManager.ts b/web/apps/photos/src/services/upload/uploadManager.ts
index f49925210e..474a4645f2 100644
--- a/web/apps/photos/src/services/upload/uploadManager.ts
+++ b/web/apps/photos/src/services/upload/uploadManager.ts
@@ -2,6 +2,7 @@ import { isDesktop } from "@/base/app";
import { createComlinkCryptoWorker } from "@/base/crypto";
import { type CryptoWorker } from "@/base/crypto/worker";
import { lowercaseExtension, nameAndExtension } from "@/base/file-name";
+import type { PublicAlbumsCredentials } from "@/base/http";
import log from "@/base/log";
import type { Electron } from "@/base/types/ipc";
import { ComlinkWorker } from "@/base/worker/comlink-worker";
@@ -96,12 +97,6 @@ export interface LivePhotoAssets {
video: UploadItem;
}
-export interface PublicUploadProps {
- token: string;
- passwordToken: string;
- accessedThroughSharedURL: boolean;
-}
-
interface UploadCancelStatus {
value: boolean;
}
@@ -325,7 +320,7 @@ class UploadManager {
private onUploadFile: (file: EnteFile) => void;
private collections: Map;
private uploadInProgress: boolean;
- private publicUploadProps: PublicUploadProps;
+ private publicAlbumsCredentials: PublicAlbumsCredentials | undefined;
private uploaderName: string;
private uiService: UIService;
@@ -336,12 +331,12 @@ class UploadManager {
public async init(
progressUpdater: ProgressUpdater,
onUploadFile: (file: EnteFile) => void,
- publicCollectProps: PublicUploadProps,
+ publicAlbumsCredentials: PublicAlbumsCredentials | undefined,
) {
this.uiService.init(progressUpdater);
- UploadService.init(publicCollectProps);
+ UploadService.init(publicAlbumsCredentials);
this.onUploadFile = onUploadFile;
- this.publicUploadProps = publicCollectProps;
+ this.publicAlbumsCredentials = publicAlbumsCredentials;
}
logout() {
@@ -497,9 +492,11 @@ class UploadManager {
};
private async updateExistingFilesAndCollections(collections: Collection[]) {
- if (this.publicUploadProps.accessedThroughSharedURL) {
+ if (this.publicAlbumsCredentials) {
this.existingFiles = await getLocalPublicFiles(
- getPublicCollectionUID(this.publicUploadProps.token),
+ getPublicCollectionUID(
+ this.publicAlbumsCredentials.accessToken,
+ ),
);
} else {
this.existingFiles = getUserOwnedFiles(await getLocalFiles());
diff --git a/web/apps/photos/src/utils/publicCollectionGallery/index.ts b/web/apps/photos/src/utils/publicCollectionGallery/index.ts
index c0e4cc9ff6..d5334d7f3f 100644
--- a/web/apps/photos/src/utils/publicCollectionGallery/index.ts
+++ b/web/apps/photos/src/utils/publicCollectionGallery/index.ts
@@ -1,21 +1,23 @@
+import type { PublicAlbumsCredentials } from "@/base/http";
import { TimeStampListItem } from "components/PhotoList";
import { createContext } from "react";
export interface PublicCollectionGalleryContextType {
- token: string;
- passwordToken: string;
+ /**
+ * The {@link PublicAlbumsCredentials} to use. These are guaranteed to be
+ * set if we are in the context of the public albums app, and will be
+ * undefined when we're in the default photos app context.
+ */
+ credentials: PublicAlbumsCredentials | undefined;
referralCode: string | null;
- accessedThroughSharedURL: boolean;
photoListHeader: TimeStampListItem;
photoListFooter: TimeStampListItem;
}
export const PublicCollectionGalleryContext =
createContext({
- token: null,
- passwordToken: null,
+ credentials: undefined,
referralCode: null,
- accessedThroughSharedURL: false,
photoListHeader: null,
photoListFooter: null,
});
diff --git a/web/packages/new/photos/components/CollectionsSortOptions.tsx b/web/packages/new/photos/components/CollectionsSortOptions.tsx
index 59e42fb1a7..d33bce1ed1 100644
--- a/web/packages/new/photos/components/CollectionsSortOptions.tsx
+++ b/web/packages/new/photos/components/CollectionsSortOptions.tsx
@@ -24,10 +24,10 @@ interface CollectionsSortOptionsProps {
*/
nestedInDialog?: boolean;
/**
- * Set this to true to disable the background in the button that triggers
- * the menu.
+ * Set this to true to disable the background for the icon button that
+ * triggers the menu.
*/
- disableTriggerButtonBackground?: boolean;
+ transparentTriggerButtonBackground?: boolean;
}
/**
@@ -37,11 +37,11 @@ interface CollectionsSortOptionsProps {
*/
export const CollectionsSortOptions: React.FC = ({
nestedInDialog,
- disableTriggerButtonBackground,
+ transparentTriggerButtonBackground,
...optProps
}) => (
}
menuPaperProps={{
sx: {
@@ -54,7 +54,7 @@ export const CollectionsSortOptions: React.FC = ({
triggerButtonProps={{
sx: {
backgroundColor: (theme) =>
- disableTriggerButtonBackground
+ transparentTriggerButtonBackground
? undefined
: theme.colors.fill.faint,
},
diff --git a/web/packages/new/photos/components/gallery/BarImpl.tsx b/web/packages/new/photos/components/gallery/BarImpl.tsx
index 6cdaf4b6ea..43ca6853ef 100644
--- a/web/packages/new/photos/components/gallery/BarImpl.tsx
+++ b/web/packages/new/photos/components/gallery/BarImpl.tsx
@@ -223,7 +223,7 @@ export const GalleryBarImpl: React.FC = ({
diff --git a/web/packages/new/photos/components/gallery/PeopleHeader.tsx b/web/packages/new/photos/components/gallery/PeopleHeader.tsx
index ca8a1bca2b..73a2e680f6 100644
--- a/web/packages/new/photos/components/gallery/PeopleHeader.tsx
+++ b/web/packages/new/photos/components/gallery/PeopleHeader.tsx
@@ -40,7 +40,6 @@ import ClearIcon from "@mui/icons-material/Clear";
import EditIcon from "@mui/icons-material/Edit";
import HideImageOutlinedIcon from "@mui/icons-material/HideImageOutlined";
import ListAltOutlinedIcon from "@mui/icons-material/ListAltOutlined";
-import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
import RestoreIcon from "@mui/icons-material/Restore";
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
import {
@@ -146,10 +145,7 @@ const CGroupPersonHeader: React.FC = ({ person }) => {
name={name}
fileCount={person.fileIDs.length}
/>
- }
- >
+
}
centerAlign
@@ -210,10 +206,7 @@ const IgnoredPersonHeader: React.FC = ({
nameProps={{ color: "text.muted" }}
fileCount={person.fileIDs.length}
/>
- }
- >
+
}
centerAlign
@@ -271,10 +264,7 @@ const ClusterPersonHeader: React.FC = ({
- }
- >
+
}
centerAlign
diff --git a/web/packages/shared/components/OverflowMenu.tsx b/web/packages/shared/components/OverflowMenu.tsx
index ab4b71fb95..2469094358 100644
--- a/web/packages/shared/components/OverflowMenu.tsx
+++ b/web/packages/shared/components/OverflowMenu.tsx
@@ -1,4 +1,5 @@
import { FluidContainer } from "@ente/shared/components/Container";
+import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
import {
Box,
IconButton,
@@ -10,7 +11,7 @@ import {
type PaperProps,
} from "@mui/material";
import Menu, { type MenuProps } from "@mui/material/Menu";
-import React, { createContext, useContext, useState } from "react";
+import React, { createContext, useContext, useMemo, useState } from "react";
const OverflowMenuContext = createContext({
// eslint-disable-next-line @typescript-eslint/no-empty-function
@@ -18,13 +19,30 @@ const OverflowMenuContext = createContext({
});
interface OverflowMenuProps {
- triggerButtonIcon: React.ReactNode;
+ /**
+ * An ARIA identifier for the overflow menu when it is displayed.
+ */
+ ariaID: string;
+ /**
+ * The icon for the trigger button.
+ *
+ * If not provided, then by default the MoreHoriz icon from MUI is used.
+ */
+ triggerButtonIcon?: React.ReactNode;
+ /**
+ * Optional additional properties for the trigger icon button.
+ */
triggerButtonProps?: Partial;
- children?: React.ReactNode;
- ariaControls: string;
+ /**
+ * Optional additional properties for the MUI {@link Paper} that underlies
+ * the {@link Menu}.
+ */
menuPaperProps?: Partial;
}
+/**
+ * A custom MUI {@link Menu} with some Ente specific styling applied to it.
+ */
export const StyledMenu = styled(Menu)`
& .MuiPaper-root {
margin: 16px auto;
@@ -38,38 +56,47 @@ export const StyledMenu = styled(Menu)`
}
`;
-export const OverflowMenu: React.FC = ({
- children,
- ariaControls,
+/**
+ * An overflow menu showing {@link OverflowMenuOptions}, alongwith a button to
+ * trigger the visibility of the menu.
+ */
+export const OverflowMenu: React.FC<
+ React.PropsWithChildren
+> = ({
+ ariaID,
triggerButtonIcon,
triggerButtonProps,
menuPaperProps,
+ children,
}) => {
- const [sortByEl, setSortByEl] = useState(
- null,
+ const [anchorEl, setAnchorEl] = useState();
+ const context = useMemo(
+ () => ({ close: () => setAnchorEl(undefined) }),
+ [],
);
- const handleClose = () => setSortByEl(null);
return (
-
+
setSortByEl(event.currentTarget)}
- aria-controls={sortByEl ? ariaControls : undefined}
+ onClick={(event) => setAnchorEl(event.currentTarget)}
+ aria-controls={anchorEl ? ariaID : undefined}
aria-haspopup="true"
- aria-expanded={sortByEl ? "true" : undefined}
+ aria-expanded={anchorEl ? "true" : undefined}
{...triggerButtonProps}
>
- {triggerButtonIcon}
+ {triggerButtonIcon ?? }
setAnchorEl(undefined)}
MenuListProps={{
disablePadding: true,
- "aria-labelledby": ariaControls,
+ "aria-labelledby": ariaID,
+ }}
+ slotProps={{
+ paper: menuPaperProps,
}}
- PaperProps={menuPaperProps}
anchorOrigin={{
vertical: "bottom",
horizontal: "right",
@@ -90,7 +117,6 @@ interface OverflowMenuOptionProps {
color?: ButtonProps["color"];
startIcon?: React.ReactNode;
endIcon?: React.ReactNode;
- keepOpenAfterClick?: boolean;
children?: any;
// To avoid changing old places without an audit, new code should use this
// option explicitly to fix/tweak the alignment of the button label and
@@ -103,7 +129,6 @@ export const OverflowMenuOption: React.FC = ({
color = "primary",
startIcon,
endIcon,
- keepOpenAfterClick,
centerAlign,
children,
}) => {
@@ -111,10 +136,9 @@ export const OverflowMenuOption: React.FC = ({
const handleClick = () => {
onClick();
- if (!keepOpenAfterClick) {
- menuContext.close();
- }
+ menuContext.close();
};
+
return (