diff --git a/TODO b/TODO index f65634f6..935fb368 100644 --- a/TODO +++ b/TODO @@ -4,9 +4,7 @@ - medium: fix public user list and public photos for authenticated user - medium: highlight sidebar menu when children menu page is active - medium: remove the use of useMediaquery -- medium: fix lightbox editor caption - medium: Fix Modal Person Edit button and hover -- low: fix photo list dropdown filter click area - low: rework mobile top menu for usability (upload, search, etc.) - low: fix warnings (in console) related to defaultProps (photo list view, etc.) - low: select language and "help translating misaligned" diff --git a/src/api_client/albums/people.ts b/src/api_client/albums/people.ts index 9a881361..c090aacd 100644 --- a/src/api_client/albums/people.ts +++ b/src/api_client/albums/people.ts @@ -8,7 +8,7 @@ export const PersonResponseSchema = z.object({ name: z.string(), face_url: z.string().nullable(), face_count: z.number(), - face_photo_url: z.string().nullable(), + face_photo_url: z.string(), video: z.boolean().optional(), id: z.number(), newPersonName: z.string().optional(), @@ -67,7 +67,7 @@ export const peopleAlbumsApi = api method: "PATCH", body: { newPersonName }, }), - transformResponse: (response, meta, query) => { + transformResponse: (_response, _meta, query) => { notification.renamePerson(query.personName, query.newPersonName); }, }), diff --git a/src/api_client/api.ts b/src/api_client/api.ts index 7cf8bfa2..6be57f91 100644 --- a/src/api_client/api.ts +++ b/src/api_client/api.ts @@ -19,6 +19,7 @@ import { CompletePersonFaceList, DeleteFacesRequest, DeleteFacesResponse, + FacesTab, IncompletePersonFaceListRequest, IncompletePersonFaceListResponse, PersonFaceList, @@ -171,7 +172,7 @@ export const api = createApi({ [Endpoints.fetchUserSelfDetails]: builder.query({ query: userId => `/user/${userId}/`, transformResponse: (response: string) => UserSchema.parse(response), - providesTags: (result, error, id) => [{ type: "UserSelfDetails" as const, id }], + providesTags: (_result, _error, id) => [{ type: "UserSelfDetails" as const, id }], }), [Endpoints.fetchUserList]: builder.query({ query: () => ({ @@ -232,8 +233,7 @@ export const api = createApi({ }); return newFacesList; }, - providesTags: (result, error, { inferred, method, orderBy }) => - result ? result.map(({ id }) => ({ type: "Faces", id })) : ["Faces"], + providesTags: result => (result ? result.map(({ id }) => ({ type: "Faces", id })) : ["Faces"]), }), [Endpoints.fetchFaces]: builder.query({ query: ({ person, page = 0, inferred = false, orderBy = "confidence", method, minConfidence }) => ({ @@ -271,7 +271,7 @@ export const api = createApi({ ) ); }, - providesTags: (result, error, { person }) => [{ type: "Faces", id: person }], + providesTags: (_result, _error, { person }) => [{ type: "Faces", id: person }], }), [Endpoints.deleteFaces]: builder.mutation({ query: ({ faceIds }) => ({ @@ -285,17 +285,18 @@ export const api = createApi({ }, async onQueryStarted({ faceIds }, { dispatch, queryFulfilled, getState }) { const { activeTab, analysisMethod, orderBy } = getState().face; - const incompleteFacesArgs = { inferred: activeTab !== "labeled", method: analysisMethod, orderBy: orderBy }; + const incompleteFacesArgs = { inferred: activeTab !== FacesTab.enum.labeled, method: analysisMethod, orderBy }; const patchIncompleteFaces = dispatch( api.util.updateQueryData(Endpoints.incompleteFaces, incompleteFacesArgs, draft => { + /* draft is wrapper with immer */ + /* eslint-disable no-param-reassign */ draft.forEach(personGroup => { personGroup.faces = personGroup.faces.filter(face => !faceIds.includes(face.id)); }); draft.forEach(personGroup => { personGroup.face_count = personGroup.faces.length; }); - draft = draft.filter(personGroup => personGroup.faces.length > 0); }) ); @@ -315,7 +316,7 @@ export const api = createApi({ }), transformResponse: response => { const payload = SetFacesLabelResponse.parse(response); - notification.addFacesToPerson(payload.results[0].person_name, payload.results.length); + notification.addFacesToPerson(payload.results[0].person_name ?? "unknown", payload.results.length); return payload; }, // To-Do: Handle optimistic updates by updating the cache. The issue is that there are multiple caches that need to be updated, where we need to remove the faces from the incomplete faces cache and add them to the labeled faces cache. diff --git a/src/components/CustomSearch.tsx b/src/components/CustomSearch.tsx index 9b48b73c..f7fe7a6c 100644 --- a/src/components/CustomSearch.tsx +++ b/src/components/CustomSearch.tsx @@ -1,5 +1,4 @@ import { Autocomplete, Avatar, Group, Text } from "@mantine/core"; -// import type { AutocompleteItem } from "@mantine/core"; import { useInterval, useViewportSize } from "@mantine/hooks"; import { IconAlbum as Album, @@ -63,7 +62,6 @@ function toPeopleSuggestion(item: Person) { const SearchSuggestionItem = forwardRef( ({ icon = , value, ...rest }: SearchSuggestion, ref) => ( - /* eslint-disable react/jsx-props-no-spreading */
{cloneElement(icon as React.ReactElement, { size: 20 })} diff --git a/src/components/SiteSearch.module.css b/src/components/SiteSearch.module.css new file mode 100644 index 00000000..5eea9fca --- /dev/null +++ b/src/components/SiteSearch.module.css @@ -0,0 +1,13 @@ +.clearSearch { + cursor: pointer; + width: 1rem; + height: 1rem; +} + +.person { + transition: transform 0.3s; + + &:hover { + transform: scale(1.3); + } +} diff --git a/src/components/SiteSearch.tsx b/src/components/SiteSearch.tsx index fe39815a..cfa9292e 100644 --- a/src/components/SiteSearch.tsx +++ b/src/components/SiteSearch.tsx @@ -1,4 +1,4 @@ -import { ActionIcon, Combobox, Group, Image, InputBase, Loader, Popover, Text, useCombobox } from "@mantine/core"; +import { ActionIcon, Box, Combobox, Group, Image, InputBase, Loader, Popover, Text, useCombobox } from "@mantine/core"; import { useViewportSize } from "@mantine/hooks"; import { IconAlbum, IconMap, IconSearch, IconTag, IconUser, IconX } from "@tabler/icons-react"; import React from "react"; @@ -101,10 +101,10 @@ export function SiteSearch() { )) .concat([ - + {searchOptionsPeople.map(person => ( - + + onOptionSubmit(option)}> - {isLoading ? {t("loading")} : searchOptions} + {isLoading ? {t("loading")} : searchOptions} -
+ ); } diff --git a/src/components/album/ModalAlbumEdit.tsx b/src/components/album/ModalAlbumEdit.tsx index ac8df099..3f94905a 100644 --- a/src/components/album/ModalAlbumEdit.tsx +++ b/src/components/album/ModalAlbumEdit.tsx @@ -1,7 +1,7 @@ import { Button, Divider, Group, Modal, Stack, Text, TextInput, Title, UnstyledButton } from "@mantine/core"; import { useMediaQuery } from "@mantine/hooks"; import { DateTime } from "luxon"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { @@ -27,11 +27,6 @@ export function ModalAlbumEdit(props: Props) { const { data: albumsUserList = [] } = useFetchUserAlbumsQuery(); const [createUserAlbum] = useCreateUserAlbumMutation(); const [addPhotoToUserAlbum] = useAddPhotoToUserAlbumMutation(); - const [filteredUserAlbumList, setFilteredUserAlbumList] = useState(albumsUserList); - - useEffect(() => { - setFilteredUserAlbumList(albumsUserList.filter(el => fuzzyMatch(newAlbumTitle, el.title))); - }, [newAlbumTitle, albumsUserList]); return ( - {filteredUserAlbumList.length > 0 && - filteredUserAlbumList.map(item => ( + {albumsUserList + .filter(el => fuzzyMatch(newAlbumTitle, el.title)) + .map(item => ( { diff --git a/src/components/facedashboard/FaceTooltip.tsx b/src/components/facedashboard/FaceTooltip.tsx index 3cd8e368..20cea7f4 100644 --- a/src/components/facedashboard/FaceTooltip.tsx +++ b/src/components/facedashboard/FaceTooltip.tsx @@ -21,8 +21,8 @@ export function FaceTooltip({ tooltipOpened, probability, timestamp, children = ? t("settings.confidencepercentage", { percentage: (probability * 100).toFixed(1) }) : null; - const dateTimeLabel = DateTime.fromISO(timestamp ? timestamp : "undefined").isValid - ? DateTime.fromISO(timestamp ? timestamp : "undefined") + const dateTimeLabel = DateTime.fromISO(timestamp || "undefined").isValid + ? DateTime.fromISO(timestamp || "undefined") .setLocale(i18nResolvedLanguage()) .toLocaleString(DateTime.DATETIME_MED) : null; diff --git a/src/components/lightbox/PersonDetailComponent.tsx b/src/components/lightbox/PersonDetailComponent.tsx index b1047e21..8af39040 100644 --- a/src/components/lightbox/PersonDetailComponent.tsx +++ b/src/components/lightbox/PersonDetailComponent.tsx @@ -1,10 +1,5 @@ import { ActionIcon, Avatar, Button, Group, Indicator, Text, Tooltip } from "@mantine/core"; -import { - IconEdit as Edit, - IconTrash as Trash, - IconUserCheck as UserCheck, - IconUserOff as UserOff, -} from "@tabler/icons-react"; +import { IconEdit, IconTrash, IconUserCheck, IconUserOff } from "@tabler/icons-react"; import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { push } from "redux-first-history"; @@ -16,36 +11,30 @@ import { useAppDispatch } from "../../store/store"; import { calculateProbabiltyColor } from "../facedashboard/FaceComponent"; import { FaceTooltip } from "../facedashboard/FaceTooltip"; -type PersonDetailProps = { +type Props = { person: any; isPublic: boolean; setFaceLocation: (face: any) => void; onPersonEdit: (faceId: string, faceUrl: string) => void; - notThisPerson: (faceId: string) => void; + notThisPerson: (faceId: number) => void; }; -export const PersonDetail: React.FC = ({ - person, - isPublic, - setFaceLocation, - onPersonEdit, - notThisPerson, -}) => { +export function PersonDetail({ person, isPublic, setFaceLocation, onPersonEdit, notThisPerson }: Props) { const { t } = useTranslation(); const dispatch = useAppDispatch(); const [tooltipOpened, setTooltipOpened] = useState(false); return ( setFaceLocation(person.location)} onMouseLeave={() => setFaceLocation(null)} > {!isPublic && person.type !== "user" && ( @@ -74,21 +61,21 @@ export const PersonDetail: React.FC = ({ variant="light" color="green" > - + )} {!isPublic && ( onPersonEdit(person.face_id, person.face_url)} variant="light"> - + )} {!isPublic && ( notThisPerson(person.face_id)}> - + )} @@ -102,10 +89,10 @@ export const PersonDetail: React.FC = ({ notification.deleteFaces(1); }} > - + )} ); -}; +} diff --git a/src/components/lightbox/Sidebar.tsx b/src/components/lightbox/Sidebar.tsx index dff44f3c..3e9fa9e0 100644 --- a/src/components/lightbox/Sidebar.tsx +++ b/src/components/lightbox/Sidebar.tsx @@ -29,13 +29,13 @@ export function Sidebar(props: Props) { const dispatch = useAppDispatch(); const [personEditOpen, setPersonEditOpen] = useState(false); const [selectedFaces, setSelectedFaces] = useState([]); - const { isPublic, closeSidepanel, setFaceLocation } = props; + const { isPublic, closeSidepanel, setFaceLocation, id } = props; - const photoDetail: PhotoType = useAppSelector(store => store.photoDetails.photoDetails[props.id]); + const photoDetail: PhotoType = useAppSelector(store => store.photoDetails.photoDetails[id]); const theme = useMantineTheme(); const { colorScheme } = useMantineColorScheme(); - const notThisPerson = faceId => { + const notThisPerson = (faceId: number) => { const ids = [faceId]; dispatch(api.endpoints.setFacesPersonLabel.initiate({ faceIds: ids, personName: "Unknown - Other" })); notification.removeFacesFromPerson(ids.length); diff --git a/src/components/lightbox/Toolbar.tsx b/src/components/lightbox/Toolbar.tsx index e4d5eef3..20f127bd 100644 --- a/src/components/lightbox/Toolbar.tsx +++ b/src/components/lightbox/Toolbar.tsx @@ -45,7 +45,7 @@ export function Toolbar(props: Props) { } } return ( - togglePlay()} variant="transparent"> + togglePlay()} variant="transparent"> {playerLoading && } {!playerLoading && playerPlaying ? : } diff --git a/src/components/modals/ModalUserEdit.tsx b/src/components/modals/ModalUserEdit.tsx index bcf096cb..d83fb8b1 100644 --- a/src/components/modals/ModalUserEdit.tsx +++ b/src/components/modals/ModalUserEdit.tsx @@ -256,7 +256,6 @@ export function ModalUserEdit(props: Props) { leftSection={} placeholder={t("login.usernameplaceholder")} name="username" - /* eslint-disable-next-line react/jsx-props-no-spreading */ {...form.getInputProps("username")} /> } placeholder={t("settings.emailplaceholder")} name="email" - /* eslint-disable-next-line react/jsx-props-no-spreading */ {...form.getInputProps("email")} /> } placeholder={t("settings.firstnameplaceholder")} name="first_name" - /* eslint-disable-next-line react/jsx-props-no-spreading */ {...form.getInputProps("first_name")} /> } placeholder={t("settings.lastnameplaceholder")} name="last_name" - /* eslint-disable-next-line react/jsx-props-no-spreading */ {...form.getInputProps("last_name")} /> @@ -304,7 +300,6 @@ export function ModalUserEdit(props: Props) { required={firstTimeSetup} placeholder={scanDirectoryPlaceholder} name="scan_directory" - /* eslint-disable-next-line react/jsx-props-no-spreading */ {...form.getInputProps("scan_directory")} /> diff --git a/src/components/react-pig/components/GroupHeader/GroupHeader.jsx b/src/components/react-pig/components/GroupHeader/GroupHeader.jsx index 191fe3af..9d2f8668 100644 --- a/src/components/react-pig/components/GroupHeader/GroupHeader.jsx +++ b/src/components/react-pig/components/GroupHeader/GroupHeader.jsx @@ -39,7 +39,7 @@ GroupHeader.propTypes = { location: PropTypes.string, date: PropTypes.string, }).isRequired, - activeTileUrl: PropTypes.string.isRequired, + activeTileUrl: PropTypes.string, }; export default GroupHeader; diff --git a/src/components/react-pig/components/Tile/Tile.jsx b/src/components/react-pig/components/Tile/Tile.jsx index 3719894d..3f0494f4 100644 --- a/src/components/react-pig/components/Tile/Tile.jsx +++ b/src/components/react-pig/components/Tile/Tile.jsx @@ -214,7 +214,7 @@ Tile.propTypes = { containerWidth: PropTypes.number.isRequired, containerOffsetTop: PropTypes.number.isRequired, getUrl: PropTypes.func.isRequired, - activeTileUrl: PropTypes.string.isRequired, + activeTileUrl: PropTypes.string, handleClick: PropTypes.func.isRequired, handleSelection: PropTypes.func.isRequired, selected: PropTypes.bool.isRequired, diff --git a/src/components/react-pig/index.jsx b/src/components/react-pig/index.jsx index f8176c41..e1e3ff19 100644 --- a/src/components/react-pig/index.jsx +++ b/src/components/react-pig/index.jsx @@ -343,6 +343,6 @@ Pig.propTypes = { updateGroups: PropTypes.func, updateItems: PropTypes.func, selectedItems: PropTypes.arrayOf(PropTypes.shape({})), - toprightoverlay: PropTypes.shape({}), - bottomleftoverlay: PropTypes.shape({}), + toprightoverlay: PropTypes.func, + bottomleftoverlay: PropTypes.func, }; diff --git a/src/components/scrollscrubber/ScrollScrubber.tsx b/src/components/scrollscrubber/ScrollScrubber.tsx index 0e9bb3a7..ae4d19ba 100644 --- a/src/components/scrollscrubber/ScrollScrubber.tsx +++ b/src/components/scrollscrubber/ScrollScrubber.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react-hooks/exhaustive-deps */ import { Badge, Box, Group, useComputedColorScheme, useMantineTheme } from "@mantine/core"; import { useElementSize, useMediaQuery } from "@mantine/hooks"; import _ from "lodash"; @@ -242,7 +241,6 @@ export function ScrollScrubber({ type, scrollPositions, targetHeight, scrollToY, [] ); - // eslint-disable-next-line arrow-body-style useEffect(() => { targetRef.current = document.getElementsByClassName("scrollscrubbertarget")?.item(0); // Clear scrollerVisibilityTimerRef on dismount diff --git a/src/layouts/albums/AlbumPeople.tsx b/src/layouts/albums/AlbumPeople.tsx index 078a80a0..3f531d8f 100644 --- a/src/layouts/albums/AlbumPeople.tsx +++ b/src/layouts/albums/AlbumPeople.tsx @@ -1,4 +1,3 @@ - import { ActionIcon, Avatar, Button, Flex, Group, Image, Menu, Modal, Text, TextInput } from "@mantine/core"; import { useDisclosure } from "@mantine/hooks"; import { @@ -13,11 +12,11 @@ import { Link } from "react-router-dom"; import { AutoSizer, Grid } from "react-virtualized"; import { - Person, useDeletePersonAlbumMutation, useFetchPeopleAlbumsQuery, useRenamePersonAlbumMutation, } from "../../api_client/albums/people"; +import type { Person } from "../../api_client/albums/people"; import { Tile } from "../../components/Tile"; import { useAlbumListGridConfig } from "../../hooks/useAlbumListGridConfig"; import { HeaderComponent } from "./HeaderComponent"; @@ -51,11 +50,12 @@ export function AlbumPeople() { showRenameDialog(); } - function getPersonIcon(album) { + function getPersonIcon(album: Person) { if (album.face_count === 0) { return ; } - if (album.text === "unknown") { + if (album.name === "unknown") { + // if (album.text === "unknown") { return ( @@ -96,10 +96,10 @@ export function AlbumPeople() { - } onClick={() => openRenameDialog(album)}> + } onClick={() => openRenameDialog(album)}> {t("rename")} - } onClick={() => openDeleteDialog(album)}> + } onClick={() => openDeleteDialog(album)}> {t("delete")} @@ -119,7 +119,7 @@ export function AlbumPeople() { } return ( -
+ <> } title={t("people")} @@ -128,6 +128,23 @@ export function AlbumPeople() { peoplelength: (albums && albums.length) || 0, })} /> + + {({ width }) => ( + renderCell(props)} + columnWidth={entrySquareSize} + columnCount={entriesPerRow} + height={gridHeight} + rowHeight={entrySquareSize + 60} + rowCount={numberOfRows} + width={width} + /> + )} + + - - - {({ width }) => ( - renderCell(props)} - columnWidth={entrySquareSize} - columnCount={entriesPerRow} - height={gridHeight} - rowHeight={entrySquareSize + 60} - rowCount={numberOfRows} - width={width} - /> - )} - -
+ ); } diff --git a/src/layouts/albums/AlbumUser.tsx b/src/layouts/albums/AlbumUser.tsx index 512e9350..8d706aa7 100644 --- a/src/layouts/albums/AlbumUser.tsx +++ b/src/layouts/albums/AlbumUser.tsx @@ -107,7 +107,7 @@ export function AlbumUser() {
- + diff --git a/src/layouts/dataviz/FaceDashboard.tsx b/src/layouts/dataviz/FaceDashboard.tsx index cf67a8d1..ee9cebf6 100644 --- a/src/layouts/dataviz/FaceDashboard.tsx +++ b/src/layouts/dataviz/FaceDashboard.tsx @@ -1,5 +1,5 @@ /* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */ -import { RemoveScroll, Stack } from "@mantine/core"; +import { Flex, RemoveScroll, Stack } from "@mantine/core"; import { useElementSize } from "@mantine/hooks"; import _ from "lodash"; import React, { useEffect, useRef, useState } from "react"; @@ -358,7 +358,7 @@ export function FaceDashboard() { notThisPerson={notThisPersonFunc} /> -
+ {({ height, width: gridWidth }) => ( )} -
+ { diff --git a/src/layouts/login/FirstTimeSetupPage.tsx b/src/layouts/login/FirstTimeSetupPage.tsx index 638f1488..d83828bb 100644 --- a/src/layouts/login/FirstTimeSetupPage.tsx +++ b/src/layouts/login/FirstTimeSetupPage.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { Button, Card, diff --git a/src/layouts/login/LoginPage.tsx b/src/layouts/login/LoginPage.tsx index c94c2d57..3813cf9e 100644 --- a/src/layouts/login/LoginPage.tsx +++ b/src/layouts/login/LoginPage.tsx @@ -75,7 +75,6 @@ export function LoginPage(): JSX.Element { leftSection={} placeholder={t("login.usernameplaceholder")} name="username" - /* eslint-disable-next-line react/jsx-props-no-spreading */ {...form.getInputProps("username")} /> } placeholder={t("login.passwordplaceholder")} name="password" - /* eslint-disable-next-line react/jsx-props-no-spreading */ {...form.getInputProps("password")} />