diff --git a/web/packages/new/photos/components/AddPersonDialog.tsx b/web/packages/new/photos/components/AddPersonDialog.tsx deleted file mode 100644 index 090f759d01..0000000000 --- a/web/packages/new/photos/components/AddPersonDialog.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import type { ModalVisibilityProps } from "@/base/components/utils/modal"; -import { pt } from "@/base/i18n"; -import { addCGroup, addClusterToCGroup } from "@/new/photos/services/ml"; -import { ensure } from "@/utils/ensure"; -import { - Dialog, - DialogContent, - DialogTitle, - styled, - Typography, - useMediaQuery, -} from "@mui/material"; -import { t } from "i18next"; -import React, { useState } from "react"; -import type { FaceCluster } from "../services/ml/cluster"; -import type { CGroupPerson, Person } from "../services/ml/people"; -import { SpaceBetweenFlex, type ButtonishProps } from "./mui"; -import { DialogCloseIconButton } from "./mui/Dialog"; -import { SingleInputDialog } from "./SingleInputForm"; -import { - ItemCard, - LargeTileButton, - LargeTilePlusOverlay, - LargeTileTextOverlay, -} from "./Tiles"; -import { useWrapAsyncOperation } from "./use-wrap-async"; - -type AddPersonDialogProps = ModalVisibilityProps & { - /** - * The list of people from show the existing named people. - */ - people: Person[]; - /** - * The cluster to add to the selected person (existing or new). - */ - cluster: FaceCluster; -}; - -/** - * A dialog allowing the user to select one of the existing named persons they - * have, or create a new one, and then associate the provided cluster to it, - * creating or updating a remote "person". - */ -export const AddPersonDialog: React.FC = ({ - open, - onClose, - people, - cluster, -}) => { - const isFullScreen = useMediaQuery("(max-width: 490px)"); - - const [openNameInput, setOpenNameInput] = useState(false); - - const cgroupPeople: CGroupPerson[] = people.filter( - (p) => p.type != "cluster", - ); - - const handleAddPerson = () => setOpenNameInput(true); - - const handleSelectPerson = useWrapAsyncOperation((id: string) => - addClusterToCGroup( - ensure(cgroupPeople.find((p) => p.id == id)).cgroup, - cluster, - ), - ); - - const handleAddPersonWithName = (name: string) => addCGroup(name, cluster); - - // [Note: Calling setState during rendering] - // - // Calling setState during rendering should be avoided when there are - // cleaner alternatives, but it is not completely verboten, and it has - // documented semantics: - // - // > React will discard the currently rendering component's output and - // > immediately attempt to render it again with the new state. - // > - // > https://react.dev/reference/react/useState - - // If we're opened without any existing people that can be selected, jump - // directly to the add person dialog. - if (open && !openNameInput && !cgroupPeople.length) { - onClose(); - setOpenNameInput(true); - return <>; - } - - return ( - <> - - - - {pt("Add name")} - - - - - - {cgroupPeople.map((person) => ( - - ))} - - - - setOpenNameInput(false)} - title={pt("New person") /* TODO-Cluster */} - label={pt("Add name")} - placeholder={t("enter_name")} - autoComplete="name" - autoFocus - submitButtonTitle={t("add")} - onSubmit={handleAddPersonWithName} - /> - - ); -}; - -const DialogContent_ = styled(DialogContent)` - display: flex; - flex-wrap: wrap; - gap: 4px; -`; - -interface PersonButtonProps { - person: Person; - onPersonClick: (personID: string) => void; -} - -const PersonButton: React.FC = ({ - person, - onPersonClick, -}) => ( - onPersonClick(person.id)} - > - - {person.name ?? ""} - - -); - -const AddPerson: React.FC = ({ onClick }) => ( - - {pt("New person")} - + - -); diff --git a/web/packages/new/photos/components/gallery/PeopleHeader.tsx b/web/packages/new/photos/components/gallery/PeopleHeader.tsx index 4e9c9014a1..94a10a39bb 100644 --- a/web/packages/new/photos/components/gallery/PeopleHeader.tsx +++ b/web/packages/new/photos/components/gallery/PeopleHeader.tsx @@ -13,6 +13,8 @@ import { useIsSmallWidth } from "@/base/hooks"; import { pt } from "@/base/i18n"; import log from "@/base/log"; import { + addCGroup, + addClusterToCGroup, applyPersonSuggestionUpdates, deleteCGroup, ignoreCluster, @@ -27,6 +29,7 @@ import { type PersonSuggestionUpdates, type PreviewableCluster, } from "@/new/photos/services/ml/people"; +import { ensure } from "@/utils/ensure"; import OverflowMenu from "@ente/shared/components/OverflowMenu/menu"; import { OverflowMenuOption } from "@ente/shared/components/OverflowMenu/option"; import AddIcon from "@mui/icons-material/Add"; @@ -46,18 +49,28 @@ import { List, ListItem, Stack, + styled, ToggleButton, ToggleButtonGroup, Tooltip, Typography, + useMediaQuery, } from "@mui/material"; import { t } from "i18next"; -import React, { useEffect, useReducer } from "react"; +import React, { useEffect, useReducer, useState } from "react"; +import type { FaceCluster } from "../../services/ml/cluster"; import { useAppContext } from "../../types/context"; -import { AddPersonDialog } from "../AddPersonDialog"; -import { SpaceBetweenFlex } from "../mui"; +import { SpaceBetweenFlex, type ButtonishProps } from "../mui"; +import { DialogCloseIconButton } from "../mui/Dialog"; import { SuggestionFaceList } from "../PeopleList"; import { SingleInputDialog } from "../SingleInputForm"; +import { + ItemCard, + LargeTileButton, + LargeTilePlusOverlay, + LargeTileTextOverlay, +} from "../Tiles"; +import { useWrapAsyncOperation } from "../use-wrap-async"; import type { GalleryBarImplProps } from "./BarImpl"; import { GalleryItemsHeaderAdapter, GalleryItemsSummary } from "./ListHeader"; @@ -147,7 +160,8 @@ const CGroupPersonHeader: React.FC = ({ }); // While technically it is possible for the cgroup not to have a name, logic - // wise we shouldn't be ending up here without a name. + // wise we shouldn't be ending up here without a name (this state is + // expected to be reached only for named persons). const name = cgroup.data.name ?? ""; return ( @@ -197,7 +211,6 @@ const CGroupPersonHeader: React.FC = ({ submitButtonTitle={t("rename")} onSubmit={handleRename} /> - = ({ ); }; +type AddPersonDialogProps = ModalVisibilityProps & { + /** + * The list of people from show the existing named people. + */ + people: Person[]; + /** + * The cluster to add to the selected person (existing or new). + */ + cluster: FaceCluster; +}; + +/** + * A dialog allowing the user to select one of the existing named persons they + * have, or create a new one, and then associate the provided cluster to it, + * creating or updating a remote "person". + */ +const AddPersonDialog: React.FC = ({ + open, + onClose, + people, + cluster, +}) => { + const isFullScreen = useMediaQuery("(max-width: 490px)"); + + const [openNameInput, setOpenNameInput] = useState(false); + + const cgroupPeople: CGroupPerson[] = people.filter( + (p) => p.type != "cluster", + ); + + const handleAddPerson = () => setOpenNameInput(true); + + const handleSelectPerson = useWrapAsyncOperation((id: string) => + addClusterToCGroup( + ensure(cgroupPeople.find((p) => p.id == id)).cgroup, + cluster, + ), + ); + + const handleAddPersonWithName = (name: string) => addCGroup(name, cluster); + + // [Note: Calling setState during rendering] + // + // Calling setState during rendering should be avoided when there are + // cleaner alternatives, but it is not completely verboten, and it has + // documented semantics: + // + // > React will discard the currently rendering component's output and + // > immediately attempt to render it again with the new state. + // > + // > https://react.dev/reference/react/useState + + // If we're opened without any existing people that can be selected, jump + // directly to the add person dialog. + if (open && !openNameInput && !cgroupPeople.length) { + onClose(); + setOpenNameInput(true); + return <>; + } + + return ( + <> + + + + {pt("Add name")} + + + + + + {cgroupPeople.map((person) => ( + + ))} + + + + setOpenNameInput(false)} + title={pt("New person") /* TODO-Cluster */} + label={pt("Add name")} + placeholder={t("enter_name")} + autoComplete="name" + autoFocus + submitButtonTitle={t("add")} + onSubmit={handleAddPersonWithName} + /> + + ); +}; + +const DialogContent_ = styled(DialogContent)` + display: flex; + flex-wrap: wrap; + gap: 4px; +`; + +interface PersonButtonProps { + person: Person; + onPersonClick: (personID: string) => void; +} + +const PersonButton: React.FC = ({ + person, + onPersonClick, +}) => ( + onPersonClick(person.id)} + > + + {person.name ?? ""} + + +); + +const AddPerson: React.FC = ({ onClick }) => ( + + {pt("New person")} + + + +); + type SuggestionsDialogProps = ModalVisibilityProps & { person: CGroupPerson; };