From c80afea4686977df02d232acc7a032a822cb3c88 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 11 Mar 2025 10:08:52 -0500 Subject: [PATCH] feat(web): better person naming interface (#16631) * feat(web): better person naming interface * feat(web): better person naming interface * feat(web): better person naming interface * feat(web): better person naming interface * feat(web): better person naming interface * feat(web): better person naming interface * feat(web): better person naming interface --- .../components/faces-page/people-card.svelte | 22 +- .../faces-page/people-infinite-scroll.svelte | 2 +- web/src/routes/(user)/people/+page.svelte | 232 ++++++++---------- 3 files changed, 104 insertions(+), 152 deletions(-) diff --git a/web/src/lib/components/faces-page/people-card.svelte b/web/src/lib/components/faces-page/people-card.svelte index fa31a3e9a2..d12855c54f 100644 --- a/web/src/lib/components/faces-page/people-card.svelte +++ b/web/src/lib/components/faces-page/people-card.svelte @@ -6,7 +6,6 @@ import { getPeopleThumbnailUrl } from '$lib/utils'; import { type PersonResponseDto } from '@immich/sdk'; import { - mdiAccountEditOutline, mdiAccountMultipleCheckOutline, mdiCalendarEditOutline, mdiDotsVertical, @@ -22,22 +21,13 @@ interface Props { person: PersonResponseDto; preload?: boolean; - onChangeName: () => void; onSetBirthDate: () => void; onMergePeople: () => void; onHidePerson: () => void; onToggleFavorite: () => void; } - let { - person, - preload = false, - onChangeName, - onSetBirthDate, - onMergePeople, - onHidePerson, - onToggleFavorite, - }: Props = $props(); + let { person, preload = false, onSetBirthDate, onMergePeople, onHidePerson, onToggleFavorite }: Props = $props(); let showVerticalDots = $state(false); @@ -63,6 +53,7 @@ altText={person.name} title={person.name} widthStyle="100%" + circle /> {#if person.isFavorite}
@@ -70,14 +61,6 @@
{/if} - {#if person.name} - - {person.name} - - {/if} {#if showVerticalDots} @@ -91,7 +74,6 @@ title={$t('show_person_options')} > - -
+
{#each people as person, index (person.id)} {#if hasNextPage && index === people.length - 1}
diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index f3ea5d8638..0dace06938 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -11,7 +11,6 @@ import SearchPeople from '$lib/components/faces-page/people-search.svelte'; import SetBirthDateModal from '$lib/components/faces-page/set-birth-date-modal.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; - import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte'; import { notificationController, NotificationType, @@ -46,10 +45,9 @@ let selectHidden = $state(false); let searchName = $state(''); - let showChangeNameModal = $state(false); let showSetBirthDateModal = $state(false); let showMergeModal = $state(false); - let personName = $state(''); + let newName = $state(''); let currentPage = $state(1); let nextPage = $state(data.people.hasNextPage ? 2 : null); let personMerge1 = $state(); @@ -57,10 +55,9 @@ let potentialMergePeople: PersonResponseDto[] = $state([]); let edittingPerson: PersonResponseDto | null = $state(null); let searchedPeopleLocal: PersonResponseDto[] = $state([]); - // let handleSearchPeople: (force?: boolean, name?: string) => Promise = $state(); - let changeNameInputEl = $state(); let innerHeight = $state(0); let searchPeopleElement = $state>(); + onMount(() => { const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE); if (getSearchedPeople) { @@ -158,7 +155,7 @@ } catch (error) { handleError(error, $t('errors.unable_to_save_name')); } - if (personToBeMergedIn.name !== personName && edittingPerson.id === personToBeMergedIn.id) { + if (personToBeMergedIn.name !== newName && edittingPerson.id === personToBeMergedIn.id) { /* * * If the user merges one of the suggested people into the person he's editing it, it's merging the suggested person AND renames @@ -166,11 +163,11 @@ * */ try { - await updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: personName } }); + await updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: newName } }); for (const person of people) { if (person.id === personToBeMergedIn.id) { - person.name = personName; + person.name = newName; break; } } @@ -184,15 +181,6 @@ } }; - const handleChangeName = (detail: PersonResponseDto) => { - showChangeNameModal = true; - personName = detail.name; - personMerge1 = detail; - edittingPerson = detail; - - setTimeout(() => changeNameInputEl?.focus(), 100); - }; - const handleSetBirthDate = (detail: PersonResponseDto) => { showSetBirthDateModal = true; edittingPerson = detail; @@ -212,7 +200,6 @@ return person; }); - showChangeNameModal = false; notificationController.show({ message: $t('changed_visibility_successfully'), type: NotificationType.Info, @@ -247,46 +234,6 @@ ); }; - const submitNameChange = async (event: Event) => { - event.preventDefault(); - - potentialMergePeople = []; - showChangeNameModal = false; - if (!edittingPerson || personName === edittingPerson.name) { - return; - } - if (personName === '') { - await changeName(); - return; - } - const data = await searchPerson({ name: personName, withHidden: true }); - - // We check if another person has the same name as the name entered by the user - - const existingPerson = data.find( - (person: PersonResponseDto) => - person.name.toLowerCase() === personName.toLowerCase() && - edittingPerson && - person.id !== edittingPerson.id && - person.name, - ); - if (existingPerson) { - personMerge2 = existingPerson; - showMergeModal = true; - potentialMergePeople = people - .filter( - (person: PersonResponseDto) => - personMerge2?.name.toLowerCase() === person.name.toLowerCase() && - person.id !== personMerge2.id && - person.id !== personMerge1?.id && - !person.isHidden, - ) - .slice(0, 3); - return; - } - await changeName(); - }; - const submitBirthDateChange = async (value: string) => { showSetBirthDateModal = false; if (!edittingPerson || value === edittingPerson.birthDate) { @@ -314,33 +261,6 @@ } }; - const changeName = async () => { - showMergeModal = false; - showChangeNameModal = false; - - if (!edittingPerson) { - return; - } - try { - const updatedPerson = await updatePerson({ - id: edittingPerson.id, - personUpdateDto: { name: personName }, - }); - people = people.map((person: PersonResponseDto) => { - if (person.id === updatedPerson.id) { - return updatedPerson; - } - return person; - }); - notificationController.show({ - message: $t('change_name_successfully'), - type: NotificationType.Info, - }); - } catch (error) { - handleError(error, $t('errors.unable_to_save_name')); - } - }; - const onResetSearchBar = async () => { await clearQueryParam(QueryParameter.SEARCHED_PEOPLE, $page.url); }; @@ -353,12 +273,74 @@ let countVisiblePeople = $derived(searchName ? searchedPeopleLocal.length : data.people.total - data.people.hidden); let showPeople = $derived(searchName ? searchedPeopleLocal : visiblePeople); - // const submitNameChange = (event: Event) => { - // event.preventDefault(); - // if (searchPeopleElement) { - // handlePromiseError(searchPeopleElement.searchPeople(true, searchName)); - // } - // }; + const onNameChangeInputFocus = (person: PersonResponseDto) => { + edittingPerson = person; + newName = person.name; + }; + + const onNameChangeSubmit = async (name: string, targetPerson: PersonResponseDto) => { + try { + if (name == targetPerson.name || showMergeModal) { + return; + } + + if (name === '') { + await updateName(targetPerson.id, ''); + return; + } + + const personWithSimilarName = await findPeopleWithSimilarName(name, targetPerson.id); + if (personWithSimilarName) { + personMerge1 = targetPerson; + personMerge2 = personWithSimilarName; + potentialMergePeople = people + .filter( + (person: PersonResponseDto) => + personMerge2?.name.toLowerCase() === person.name.toLowerCase() && + person.id !== personMerge2.id && + person.id !== personMerge1?.id && + !person.isHidden, + ) + .slice(0, 3); + showMergeModal = true; + return; + } + await updateName(targetPerson.id, name); + } catch (error) { + handleError(error, $t('errors.unable_to_save_name')); + } + }; + + const onNameChangeInputUpdate = (event: Event) => { + if (event.target) { + newName = (event.target as HTMLInputElement).value; + } + }; + + const updateName = async (id: string, name: string) => { + await updatePerson({ + id, + personUpdateDto: { name }, + }); + + newName = ''; + }; + + const findPeopleWithSimilarName = async (name: string, personId: string) => { + const searchResult = await searchPerson({ name, withHidden: true }); + return searchResult.find( + (person) => person.name.toLowerCase() === name.toLowerCase() && person.id !== personId && person.name, + ); + }; + + const handleMergeCancel = async () => { + if (!personMerge1) { + return; + } + + await updateName(personMerge1.id, newName); + showMergeModal = false; + }; @@ -368,8 +350,10 @@ {personMerge1} {personMerge2} {potentialMergePeople} - onClose={() => (showMergeModal = false)} - onReject={changeName} + onClose={() => { + showMergeModal = false; + }} + onReject={() => handleMergeCancel()} onConfirm={handleMergeSamePerson} /> {/if} @@ -425,15 +409,30 @@ {#if countVisiblePeople > 0 && (!searchName || searchedPeopleLocal.length > 0)} {#snippet children({ person, index })} - handleChangeName(person)} - onSetBirthDate={() => handleSetBirthDate(person)} - onMergePeople={() => handleMergePeople(person)} - onHidePerson={() => handleHidePerson(person)} - onToggleFavorite={() => handleToggleFavorite(person)} - /> +
+ handleSetBirthDate(person)} + onMergePeople={() => handleMergePeople(person)} + onHidePerson={() => handleHidePerson(person)} + onToggleFavorite={() => handleToggleFavorite(person)} + /> + +
onNameChangeSubmit(newName, person)}> + onNameChangeInputFocus(person)} + onfocusout={() => onNameChangeSubmit(newName, person)} + oninput={(event) => onNameChangeInputUpdate(event)} + /> +
+
{/snippet}
{:else} @@ -447,35 +446,6 @@
{/if} - {#if showChangeNameModal} - (showChangeNameModal = false)}> -
-
- - -
-
- - {#snippet stickyBottom()} - - - {/snippet} -
- {/if} - {#if showSetBirthDateModal}