mirror of
https://github.com/ente-io/ente.git
synced 2025-08-08 07:28:26 +00:00
people list checkpoint
This commit is contained in:
parent
4e04739d54
commit
2827a166dc
@ -12,18 +12,25 @@ import {
|
||||
type ParsedMetadataDate,
|
||||
} from "@/media/file-metadata";
|
||||
import { FileType } from "@/media/file-type";
|
||||
import { UnidentifiedFaces } from "@/new/photos/components/PeopleList";
|
||||
import {
|
||||
AnnotatedFacePeopleList,
|
||||
UnclusteredFaceList,
|
||||
} from "@/new/photos/components/PeopleList";
|
||||
import { PhotoDateTimePicker } from "@/new/photos/components/PhotoDateTimePicker";
|
||||
import { photoSwipeZIndex } from "@/new/photos/components/PhotoViewer";
|
||||
import { tagNumericValue, type RawExifTags } from "@/new/photos/services/exif";
|
||||
import { annotatedFaceIDsForFile, AnnotatedFacesForFile, getAnnotatedFacesForFile, getFacesForFile, isMLEnabled } from "@/new/photos/services/ml";
|
||||
import {
|
||||
AnnotatedFacesForFile,
|
||||
getAnnotatedFacesForFile,
|
||||
isMLEnabled,
|
||||
type AnnotatedFaceID,
|
||||
} from "@/new/photos/services/ml";
|
||||
import { EnteFile } from "@/new/photos/types/file";
|
||||
import { formattedByteSize } from "@/new/photos/utils/units";
|
||||
import CopyButton from "@ente/shared/components/CodeBlock/CopyButton";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import EnteSpinner from "@ente/shared/components/EnteSpinner";
|
||||
import { getPublicMagicMetadataSync } from "@ente/shared/file-metadata";
|
||||
import useMemoSingleThreaded from "@ente/shared/hooks/useMemoSingleThreaded";
|
||||
import { formatDate, formatTime } from "@ente/shared/time/format";
|
||||
import CalendarTodayIcon from "@mui/icons-material/CalendarToday";
|
||||
import CameraOutlined from "@mui/icons-material/CameraOutlined";
|
||||
@ -98,7 +105,9 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
|
||||
const [exifInfo, setExifInfo] = useState<ExifInfo | undefined>();
|
||||
const [openRawExif, setOpenRawExif] = useState(false);
|
||||
const [annotatedFaces, setAnnotatedFaces] = useState<AnnotatedFacesForFile | undefined>();
|
||||
const [annotatedFaces, setAnnotatedFaces] = useState<
|
||||
AnnotatedFacesForFile | undefined
|
||||
>();
|
||||
|
||||
const location = useMemo(() => {
|
||||
if (file) {
|
||||
@ -108,18 +117,20 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
return exif?.parsed?.location;
|
||||
}, [file, exif]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!file) return;
|
||||
|
||||
useEffect(() => {
|
||||
let didCancel = false;
|
||||
let didCancel = false;
|
||||
|
||||
void (async () => {
|
||||
const result = await getAnnotatedFacesForFile(file);
|
||||
!didCancel && setAnnotatedFaces(result);
|
||||
})();
|
||||
|
||||
return () => { didCancel = true;}
|
||||
}, [file]);
|
||||
void (async () => {
|
||||
const result = await getAnnotatedFacesForFile(file);
|
||||
!didCancel && setAnnotatedFaces(result);
|
||||
})();
|
||||
|
||||
return () => {
|
||||
didCancel = true;
|
||||
};
|
||||
}, [file]);
|
||||
|
||||
useEffect(() => {
|
||||
setExifInfo(parseExifInfo(exif));
|
||||
@ -144,6 +155,10 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
getMapDisableConfirmationDialog(() => updateMapEnabled(false)),
|
||||
);
|
||||
|
||||
const handleSelectFace = (annotatedFaceID: AnnotatedFaceID) => {
|
||||
console.log(annotatedFaceID);
|
||||
};
|
||||
|
||||
return (
|
||||
<FileInfoSidebar open={showInfo} onClose={handleCloseInfo}>
|
||||
<Titlebar onClose={handleCloseInfo} title={t("INFO")} backIsClose />
|
||||
@ -282,11 +297,17 @@ export const FileInfo: React.FC<FileInfoProps> = ({
|
||||
</InfoItem>
|
||||
)}
|
||||
|
||||
{isMLEnabled() && (
|
||||
{isMLEnabled() && annotatedFaces && (
|
||||
<>
|
||||
{annotatedFaces?.annotatedFaceIDs.length &&
|
||||
// {/* TODO-Cluster <PhotoPeopleList file={file} /> */}
|
||||
<UnidentifiedFaces enteFile={file} />
|
||||
<AnnotatedFacePeopleList
|
||||
enteFile={file}
|
||||
annotatedFaceIDs={annotatedFaces.annotatedFaceIDs}
|
||||
onSelectFace={handleSelectFace}
|
||||
/>
|
||||
<UnclusteredFaceList
|
||||
enteFile={file}
|
||||
faceIDs={annotatedFaces.otherFaceIDs}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useIsMobileWidth } from "@/base/hooks";
|
||||
import { pt } from "@/base/i18n";
|
||||
import { faceCrop, type AnnotatedFaceID } from "@/new/photos/services/ml";
|
||||
import type { Person } from "@/new/photos/services/ml/people";
|
||||
import type { EnteFile } from "@/new/photos/types/file";
|
||||
@ -25,7 +26,7 @@ export const SearchPeopleList: React.FC<SearchPeopleListProps> = ({
|
||||
sx={{ justifyContent: people.length > 3 ? "center" : "start" }}
|
||||
>
|
||||
{people.slice(0, isMobileWidth ? 6 : 7).map((person) => (
|
||||
<SearchPeopleButton
|
||||
<SearchPersonButton
|
||||
key={person.id}
|
||||
onClick={() => onSelectPerson(person)}
|
||||
>
|
||||
@ -34,7 +35,7 @@ export const SearchPeopleList: React.FC<SearchPeopleListProps> = ({
|
||||
enteFile={person.displayFaceFile}
|
||||
placeholderDimension={87}
|
||||
/>
|
||||
</SearchPeopleButton>
|
||||
</SearchPersonButton>
|
||||
))}
|
||||
</SearchPeopleContainer>
|
||||
);
|
||||
@ -49,7 +50,7 @@ const SearchPeopleContainer = styled("div")`
|
||||
margin-block-end: 15px;
|
||||
`;
|
||||
|
||||
const SearchPeopleButton = styled(UnstyledButton)(
|
||||
const SearchPersonButton = styled(UnstyledButton)(
|
||||
({ theme }) => `
|
||||
width: 87px;
|
||||
height: 87px;
|
||||
@ -67,6 +68,13 @@ const SearchPeopleButton = styled(UnstyledButton)(
|
||||
);
|
||||
|
||||
export interface AnnotatedFacePeopleListProps {
|
||||
/**
|
||||
* The {@link EnteFile} whose information we are showing.
|
||||
*/
|
||||
enteFile: EnteFile;
|
||||
/**
|
||||
* The list of faces in the file that are associated with a person.
|
||||
*/
|
||||
annotatedFaceIDs: AnnotatedFaceID[];
|
||||
/**
|
||||
* Called when the user selects a face in the list.
|
||||
@ -80,41 +88,43 @@ export interface AnnotatedFacePeopleListProps {
|
||||
*/
|
||||
export const AnnotatedFacePeopleList: React.FC<
|
||||
AnnotatedFacePeopleListProps
|
||||
> = ({ annotatedFaceIDs, onSelectFace }) => {
|
||||
const isMobileWidth = useIsMobileWidth();
|
||||
> = ({ enteFile, annotatedFaceIDs, onSelectFace }) => {
|
||||
if (annotatedFaceIDs.length == 0) return <></>;
|
||||
|
||||
return (
|
||||
<SearchPeopleContainer
|
||||
sx={{ justifyContent: people.length > 3 ? "center" : "start" }}
|
||||
>
|
||||
{people.slice(0, isMobileWidth ? 6 : 7).map((person) => (
|
||||
<SearchPeopleButton
|
||||
key={person.id}
|
||||
onClick={() => onSelectPerson(person)}
|
||||
>
|
||||
<FaceCropImageView
|
||||
faceID={person.displayFaceID}
|
||||
enteFile={person.displayFaceFile}
|
||||
placeholderDimension={87}
|
||||
/>
|
||||
</SearchPeopleButton>
|
||||
))}
|
||||
</SearchPeopleContainer>
|
||||
<>
|
||||
<Typography variant="large" p={1}>
|
||||
{t("people")}
|
||||
</Typography>
|
||||
<AnnotatedFacePeopleContainer>
|
||||
{annotatedFaceIDs.map((annotatedFaceID) => (
|
||||
<AnnotatedFaceButton
|
||||
key={annotatedFaceID.faceID}
|
||||
onClick={() => onSelectFace(annotatedFaceID)}
|
||||
>
|
||||
<FaceCropImageView
|
||||
faceID={annotatedFaceID.faceID}
|
||||
enteFile={enteFile}
|
||||
placeholderDimension={112}
|
||||
/>
|
||||
</AnnotatedFaceButton>
|
||||
))}
|
||||
</AnnotatedFacePeopleContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const SearchPeopleContainer = styled("div")`
|
||||
const AnnotatedFacePeopleContainer = styled("div")`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin-block-start: 12px;
|
||||
margin-block-end: 15px;
|
||||
`;
|
||||
|
||||
const SearchPeopleButton = styled(UnstyledButton)(
|
||||
const AnnotatedFaceButton = styled(UnstyledButton)(
|
||||
({ theme }) => `
|
||||
width: 87px;
|
||||
height: 87px;
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
& > img {
|
||||
@ -128,7 +138,48 @@ const SearchPeopleButton = styled(UnstyledButton)(
|
||||
`,
|
||||
);
|
||||
|
||||
const FaceChipContainer = styled("div")`
|
||||
export interface UnclusteredFaceListProps {
|
||||
/**
|
||||
* The {@link EnteFile} whose information we are showing.
|
||||
*/
|
||||
enteFile: EnteFile;
|
||||
/**
|
||||
* The list of faces in the file that are not associated with a person.
|
||||
*/
|
||||
faceIDs: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the list of faces in the given file that are not associated with a
|
||||
* specific person.
|
||||
*/
|
||||
export const UnclusteredFaceList: React.FC<UnclusteredFaceListProps> = ({
|
||||
enteFile,
|
||||
faceIDs,
|
||||
}) => {
|
||||
if (faceIDs.length == 0) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="large" p={1}>
|
||||
{pt("Other faces")}
|
||||
{/*t("UNIDENTIFIED_FACES") TODO-Cluster */}
|
||||
</Typography>
|
||||
<UnclusteredFacesContainer>
|
||||
{faceIDs.map((faceID) => (
|
||||
<UnclusteredFace key={faceID}>
|
||||
<FaceCropImageView
|
||||
placeholderDimension={112}
|
||||
{...{ enteFile, faceID }}
|
||||
/>
|
||||
</UnclusteredFace>
|
||||
))}
|
||||
</UnclusteredFacesContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const UnclusteredFacesContainer = styled("div")`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
@ -138,63 +189,19 @@ const FaceChipContainer = styled("div")`
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
const FaceChip = styled("div")<{ clickable?: boolean }>`
|
||||
const UnclusteredFace = styled("div")`
|
||||
width: 112px;
|
||||
height: 112px;
|
||||
margin: 5px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
cursor: ${({ clickable }) => (clickable ? "pointer" : "normal")};
|
||||
& > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export interface PhotoPeopleListProps {
|
||||
file: EnteFile;
|
||||
onSelect?: (person: Person, index: number) => void;
|
||||
}
|
||||
|
||||
export function PhotoPeopleList() {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
interface UnidentifiedFacesProps {
|
||||
enteFile: EnteFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the list of faces in the given file that are not linked to a specific
|
||||
* person ("face cluster").
|
||||
*/
|
||||
export const UnidentifiedFaces: React.FC<UnidentifiedFacesProps> = ({
|
||||
enteFile,
|
||||
}) => {
|
||||
const [faceIDs, setFaceIDs] = useState<string[]>([]);
|
||||
|
||||
if (faceIDs.length == 0) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="large" p={1}>
|
||||
{t("UNIDENTIFIED_FACES")}
|
||||
</Typography>
|
||||
<FaceChipContainer>
|
||||
{faceIDs.map((faceID) => (
|
||||
<FaceChip key={faceID}>
|
||||
<FaceCropImageView
|
||||
placeholderDimension={112}
|
||||
{...{ enteFile, faceID }}
|
||||
/>
|
||||
</FaceChip>
|
||||
))}
|
||||
</FaceChipContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
interface FaceCropImageViewProps {
|
||||
/** The ID of the face to display. */
|
||||
faceID: string;
|
||||
|
Loading…
x
Reference in New Issue
Block a user