[desktop] People - Part x/x (#3317)

This commit is contained in:
Manav Rathi
2024-09-17 20:33:49 +05:30
committed by GitHub
5 changed files with 81 additions and 128 deletions

View File

@@ -270,7 +270,7 @@ export const FileInfo: React.FC<FileInfoProps> = ({
{isMLEnabled() && (
<>
{/* <PhotoPeopleList file={file} /> */}
{/* TODO-Cluster <PhotoPeopleList file={file} /> */}
<UnidentifiedFaces enteFile={file} />
</>
)}

View File

@@ -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<PeopleListProps> = ({
people,
maxRows,
onSelect,
}) => {
const isMobileWidth = useIsMobileWidth();
// TODO-Cluster: FaceCropImageView has hardcoded placeholder dimensions
return (
<FaceChipContainer style={{ maxHeight: maxRows * 122 + 28 }}>
{people.map((person, index) => (
<FaceChip
<SearchFaceChipContainer style={{ maxHeight: maxRows * 87 + 28 }}>
{people.slice(0, isMobileWidth ? 6 : 7).map((person, index) => (
<SearchFaceChip
key={person.id}
clickable={!!onSelect}
onClick={() => onSelect && onSelect(person, index)}
>
<FaceCropImageView
faceID={person.displayFaceID}
enteFile={person.displayFaceFile}
/>
</FaceChip>
</SearchFaceChip>
))}
</FaceChipContainer>
</SearchFaceChipContainer>
);
};
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;

View File

@@ -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<EmptyStateProps> = () => {
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<EmptyStateProps> = () => {
break;
}
// TODO-Cluster this is where it'll go.
// const people = wipPersons();
return (
<Box>
<Typography variant="mini" sx={{ textAlign: "left" }}>
{label}
</Typography>
<Box sx={{ textAlign: "left" }}>
{people && people.length > 0 && (
<>
<PeopleHeader />
<PeopleList
people={people}
maxRows={2}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onSelect={(...args: any) => console.log(args)}
/>
</>
)}
<Typography variant="mini">{label}</Typography>
</Box>
);
// 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 (
// <SelectComponents.Menu {...props}>
// <Box my={1}>
// {isMLEnabled() &&
// indexStatus &&
// (people && people.length > 0 ? (
// <Box>
// <Legend>{t("people")}</Legend>
// </Box>
// ) : (
// <Box height={6} />
// ))}
// {isMLEnabled() && indexStatus && (
// <Box>
// <Caption>{indexStatusSuggestion.label}</Caption>
// </Box>
// )}
// {people && people.length > 0 && (
// <Row> // "@ente/shared/components/Container"
// <PeopleList // @/new/photos/components/PeopleList
// people={people}
// maxRows={2}
// onSelect={(_, index) => {
// }}
// />
// </Row>
// )}
// </Box>
// {props.children}
// </SelectComponents.Menu>
// );
};
// TODO-Cluster
// const Legend = styled("span")`
// font-size: 20px;
// color: #ddd;
// display: inline;
// padding: 0px 12px;
// `;
/*
TODO: Cluster
export async function getAllPeopleSuggestion(): Promise<Array<Suggestion>> {
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<Person> = []; // await mlIDbStorage.getAllPeople();
// people = await wipCluster();
// // await mlPeopleStore.iterate<Person, void>((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 (
<Stack direction="row" sx={{ cursor: "pointer" }} onClick={handleClick}>
<Typography color="text.base" variant="large">
{t("people")}
</Typography>
<ChevronRightIcon />
</Stack>
);
};
const Option: React.FC<OptionProps<SearchOption, false>> = (props) => (
<SelectComponents.Option {...props}>

View File

@@ -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<Person[] | undefined> => {
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;
};

View File

@@ -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",