From 4c5d340b571f81bdbf97fc88a74067f5c10c8726 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 20:38:00 +0530 Subject: [PATCH 1/7] More tsc --- web/apps/cast/src/pages/slideshow.tsx | 2 +- .../photos/src/components/Collections/CollectionShare.tsx | 8 +++----- web/apps/photos/src/pages/shared-albums.tsx | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 2bc1b32eff..4ecdb61e2a 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -9,7 +9,7 @@ import { imageURLGenerator } from "services/render"; const Page: React.FC = () => { const [isEmpty, setIsEmpty] = useState(false); - const [imageURL, setImageURL] = useState(); + const [imageURL, setImageURL] = useState(""); const router = useRouter(); diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index fd17723720..e09fe29fb4 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -1347,9 +1347,7 @@ const ManagePublicShareOptions: React.FC = ({ setBlockingLoad, onRemotePull, }) => { - const [errorMessage, setErrorMessage] = useState( - undefined, - ); + const [errorMessage, setErrorMessage] = useState(""); const [copied, handleCopyLink] = useClipboardCopy(resolvedURL); @@ -1362,7 +1360,7 @@ const ManagePublicShareOptions: React.FC = ({ updates: UpdatePublicURLAttributes, ) => { setBlockingLoad(true); - setErrorMessage(undefined); + setErrorMessage(""); try { setPublicURL(await updatePublicURL(collection.id, updates)); void onRemotePull({ silent: true }); @@ -1375,7 +1373,7 @@ const ManagePublicShareOptions: React.FC = ({ }; const handleRemovePublicLink = async () => { setBlockingLoad(true); - setErrorMessage(undefined); + setErrorMessage(""); try { await deleteShareURL(collection.id); setPublicURL(undefined); diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index b3427da707..ac6ffd7759 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -98,7 +98,7 @@ export default function PublicCollectionGallery() { const [publicFiles, setPublicFiles] = useState( undefined, ); - const [errorMessage, setErrorMessage] = useState(null); + const [errorMessage, setErrorMessage] = useState(""); const [loading, setLoading] = useState(true); const [isPasswordProtected, setIsPasswordProtected] = useState(false); const [uploadTypeSelectorView, setUploadTypeSelectorView] = useState(false); @@ -229,7 +229,7 @@ export default function PublicCollectionGallery() { const isPasswordProtected = !!collection.publicURLs[0]?.passwordEnabled; setIsPasswordProtected(isPasswordProtected); - setErrorMessage(null); + setErrorMessage(""); // Remove the locally cached accessTokenJWT if the sharer has // disabled password protection on the link. From 236c6f612b0d44f366605e66e872871fdd8e81ff Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 07:07:04 +0530 Subject: [PATCH 2/7] Separate internal and external interfaces --- .../Collections/GalleryBarAndListHeader.tsx | 11 ++--- web/apps/photos/src/components/FileList.tsx | 47 ++++++++++++------- web/apps/photos/src/pages/gallery.tsx | 13 ++--- web/apps/photos/src/pages/shared-albums.tsx | 1 - 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 96b36a5a02..48e32abce6 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -3,7 +3,7 @@ import { CollectionShare, type CollectionShareProps, } from "components/Collections/CollectionShare"; -import type { TimeStampListItem } from "components/FileList"; +import type { FileListHeaderOrFooter } from "components/FileList"; import { useModalVisibility } from "ente-base/components/utils/modal"; import { isSaveCancelled, @@ -49,7 +49,7 @@ type GalleryBarAndListHeaderProps = Omit< barCollectionSummaries: CollectionSummaries; activeCollection: Collection; setActiveCollectionID: (collectionID: number) => void; - setPhotoListHeader: (value: TimeStampListItem) => void; + setFileListHeader: (header: FileListHeaderOrFooter) => void; saveGroups: SaveGroup[]; } & Pick & Pick< @@ -66,7 +66,7 @@ type GalleryBarAndListHeaderProps = Omit< * of the actual list of items. * * These are disparate views - indeed, the list header is not even a child of - * this component but is instead proxied via {@link setPhotoListHeader}. Still, + * this component but is instead proxied via {@link setFileListHeader}. Still, * having this intermediate wrapper component allows us to move some of the * common concerns shared by both the gallery bar and list header (e.g. some * dialogs that can be invoked from both places) into this file instead of @@ -95,7 +95,7 @@ export const GalleryBarAndListHeader: React.FC< onRemotePull, onAddSaveGroup, onSelectPerson, - setPhotoListHeader, + setFileListHeader, }) => { const { show: showAllAlbums, props: allAlbumsVisibilityProps } = useModalVisibility(); @@ -134,7 +134,7 @@ export const GalleryBarAndListHeader: React.FC< useEffect(() => { if (shouldHide) return; - setPhotoListHeader({ + setFileListHeader({ item: mode != "people" ? ( ), - tag: "header", height: 68, }); }, [ diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index cf2314ef3f..7d0737d79c 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -53,14 +53,27 @@ const FOOTER_HEIGHT = 90; const ALBUM_FOOTER_HEIGHT = 75; const ALBUM_FOOTER_HEIGHT_WITH_REFERRAL = 113; -export type FileListItemTag = "header" | "publicAlbumsFooter" | "date" | "file"; +/** + * A component with an explicit height suitable for being plugged in as the + * {@link header} or {@link footer} of the {@link FileList}. + */ +export interface FileListHeaderOrFooter { + /** + * The component itself. + */ + item: React.ReactNode; + /** + * The height of the component (in px). + */ + height: number; +} -export interface TimeStampListItem { +interface TimeStampListItem { /** * An optional {@link FileListItemTag} that can be used to identify item * types for conditional behaviour. */ - tag?: FileListItemTag; + tag?: "date" | "file"; items?: FileListAnnotatedFile[]; itemStartIndex?: number; date?: string; @@ -122,6 +135,19 @@ export interface FileListProps { * another mode in which the gallery operates. */ modePlus?: GalleryBarMode | "search"; + /** + * An optional component shown before all the items in the list. + * + * It is not sticky, and scrolls along with the content of the list. + */ + header?: FileListHeaderOrFooter; + /** + * An optional component shown after all the items in the list. + * + * It is not sticky, and scrolls along with the content of the list. + */ + footer?: FileListHeaderOrFooter; + showAppDownloadBanner?: boolean; /** * The logged in user, if any. * @@ -130,7 +156,6 @@ export interface FileListProps { * omit this prop. */ user?: LocalUser; - showAppDownloadBanner?: boolean; /** * If `true`, then the current listing is showing magic search results. */ @@ -157,16 +182,6 @@ export interface FileListProps { * omitted when running in the public albums app. */ emailByUserID?: Map; - /** - * An optional {@link TimeStampListItem} shown before all the items in the - * list. It is not sticky, and scrolls along with the content of the list. - */ - header?: TimeStampListItem; - /** - * An optional {@link TimeStampListItem} shown after all the items in the - * list. It is not sticky, and scrolls along with the content of the list. - */ - footer?: TimeStampListItem; /** * Called when the user activates the thumbnail at the given {@link index}. * @@ -185,6 +200,7 @@ export const FileList: React.FC = ({ mode, modePlus, header, + footer, user, annotatedFiles, showAppDownloadBanner, @@ -196,7 +212,6 @@ export const FileList: React.FC = ({ activePersonID, favoriteFileIDs, emailByUserID, - footer, onItemClick, }) => { const publicCollectionGalleryContext = useContext( @@ -385,7 +400,6 @@ export const FileList: React.FC = ({ }; const getAppDownloadFooter = (): TimeStampListItem => ({ - tag: "publicAlbumsFooter", height: FOOTER_HEIGHT, item: ( @@ -415,7 +429,6 @@ export const FileList: React.FC = ({ }); const getAlbumsFooter = (): TimeStampListItem => ({ - tag: "publicAlbumsFooter", height: publicCollectionGalleryContext.referralCode ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL : ALBUM_FOOTER_HEIGHT, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 949b6e43a4..7e3d14bc72 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -5,7 +5,7 @@ import { IconButton, Stack, Typography } from "@mui/material"; import { AuthenticateUser } from "components/AuthenticateUser"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; import { DownloadStatusNotifications } from "components/DownloadStatusNotifications"; -import { type TimeStampListItem } from "components/FileList"; +import type { FileListHeaderOrFooter } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; import { FixCreationTime } from "components/FixCreationTime"; import { Sidebar } from "components/Sidebar"; @@ -180,9 +180,11 @@ const Page: React.FC = () => { const [fixCreationTimeFiles, setFixCreationTimeFiles] = useState< EnteFile[] >([]); - // The (non-sticky) header shown at the top of the gallery items. + /** + * The (non-sticky) header shown at the top of the gallery items. + */ const [fileListHeader, setFileListHeader] = useState< - TimeStampListItem | undefined + FileListHeaderOrFooter | undefined >(undefined); const [openCollectionSelector, setOpenCollectionSelector] = useState(false); @@ -409,14 +411,13 @@ const Page: React.FC = () => { useEffect(() => { if (isInSearchMode && state.searchSuggestion) { setFileListHeader({ - height: 104, item: ( ), - tag: "header", + height: 104, }); } }, [isInSearchMode, state.searchSuggestion, state.searchResults]); @@ -1072,7 +1073,7 @@ const Page: React.FC = () => { activeCollection, activeCollectionID, activePerson, - setPhotoListHeader: setFileListHeader, + setFileListHeader, saveGroups, onAddSaveGroup, }} diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index ac6ffd7759..52a2aabc8e 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -398,7 +398,6 @@ export default function PublicCollectionGallery() { }} /> ), - tag: "header" as const, height: 68, } : undefined, From f4b909f4a79091e8dbc61b34a8845187977c61a1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 07:39:55 +0530 Subject: [PATCH 3/7] Cleanup --- web/apps/photos/src/components/FileList.tsx | 45 ----------------- .../src/components/FileListWithViewer.tsx | 3 -- web/apps/photos/src/pages/gallery.tsx | 50 +++++++++++++++++-- 3 files changed, 46 insertions(+), 52 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 7d0737d79c..905e81d227 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -147,7 +147,6 @@ export interface FileListProps { * It is not sticky, and scrolls along with the content of the list. */ footer?: FileListHeaderOrFooter; - showAppDownloadBanner?: boolean; /** * The logged in user, if any. * @@ -203,7 +202,6 @@ export const FileList: React.FC = ({ footer, user, annotatedFiles, - showAppDownloadBanner, isMagicSearchResult, selectable, selected, @@ -276,8 +274,6 @@ export const FileList: React.FC = ({ timeStampList.push(getVacuumItem(timeStampList)); if (footer) { timeStampList.push(asFullSpanListItem(footer)); - } else if (showAppDownloadBanner) { - timeStampList.push(getAppDownloadFooter()); } if (publicCollectionGalleryContext.credentials) { @@ -399,35 +395,6 @@ export const FileList: React.FC = ({ }; }; - const getAppDownloadFooter = (): TimeStampListItem => ({ - height: FOOTER_HEIGHT, - item: ( - - - - ), - b: ( - - ), - }} - /> - - - ), - }); - const getAlbumsFooter = (): TimeStampListItem => ({ height: publicCollectionGalleryContext.referralCode ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL @@ -966,18 +933,6 @@ const DateContainer = styled(ListItemContainer)( `, ); -const FooterContainer = styled(ListItemContainer)` - margin-bottom: 0.75rem; - @media (max-width: 540px) { - font-size: 12px; - margin-bottom: 0.5rem; - } - text-align: center; - justify-content: center; - align-items: flex-end; - margin-top: calc(2rem + 20px); -`; - const AlbumFooterContainer = styled(ListItemContainer, { shouldForwardProp: (propName) => propName != "hasReferral", })<{ hasReferral: boolean }>` diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 3386588ec6..a332c70364 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -58,7 +58,6 @@ export type FileListWithViewerProps = { | "modePlus" | "header" | "footer" - | "showAppDownloadBanner" | "isMagicSearchResult" | "selectable" | "selected" @@ -98,7 +97,6 @@ export const FileListWithViewer: React.FC = ({ user, files, enableDownload, - showAppDownloadBanner, isMagicSearchResult, selectable, selected, @@ -186,7 +184,6 @@ export const FileListWithViewer: React.FC = ({ header, footer, user, - showAppDownloadBanner, isMagicSearchResult, selectable, selected, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 7e3d14bc72..0cd16af4ee 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1,7 +1,7 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined"; import MenuIcon from "@mui/icons-material/Menu"; -import { IconButton, Stack, Typography } from "@mui/material"; +import { IconButton, Link, Stack, Typography } from "@mui/material"; import { AuthenticateUser } from "components/AuthenticateUser"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; import { DownloadStatusNotifications } from "components/DownloadStatusNotifications"; @@ -967,6 +967,14 @@ const Page: React.FC = () => { [], ); + const showAppDownloadFooter = + state.collectionFiles.length < 30 && !isInSearchMode; + + const fileListFooter = useMemo( + () => (showAppDownloadFooter ? createAppDownloadFooter() : undefined), + [showAppDownloadFooter], + ); + const showSelectionBar = selected.count > 0 && selected.collectionID === activeCollectionID; @@ -1149,12 +1157,10 @@ const Page: React.FC = () => { mode={barMode} modePlus={isInSearchMode ? "search" : barMode} header={fileListHeader} + footer={fileListFooter} user={user} files={filteredFiles} enableDownload={true} - showAppDownloadBanner={ - state.collectionFiles.length < 30 && !isInSearchMode - } isMagicSearchResult={state.searchSuggestion?.type == "clip"} selectable={true} selected={selected} @@ -1383,3 +1389,39 @@ const handleSubscriptionCompletionRedirectIfNeeded = async ( } } }; + +const createAppDownloadFooter = (): FileListHeaderOrFooter => ({ + item: ( + + + ), + b: ( + + ), + }} + /> + + ), + height: 90, +}); From ef1a5358fde8d5eced4543783e0ebec7fbb8aa0c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 07:55:49 +0530 Subject: [PATCH 4/7] Rename --- web/apps/photos/src/components/FileList.tsx | 13 ++++++++----- .../photos/src/components/FileListWithViewer.tsx | 6 +++--- web/apps/photos/src/pages/gallery.tsx | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 905e81d227..16ba5b41a9 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -156,9 +156,12 @@ export interface FileListProps { */ user?: LocalUser; /** - * If `true`, then the current listing is showing magic search results. + * If `true`, then the default behaviour of grouping files by their date is + * suppressed. + * + * This behaviour is used when showing magic search results. */ - isMagicSearchResult?: boolean; + disableGrouping?: boolean; selectable?: boolean; setSelected: ( selected: SelectedState | ((selected: SelectedState) => SelectedState), @@ -202,7 +205,7 @@ export const FileList: React.FC = ({ footer, user, annotatedFiles, - isMagicSearchResult, + disableGrouping, selectable, selected, setSelected, @@ -259,7 +262,7 @@ export const FileList: React.FC = ({ if (header) { timeStampList.push(asFullSpanListItem(header)); } - if (isMagicSearchResult) { + if (disableGrouping) { noGrouping(timeStampList); } else { groupByTime(timeStampList); @@ -294,7 +297,7 @@ export const FileList: React.FC = ({ annotatedFiles, header, footer, - isMagicSearchResult, + disableGrouping, publicCollectionGalleryContext.credentials, ]); diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index a332c70364..92c81df25f 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -58,7 +58,7 @@ export type FileListWithViewerProps = { | "modePlus" | "header" | "footer" - | "isMagicSearchResult" + | "disableGrouping" | "selectable" | "selected" | "setSelected" @@ -97,7 +97,7 @@ export const FileListWithViewer: React.FC = ({ user, files, enableDownload, - isMagicSearchResult, + disableGrouping, selectable, selected, setSelected, @@ -184,7 +184,7 @@ export const FileListWithViewer: React.FC = ({ header, footer, user, - isMagicSearchResult, + disableGrouping, selectable, selected, setSelected, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 0cd16af4ee..6fb596b991 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1161,7 +1161,7 @@ const Page: React.FC = () => { user={user} files={filteredFiles} enableDownload={true} - isMagicSearchResult={state.searchSuggestion?.type == "clip"} + disableGrouping={state.searchSuggestion?.type == "clip"} selectable={true} selected={selected} setSelected={setSelected} From d327eb027c4f52e7642b041527e9fab91f001f28 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 08:23:39 +0530 Subject: [PATCH 5/7] Extract and merge, part 1 --- .../Collections/GalleryBarAndListHeader.tsx | 4 +- web/apps/photos/src/pages/gallery.tsx | 3 - web/apps/photos/src/pages/shared-albums.tsx | 125 +++++++++++++++--- .../photos/components/gallery/ListHeader.tsx | 4 +- 4 files changed, 111 insertions(+), 25 deletions(-) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 48e32abce6..1bf7574f06 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -62,8 +62,8 @@ type GalleryBarAndListHeaderProps = Omit< * dialogs that might be triggered by actions on either the bar or the header.. * * This component manages the sticky horizontally scrollable bar shown at the - * top of the gallery, AND the non-sticky header shown below the bar, at the top - * of the actual list of items. + * top of the gallery, AND the (non-sticky) header shown below the bar, at the + * top of the actual list of items. * * These are disparate views - indeed, the list header is not even a child of * this component but is instead proxied via {@link setFileListHeader}. Still, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 6fb596b991..dbb6f1425c 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -180,9 +180,6 @@ const Page: React.FC = () => { const [fixCreationTimeFiles, setFixCreationTimeFiles] = useState< EnteFile[] >([]); - /** - * The (non-sticky) header shown at the top of the gallery items. - */ const [fileListHeader, setFileListHeader] = useState< FileListHeaderOrFooter | undefined >(undefined); diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 52a2aabc8e..ea565c7adb 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -390,7 +390,7 @@ export default function PublicCollectionGallery() { publicCollection && publicFiles ? { item: ( - ), - height: 68, + height: fileListHeaderHeight, } : undefined, [onAddSaveGroup, publicCollection, publicFiles], ); - const fileListFooter = useMemo( - () => - onAddPhotos - ? { - item: ( - - - - ), - height: 104, - } - : undefined, - [onAddPhotos], - ); + const fileListFooter = useMemo(() => { + const props = { referralCode: referralCode.current, onAddPhotos }; + return { + item: , + height: fileListFooterHeightForProps(props), + }; + }, [referralCode.current, onAddPhotos]); if (loading && (!publicFiles || !credentials.current)) { return ; @@ -626,13 +619,24 @@ const SelectedFileOptions: React.FC = ({ ); -interface ListHeaderProps { +interface FileListHeaderProps { publicCollection: Collection; publicFiles: EnteFile[]; onAddSaveGroup: AddSaveGroup; } -const ListHeader: React.FC = ({ +/** + * The fixed height (in px) of {@link FileListHeader}. + */ +const fileListHeaderHeight = 68; + +/** + * A header shown before the listing of files. + * + * It scrolls along with the content. It has a fixed height, + * {@link fileListHeaderHeight}. + */ +const FileListHeader: React.FC = ({ publicCollection, publicFiles, onAddSaveGroup, @@ -670,3 +674,88 @@ const ListHeader: React.FC = ({ ); }; + +interface FileListFooterProps { + referralCode?: string; + onAddPhotos?: () => void; +} + +/** + * The dynamic (prop-depedent) height of {@link FileListFooter}. + */ +const fileListFooterHeightForProps = ({ + referralCode, + onAddPhotos, +}: FileListFooterProps) => (onAddPhotos ? 104 : 0) + (referralCode ? 113 : 75); + +/** + * A footer shown after the listing of files. + * + * It scrolls along with the content. It has a dynamic height, dependent on the + * props, calculated using {@link fileListFooterHeightForProps}. + */ + +const FileListFooter: React.FC = ({ + referralCode, + onAddPhotos, +}) => { + if (onAddPhotos) + return ( + + + + ); + + return ( + + {/* Make the entire area tappable, otherwise it is hard to + get at on mobile devices. */} + + + + + ), + }} + values={{ url: "ente.io" }} + /> + + + {publicCollectionGalleryContext.referralCode ? ( + + + + + + ) : null} + + + ); +}; diff --git a/web/packages/new/photos/components/gallery/ListHeader.tsx b/web/packages/new/photos/components/gallery/ListHeader.tsx index 91838f3852..1979ff5fe5 100644 --- a/web/packages/new/photos/components/gallery/ListHeader.tsx +++ b/web/packages/new/photos/components/gallery/ListHeader.tsx @@ -38,8 +38,8 @@ interface GalleryItemsSummaryProps { } /** - * A component suitable for being used as a (non-sticky) summary displayed on - * top of the of a list of photos (or other items) shown in the gallery. + * A component suitable for being used as a summary displayed on top of the of a + * list of photos (or other items) shown in the gallery. */ export const GalleryItemsSummary: React.FC = ({ name, From f9adbdf639ad255c9eb57cbcb645ef1ab5836735 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 08:32:19 +0530 Subject: [PATCH 6/7] Extract and merge, part 2 --- web/apps/photos/src/components/FileList.tsx | 105 +-------------- web/apps/photos/src/pages/shared-albums.tsx | 136 ++++++++++++-------- 2 files changed, 87 insertions(+), 154 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 16ba5b41a9..e7eaa88228 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -3,7 +3,7 @@ import AlbumOutlinedIcon from "@mui/icons-material/AlbumOutlined"; import FavoriteRoundedIcon from "@mui/icons-material/FavoriteRounded"; import PlayCircleOutlineOutlinedIcon from "@mui/icons-material/PlayCircleOutlineOutlined"; -import { Box, Checkbox, Link, Typography, styled } from "@mui/material"; +import { Box, Checkbox, Typography, styled } from "@mui/material"; import Avatar from "components/Avatar"; import type { LocalUser } from "ente-accounts/services/user"; import { assertionFailed } from "ente-base/assert"; @@ -31,7 +31,6 @@ import { PseudoCollectionID } from "ente-new/photos/services/collection-summary" import { t } from "i18next"; import memoize from "memoize-one"; import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { Trans } from "react-i18next"; import { VariableSizeList as List, type ListChildComponentProps, @@ -49,10 +48,6 @@ export const SPACE_BTW_DATES = 44; const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244; -const FOOTER_HEIGHT = 90; -const ALBUM_FOOTER_HEIGHT = 75; -const ALBUM_FOOTER_HEIGHT_WITH_REFERRAL = 113; - /** * A component with an explicit height suitable for being plugged in as the * {@link header} or {@link footer} of the {@link FileList}. @@ -274,15 +269,12 @@ export const FileList: React.FC = ({ if (timeStampList.length === 1) { timeStampList.push(getEmptyListItem()); } - timeStampList.push(getVacuumItem(timeStampList)); + const footerHeight = footer?.height ?? 0; + timeStampList.push(getVacuumItem(timeStampList, footerHeight)); if (footer) { timeStampList.push(asFullSpanListItem(footer)); } - if (publicCollectionGalleryContext.credentials) { - timeStampList.push(getAlbumsFooter()); - } - setTimeStampList(timeStampList); refreshInProgress.current = false; if (shouldRefresh.current) { @@ -372,15 +364,7 @@ export const FileList: React.FC = ({ }; }; - const getVacuumItem = (timeStampList) => { - let footerHeight; - if (publicCollectionGalleryContext.credentials) { - footerHeight = publicCollectionGalleryContext.referralCode - ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL - : ALBUM_FOOTER_HEIGHT; - } else { - footerHeight = FOOTER_HEIGHT; - } + const getVacuumItem = (timeStampList, footerHeight: number) => { const fileListHeight = (() => { let sum = 0; const getCurrentItemSize = getItemSize(timeStampList); @@ -398,64 +382,6 @@ export const FileList: React.FC = ({ }; }; - const getAlbumsFooter = (): TimeStampListItem => ({ - height: publicCollectionGalleryContext.referralCode - ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL - : ALBUM_FOOTER_HEIGHT, - item: ( - - {/* Make the entire area tappable, otherwise it is hard to - get at on mobile devices. */} - - - - - ), - }} - values={{ url: "ente.io" }} - /> - - - {publicCollectionGalleryContext.referralCode ? ( - - - - - - ) : null} - - - ), - }); - /** * Checks and merge multiple dates into a single row. */ @@ -936,29 +862,6 @@ const DateContainer = styled(ListItemContainer)( `, ); -const AlbumFooterContainer = styled(ListItemContainer, { - shouldForwardProp: (propName) => propName != "hasReferral", -})<{ hasReferral: boolean }>` - margin-top: 48px; - margin-bottom: ${({ hasReferral }) => (!hasReferral ? `10px` : "0px")}; - text-align: center; - justify-content: center; -`; - -const FullStretchContainer = styled("div")( - ({ theme }) => ` - margin: 0 -24px; - width: calc(100% + 46px); - left: -24px; - @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { - margin: 0 -4px; - width: calc(100% + 6px); - left: -4px; - } - background-color: ${theme.vars.palette.accent.main}; -`, -); - const NothingContainer = styled(ListItemContainer)` text-align: center; justify-content: center; diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index ea565c7adb..b69f07e549 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -3,7 +3,15 @@ import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternate import CloseIcon from "@mui/icons-material/Close"; import DownloadIcon from "@mui/icons-material/Download"; import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; -import { Box, Button, IconButton, Stack, styled, Tooltip } from "@mui/material"; +import { + Box, + Button, + IconButton, + Link, + Stack, + styled, + Tooltip, +} from "@mui/material"; import Typography from "@mui/material/Typography"; import { DownloadStatusNotifications } from "components/DownloadStatusNotifications"; import { FileListWithViewer } from "components/FileListWithViewer"; @@ -74,6 +82,10 @@ import { removePublicCollectionFileData, verifyPublicAlbumPassword, } from "ente-new/albums/services/public-collection"; +import { + IMAGE_CONTAINER_MAX_WIDTH, + MIN_COLUMNS, +} from "ente-new/photos/components/FileList"; import { GalleryItemsHeaderAdapter, GalleryItemsSummary, @@ -84,6 +96,7 @@ import { t } from "i18next"; import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FileWithPath } from "react-dropzone"; +import { Trans } from "react-i18next"; import { uploadManager } from "services/upload-manager"; import { getSelectedFiles, type SelectedState } from "utils/file"; import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; @@ -699,63 +712,80 @@ const FileListFooter: React.FC = ({ referralCode, onAddPhotos, }) => { - if (onAddPhotos) - return ( - - - - ); - return ( - - {/* Make the entire area tappable, otherwise it is hard to + + {onAddPhotos && ( + + + + )} + + {/* Make the entire area tappable, otherwise it is hard to get at on mobile devices. */} - - - - - ), - }} - values={{ url: "ente.io" }} - /> - - - {publicCollectionGalleryContext.referralCode ? ( - - + + + + ), }} + values={{ url: "ente.io" }} /> - - ) : null} - - + + {referralCode ? ( + + + + + + ) : null} + + + ); }; + +const AlbumFooterContainer = styled("div", { + shouldForwardProp: (propName) => propName != "hasReferral", +})<{ hasReferral: boolean }>` + margin-top: 48px; + margin-bottom: ${({ hasReferral }) => (!hasReferral ? `10px` : "0px")}; + text-align: center; + justify-content: center; +`; + +const FullStretchContainer = styled("div")( + ({ theme }) => ` + margin: 0 -24px; + width: calc(100% + 46px); + left: -24px; + @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { + margin: 0 -4px; + width: calc(100% + 6px); + left: -4px; + } + background-color: ${theme.vars.palette.accent.main}; +`, +); From 1d7f9522e3c2318fa71a3dcf9c670d0d671290c9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 09:25:50 +0530 Subject: [PATCH 7/7] Fin --- web/apps/photos/src/pages/shared-albums.tsx | 154 ++++++++---------- .../utils/publicCollectionGallery/index.ts | 2 - 2 files changed, 64 insertions(+), 92 deletions(-) diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index b69f07e549..4ec23bae9f 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -82,10 +82,6 @@ import { removePublicCollectionFileData, verifyPublicAlbumPassword, } from "ente-new/albums/services/public-collection"; -import { - IMAGE_CONTAINER_MAX_WIDTH, - MIN_COLUMNS, -} from "ente-new/photos/components/FileList"; import { GalleryItemsHeaderAdapter, GalleryItemsSummary, @@ -111,6 +107,7 @@ export default function PublicCollectionGallery() { const [publicFiles, setPublicFiles] = useState( undefined, ); + const [referralCode, setReferralCode] = useState(""); const [errorMessage, setErrorMessage] = useState(""); const [loading, setLoading] = useState(true); const [isPasswordProtected, setIsPasswordProtected] = useState(false); @@ -130,7 +127,6 @@ export default function PublicCollectionGallery() { const credentials = useRef(undefined); const collectionKey = useRef(null); const url = useRef(null); - const referralCode = useRef(""); const { saveGroups, onAddSaveGroup, onRemoveSaveGroup } = useSaveGroups(); @@ -193,8 +189,9 @@ export default function PublicCollectionGallery() { const accessToken = t; let accessTokenJWT: string | undefined; if (collection) { - referralCode.current = - await savedLastPublicCollectionReferralCode(); + setReferralCode( + (await savedLastPublicCollectionReferralCode()) ?? "", + ); setPublicCollection(collection); setIsPasswordProtected( !!collection.publicURLs[0]?.passwordEnabled, @@ -236,7 +233,7 @@ export default function PublicCollectionGallery() { try { const { collection, referralCode: userReferralCode } = await pullCollection(accessToken, collectionKey.current); - referralCode.current = userReferralCode; + setReferralCode(userReferralCode); setPublicCollection(collection); const isPasswordProtected = @@ -418,12 +415,12 @@ export default function PublicCollectionGallery() { ); const fileListFooter = useMemo(() => { - const props = { referralCode: referralCode.current, onAddPhotos }; + const props = { referralCode, onAddPhotos }; return { item: , height: fileListFooterHeightForProps(props), }; - }, [referralCode.current, onAddPhotos]); + }, [referralCode, onAddPhotos]); if (loading && (!publicFiles || !credentials.current)) { return ; @@ -465,10 +462,7 @@ export default function PublicCollectionGallery() { } // TODO: memo this (after the dependencies are traceable). - const context = { - credentials: credentials.current, - referralCode: referralCode.current, - }; + const context = { credentials: credentials.current }; return ( @@ -711,81 +705,61 @@ const fileListFooterHeightForProps = ({ const FileListFooter: React.FC = ({ referralCode, onAddPhotos, -}) => { - return ( - - {onAddPhotos && ( - - - - )} - - {/* Make the entire area tappable, otherwise it is hard to - get at on mobile devices. */} - - - - - ), - }} - values={{ url: "ente.io" }} - /> - - - {referralCode ? ( - +}) => ( + + {onAddPhotos && ( + + + + )} + {/* Make the entire area tappable, otherwise it is hard to + get at on mobile devices. */} + + + - - - - ) : null} - - - - ); -}; - -const AlbumFooterContainer = styled("div", { - shouldForwardProp: (propName) => propName != "hasReferral", -})<{ hasReferral: boolean }>` - margin-top: 48px; - margin-bottom: ${({ hasReferral }) => (!hasReferral ? `10px` : "0px")}; - text-align: center; - justify-content: center; -`; - -const FullStretchContainer = styled("div")( - ({ theme }) => ` - margin: 0 -24px; - width: calc(100% + 46px); - left: -24px; - @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { - margin: 0 -4px; - width: calc(100% + 6px); - left: -4px; - } - background-color: ${theme.vars.palette.accent.main}; -`, + variant="small" + component="span" + sx={{ color: "accent.main" }} + /> + ), + }} + values={{ url: "ente.io" }} + /> + + + {referralCode && ( + + + + )} + ); diff --git a/web/apps/photos/src/utils/publicCollectionGallery/index.ts b/web/apps/photos/src/utils/publicCollectionGallery/index.ts index 38b29a9cf1..828134098c 100644 --- a/web/apps/photos/src/utils/publicCollectionGallery/index.ts +++ b/web/apps/photos/src/utils/publicCollectionGallery/index.ts @@ -8,11 +8,9 @@ export interface PublicCollectionGalleryContextType { * undefined when we're in the default photos app context. */ credentials: PublicAlbumsCredentials | undefined; - referralCode: string | null; } export const PublicCollectionGalleryContext = createContext({ credentials: undefined, - referralCode: null, });