From 6997ed83c405c0f74faa2ed37ae1f88e19450768 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Thu, 8 Jan 2026 15:41:20 -0500 Subject: [PATCH] refactor(web): set birthdate (#25139) --- .../components/faces-page/people-card.svelte | 14 +-- web/src/lib/managers/event-manager.svelte.ts | 3 + .../modals/PersonEditBirthDateModal.svelte | 91 +++++++------------ web/src/lib/services/person.service.ts | 31 +++++++ web/src/routes/(user)/people/+page.svelte | 29 +++--- .../[[assetId=id]]/+page.svelte | 37 +++----- 6 files changed, 101 insertions(+), 104 deletions(-) create mode 100644 web/src/lib/services/person.service.ts diff --git a/web/src/lib/components/faces-page/people-card.svelte b/web/src/lib/components/faces-page/people-card.svelte index e18109de0d..650c00b0e2 100644 --- a/web/src/lib/components/faces-page/people-card.svelte +++ b/web/src/lib/components/faces-page/people-card.svelte @@ -1,13 +1,14 @@
- + import DateInput from '$lib/elements/DateInput.svelte'; - import { handleError } from '$lib/utils/handle-error'; - import { updatePerson, type PersonResponseDto } from '@immich/sdk'; - import { Button, HStack, Modal, ModalBody, ModalFooter, toastManager } from '@immich/ui'; + import { handleUpdatePersonBirthDate } from '$lib/services/person.service'; + import { type PersonResponseDto } from '@immich/sdk'; + import { Button, FormModal, Text } from '@immich/ui'; import { mdiCake } from '@mdi/js'; import { t } from 'svelte-i18n'; - interface Props { + type Props = { person: PersonResponseDto; - onClose: (updatedPerson?: PersonResponseDto) => void; - } + onClose: () => void; + }; let { person, onClose }: Props = $props(); - let birthDate = $state(person.birthDate ?? ''); + let birthDate = $derived(person.birthDate ?? ''); - const todayFormatted = new Date().toISOString().split('T')[0]; - - const handleUpdateBirthDate = async () => { - try { - const updatedPerson = await updatePerson({ - id: person.id, - personUpdateDto: { birthDate }, - }); - - toastManager.success($t('date_of_birth_saved')); - onClose(updatedPerson); - } catch (error) { - handleError(error, $t('errors.unable_to_save_date_of_birth')); + const onSubmit = async () => { + const success = await handleUpdatePersonBirthDate(person, birthDate); + if (success) { + onClose(); } }; + + const todayFormatted = new Date().toISOString().split('T')[0]; - - -
-

- {$t('birthdate_set_description')} -

-
- -
handleUpdateBirthDate()} autocomplete="off" id="set-birth-date-form"> -
- - {#if person.birthDate} -
- -
- {/if} + + {$t('birthdate_set_description')} +
+ + {#if person.birthDate} +
+
- - - - - - - - - - + {/if} +
+
diff --git a/web/src/lib/services/person.service.ts b/web/src/lib/services/person.service.ts new file mode 100644 index 0000000000..f6b8289f13 --- /dev/null +++ b/web/src/lib/services/person.service.ts @@ -0,0 +1,31 @@ +import { eventManager } from '$lib/managers/event-manager.svelte'; +import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte'; +import { handleError } from '$lib/utils/handle-error'; +import { getFormatter } from '$lib/utils/i18n'; +import { updatePerson, type PersonResponseDto } from '@immich/sdk'; +import { modalManager, toastManager, type ActionItem } from '@immich/ui'; +import { mdiCalendarEditOutline } from '@mdi/js'; +import type { MessageFormatter } from 'svelte-i18n'; + +export const getPersonActions = ($t: MessageFormatter, person: PersonResponseDto) => { + const SetDateOfBirth: ActionItem = { + title: $t('set_date_of_birth'), + icon: mdiCalendarEditOutline, + onAction: () => modalManager.show(PersonEditBirthDateModal, { person }), + }; + + return { SetDateOfBirth }; +}; + +export const handleUpdatePersonBirthDate = async (person: PersonResponseDto, birthDate: string) => { + const $t = await getFormatter(); + + try { + const response = await updatePerson({ id: person.id, personUpdateDto: { birthDate } }); + toastManager.success($t('date_of_birth_saved')); + eventManager.emit('PersonUpdate', response); + return true; + } catch (error) { + handleError(error, $t('errors.unable_to_save_date_of_birth')); + } +}; diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte index 62c3dfa44e..8a325d0d1a 100644 --- a/web/src/routes/(user)/people/+page.svelte +++ b/web/src/routes/(user)/people/+page.svelte @@ -9,8 +9,8 @@ import PeopleInfiniteScroll from '$lib/components/faces-page/people-infinite-scroll.svelte'; import SearchPeople from '$lib/components/faces-page/people-search.svelte'; import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte'; + import OnEvents from '$lib/components/OnEvents.svelte'; import { ActionQueryParameterValue, AppRoute, QueryParameter, SessionStorageKey } from '$lib/constants'; - import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte'; import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte'; import { locale } from '$lib/stores/preferences.store'; import { websocketEvents } from '$lib/stores/websocket'; @@ -210,21 +210,6 @@ ); }; - const handleChangeBirthDate = async (person: PersonResponseDto) => { - const updatedPerson = await modalManager.show(PersonEditBirthDateModal, { person }); - - if (!updatedPerson) { - return; - } - - people = people.map((person: PersonResponseDto) => { - if (person.id === updatedPerson.id) { - return updatedPerson; - } - return person; - }); - }; - const onResetSearchBar = async () => { await clearQueryParam(QueryParameter.SEARCHED_PEOPLE, $page.url); }; @@ -293,10 +278,21 @@ (person) => person.name.toLowerCase() === name.toLowerCase() && person.id !== personId && person.name, ); }; + + const onPersonUpdate = (response: PersonResponseDto) => { + people = people.map((person: PersonResponseDto) => { + if (person.id === response.id) { + return response; + } + return person; + }); + }; + + handleChangeBirthDate(person)} onMergePeople={() => handleMergePeople(person)} onHidePerson={() => handleHidePerson(person)} onToggleFavorite={() => handleToggleFavorite(person)} diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index c822855310..b338e195c0 100644 --- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -4,10 +4,12 @@ import { clickOutside } from '$lib/actions/click-outside'; import { listNavigation } from '$lib/actions/list-navigation'; import { scrollMemoryClearer } from '$lib/actions/scroll-memory'; + import ActionMenuItem from '$lib/components/ActionMenuItem.svelte'; import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte'; import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte'; import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte'; import UnMergeFaceSelector from '$lib/components/faces-page/unmerge-face-selector.svelte'; + import OnEvents from '$lib/components/OnEvents.svelte'; import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; @@ -28,8 +30,8 @@ import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; - import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte'; import PersonMergeSuggestionModal from '$lib/modals/PersonMergeSuggestionModal.svelte'; + import { getPersonActions } from '$lib/services/person.service'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { locale } from '$lib/stores/preferences.store'; @@ -50,7 +52,6 @@ mdiAccountBoxOutline, mdiAccountMultipleCheckOutline, mdiArrowLeft, - mdiCalendarEditOutline, mdiDotsVertical, mdiEyeOffOutline, mdiEyeOutline, @@ -79,7 +80,6 @@ let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS); let isEditingName = $state(false); let previousRoute: string = $state(AppRoute.EXPLORE); - let people: PersonResponseDto[] = []; let personMerge1: PersonResponseDto | undefined = $state(); let personMerge2: PersonResponseDto | undefined = $state(); let potentialMergePeople: PersonResponseDto[] = $state([]); @@ -223,9 +223,8 @@ return { merged: false }; } - const [personToMerge, personToBeMergedInto] = result; + const [, personToBeMergedInto] = result; - people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id); if (personToBeMergedInto.name != personName && person.id === personToBeMergedInto.id) { await updateAssetCount(); return { merged: true }; @@ -309,22 +308,6 @@ await changeName(); }; - const handleSetBirthDate = async () => { - const updatedPerson = await modalManager.show(PersonEditBirthDateModal, { person }); - - if (!updatedPerson) { - return; - } - - person = updatedPerson; - people = people.map((person: PersonResponseDto) => { - if (person.id === updatedPerson.id) { - return updatedPerson; - } - return person; - }); - }; - const handleGoBack = async () => { viewMode = PersonPageViewMode.VIEW_ASSETS; if ($page.url.searchParams.has(QueryParameter.ACTION)) { @@ -351,8 +334,18 @@ timelineManager.removeAssets(assetIds); assetInteraction.clearMultiselect(); }; + + const onPersonUpdate = (response: PersonResponseDto) => { + if (person.id === response.id) { + return (person = response); + } + }; + + const { SetDateOfBirth } = $derived(getPersonActions($t, person)); + +
toggleHidePerson()} /> - +