[desktop] People - Part x/x (#3317)
This commit is contained in:
@@ -270,7 +270,7 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
|
||||
{isMLEnabled() && (
|
||||
<>
|
||||
{/* <PhotoPeopleList file={file} /> */}
|
||||
{/* TODO-Cluster <PhotoPeopleList file={file} /> */}
|
||||
<UnidentifiedFaces enteFile={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;
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user