diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx index 21675c36a8..d996b72020 100644 --- a/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/index.tsx @@ -270,7 +270,7 @@ export const FileInfo: React.FC = ({ {isMLEnabled() && ( <> - {/* */} + {/* TODO-Cluster */} )} diff --git a/web/packages/new/photos/components/PeopleList.tsx b/web/packages/new/photos/components/PeopleList.tsx index 6de6dadd1b..161539a52a 100644 --- a/web/packages/new/photos/components/PeopleList.tsx +++ b/web/packages/new/photos/components/PeopleList.tsx @@ -1,3 +1,4 @@ +import { useIsMobileWidth } from "@/base/hooks"; import type { Person } from "@/new/photos/services/ml"; import { faceCrop, unidentifiedFaceIDs } from "@/new/photos/services/ml"; import type { EnteFile } from "@/new/photos/types/file"; @@ -6,34 +7,64 @@ import { t } from "i18next"; import React, { useEffect, useState } from "react"; export interface PeopleListProps { + /** The list of {@link Person} entities to show. */ people: Person[]; + /** Limit to display to whatever fits within {@link maxRows} rows. */ maxRows: number; + /** Optional callback invoked when a particular person is selected. */ onSelect?: (person: Person, index: number) => void; } - +/** + * Shows a list of {@link Person} (named cluster groups). + */ export const PeopleList: React.FC = ({ people, maxRows, onSelect, }) => { + const isMobileWidth = useIsMobileWidth(); + // TODO-Cluster: FaceCropImageView has hardcoded placeholder dimensions return ( - - {people.map((person, index) => ( - + {people.slice(0, isMobileWidth ? 6 : 7).map((person, index) => ( + onSelect && onSelect(person, index)} > - + ))} - + ); }; +const SearchFaceChipContainer = styled("div")` + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: center; + gap: 5px; + margin-block: 16px; + /* On very small (~ < 375px) mobile screens 6 faces won't fit in 2 rows. + Clip the third one. */ + overflow: hidden; +`; + +const SearchFaceChip = styled("div")` + width: 87px; + height: 87px; + border-radius: 50%; + overflow: hidden; + cursor: "pointer"; + & > img { + width: 100%; + height: 100%; + } +`; + const FaceChipContainer = styled("div")` display: flex; flex-wrap: wrap; diff --git a/web/packages/new/photos/components/SearchBar.tsx b/web/packages/new/photos/components/SearchBar.tsx index 276dff408b..8d67c0a37a 100644 --- a/web/packages/new/photos/components/SearchBar.tsx +++ b/web/packages/new/photos/components/SearchBar.tsx @@ -1,15 +1,19 @@ import { assertionFailed } from "@/base/assert"; import { useIsMobileWidth } from "@/base/hooks"; +import log from "@/base/log"; import { ItemCard, ResultPreviewTile } from "@/new/photos/components/ItemCards"; import { isMLSupported, mlStatusSnapshot, mlStatusSubscribe, + peopleSnapshot, + peopleSubscribe, } from "@/new/photos/services/ml"; import { searchOptionsForString } from "@/new/photos/services/search"; import type { SearchOption } from "@/new/photos/services/search/types"; import { nullToUndefined } from "@/utils/transform"; import CalendarIcon from "@mui/icons-material/CalendarMonth"; +import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import CloseIcon from "@mui/icons-material/Close"; import ImageIcon from "@mui/icons-material/Image"; import LocationIcon from "@mui/icons-material/LocationOn"; @@ -37,6 +41,7 @@ import { type StylesConfig, } from "react-select"; import AsyncSelect from "react-select/async"; +import { PeopleList } from "./PeopleList"; export interface SearchBarProps { /** @@ -243,7 +248,11 @@ const createSelectStyles = ({ cursor: "text", }, }), - input: (styles) => ({ ...styles, color: colors.text.base }), + input: (styles) => ({ + ...styles, + color: colors.text.base, + overflowX: "hidden", + }), menu: (style) => ({ ...style, // Suppress the default margin at the top. @@ -268,6 +277,7 @@ const createSelectStyles = ({ ...style, color: colors.text.muted, whiteSpace: "nowrap", + overflowX: "hidden", }), // Hide some things we don't need. dropdownIndicator: (style) => ({ ...style, display: "none" }), @@ -358,7 +368,10 @@ interface EmptyStateProps { */ const EmptyState: React.FC = () => { const mlStatus = useSyncExternalStore(mlStatusSubscribe, mlStatusSnapshot); + const people = useSyncExternalStore(peopleSubscribe, peopleSnapshot); + log.debug(() => ["EmptyState", { mlStatus, people }]); + // TODO-Cluster if (!mlStatus || mlStatus.phase == "disabled") { assertionFailed(); return <>; @@ -383,108 +396,35 @@ const EmptyState: React.FC = () => { break; } - // TODO-Cluster this is where it'll go. - // const people = wipPersons(); - return ( - - - {label} - + + {people && people.length > 0 && ( + <> + + console.log(args)} + /> + + )} + {label} ); - - // TODO-Cluster - // const options = props.selectProps.options as SearchOption[]; - // const peopleSuggestions = options.filter( - // (o) => o.type === SuggestionType.PERSON, - // ); - // const people = peopleSuggestions.map((o) => o.value as Person); - // return ( - // - // - // {isMLEnabled() && - // indexStatus && - // (people && people.length > 0 ? ( - // - // {t("people")} - // - // ) : ( - // - // ))} - // {isMLEnabled() && indexStatus && ( - // - // {indexStatusSuggestion.label} - // - // )} - // {people && people.length > 0 && ( - // // "@ente/shared/components/Container" - // { - // }} - // /> - // - // )} - // - // {props.children} - // - // ); }; -// TODO-Cluster -// const Legend = styled("span")` -// font-size: 20px; -// color: #ddd; -// display: inline; -// padding: 0px 12px; -// `; - -/* -TODO: Cluster - -export async function getAllPeopleSuggestion(): Promise> { - try { - const people = await getAllPeople(200); - return people.map((person) => ({ - label: person.name, - type: SuggestionType.PERSON, - value: person, - hide: true, - })); - } catch (e) { - log.error("getAllPeopleSuggestion failed", e); - return []; - } -} - -async function getAllPeople(limit: number = undefined) { - return (await wipPersons()).slice(0, limit); - // TODO-Clustetr - // if (done) return []; - - // done = true; - // if (process.env.NEXT_PUBLIC_ENTE_WIP_CL_FETCH) { - // await syncCGroups(); - // const people = await clusterGroups(); - // log.debug(() => ["people", { people }]); - // } - - // let people: Array = []; // await mlIDbStorage.getAllPeople(); - // people = await wipCluster(); - // // await mlPeopleStore.iterate((person) => { - // // people.push(person); - // // }); - // people = people ?? []; - // const result = people - // .sort((p1, p2) => p2.files.length - p1.files.length) - // .slice(0, limit); - // // log.debug(() => ["getAllPeople", result]); - - // return result; -} -*/ +const PeopleHeader: React.FC = () => { + const handleClick = () => console.log("click"); + return ( + + + {t("people")} + + + + ); +}; const Option: React.FC> = (props) => ( diff --git a/web/packages/new/photos/services/ml/index.ts b/web/packages/new/photos/services/ml/index.ts index a36bdc02a1..a57bb2c03f 100644 --- a/web/packages/new/photos/services/ml/index.ts +++ b/web/packages/new/photos/services/ml/index.ts @@ -440,6 +440,7 @@ export const wipClusterDebugPageContents = async ( _wip_isClustering = false; _wip_people = people; triggerStatusUpdate(); + triggerPeopleUpdate(); return { clusters, @@ -654,7 +655,7 @@ export const peopleSubscribe = (onChange: () => void): (() => void) => { export const peopleSnapshot = (): Person[] | undefined => { const result = _state.peopleSnapshot; // We don't have it yet, trigger an update. - if (!result) triggerPeopleUpdate(); + // if (!result) triggerPeopleUpdate(); return result; }; @@ -678,9 +679,9 @@ const setPeopleSnapshot = (snapshot: Person[] | undefined) => { * people might be updated in a push based manner. */ const getPeople = async (): Promise => { - if (!_state.isMLEnabled) return undefined; + if (!_state.isMLEnabled) return []; // TODO-Cluster additional check for now as it is heavily WIP. - if (!process.env.NEXT_PUBLIC_ENTE_WIP_CL) return undefined; + if (!process.env.NEXT_PUBLIC_ENTE_WIP_CL) return []; if (!(await wipClusterEnable())) return []; return _wip_people; }; diff --git a/web/packages/shared/components/Container.tsx b/web/packages/shared/components/Container.tsx index cd0dca4914..d275837db8 100644 --- a/web/packages/shared/components/Container.tsx +++ b/web/packages/shared/components/Container.tsx @@ -10,21 +10,6 @@ export const VerticallyCentered = styled(Box)` overflow: auto; `; -export const Row = styled("div")` - min-height: 32px; - display: flex; - align-items: center; - margin-bottom: ${({ theme }) => theme.spacing(2)}; - flex: 1; -`; - -export const Value = styled("div")<{ width?: string }>` - display: flex; - justify-content: flex-start; - align-items: center; - width: ${(props) => props.width ?? "30%"}; -`; - export const FlexWrapper = styled(Box)` display: flex; width: 100%; @@ -59,10 +44,6 @@ export const HorizontalFlex = styled(Box)({ display: "flex", }); -export const VerticalFlex = styled(HorizontalFlex)({ - flexDirection: "column", -}); - export const VerticallyCenteredFlex = styled(HorizontalFlex)({ alignItems: "center", display: "flex",