refactor: date of birth modal (#18283)

This commit is contained in:
Daniel Dietzler 2025-05-14 14:20:22 +02:00 committed by GitHub
parent c9d45eee86
commit 4efc41d5d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 100 additions and 126 deletions

View File

@ -1,48 +0,0 @@
<script lang="ts">
import Button from '../elements/buttons/button.svelte';
import FullScreenModal from '../shared-components/full-screen-modal.svelte';
import { mdiCake } from '@mdi/js';
import DateInput from '../elements/date-input.svelte';
import { t } from 'svelte-i18n';
interface Props {
birthDate: string;
onClose: () => void;
onUpdate: (birthDate: string) => void;
}
let { birthDate = $bindable(), onClose, onUpdate }: Props = $props();
const todayFormatted = new Date().toISOString().split('T')[0];
const onSubmit = (event: Event) => {
event.preventDefault();
onUpdate(birthDate);
};
</script>
<FullScreenModal title={$t('set_date_of_birth')} icon={mdiCake} {onClose}>
<div class="text-immich-primary dark:text-immich-dark-primary">
<p class="text-sm dark:text-immich-dark-fg">
{$t('birthdate_set_description')}
</p>
</div>
<form onsubmit={(e) => onSubmit(e)} autocomplete="off" id="set-birth-date-form">
<div class="my-4 flex flex-col gap-2">
<DateInput
class="immich-form-input"
id="birthDate"
name="birthDate"
type="date"
bind:value={birthDate}
max={todayFormatted}
/>
</div>
</form>
{#snippet stickyBottom()}
<Button color="gray" fullwidth onclick={onClose}>{$t('cancel')}</Button>
<Button type="submit" fullwidth form="set-birth-date-form">{$t('set')}</Button>
{/snippet}
</FullScreenModal>

View File

@ -384,7 +384,6 @@ export enum PersonPageViewMode {
SELECT_PERSON = 'select-person', SELECT_PERSON = 'select-person',
MERGE_PEOPLE = 'merge-people', MERGE_PEOPLE = 'merge-people',
SUGGEST_MERGE = 'suggest-merge', SUGGEST_MERGE = 'suggest-merge',
BIRTH_DATE = 'birth-date',
UNASSIGN_ASSETS = 'unassign-faces', UNASSIGN_ASSETS = 'unassign-faces',
} }

View File

@ -0,0 +1,67 @@
<script lang="ts">
import {
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { updatePerson, type PersonResponseDto } from '@immich/sdk';
import { Modal, ModalBody, ModalFooter } from '@immich/ui';
import { mdiCake } from '@mdi/js';
import { t } from 'svelte-i18n';
import Button from '../components/elements/buttons/button.svelte';
import DateInput from '../components/elements/date-input.svelte';
interface Props {
person: PersonResponseDto;
onClose: (updatedPerson?: PersonResponseDto) => void;
}
let { person, onClose }: Props = $props();
let birthDate = $state(person.birthDate ?? '');
const todayFormatted = new Date().toISOString().split('T')[0];
const handleUpdateBirthDate = async () => {
try {
const updatedPerson = await updatePerson({
id: person.id,
personUpdateDto: { birthDate: birthDate.length > 0 ? birthDate : null },
});
notificationController.show({ message: $t('date_of_birth_saved'), type: NotificationType.Info });
onClose(updatedPerson);
} catch (error) {
handleError(error, $t('errors.unable_to_save_date_of_birth'));
}
};
</script>
<Modal title={$t('set_date_of_birth')} icon={mdiCake} {onClose} size="small">
<ModalBody>
<div class="text-immich-primary dark:text-immich-dark-primary">
<p class="text-sm dark:text-immich-dark-fg">
{$t('birthdate_set_description')}
</p>
</div>
<form onsubmit={() => handleUpdateBirthDate()} autocomplete="off" id="set-birth-date-form">
<div class="my-4 flex flex-col gap-2">
<DateInput
class="immich-form-input"
id="birthDate"
name="birthDate"
type="date"
bind:value={birthDate}
max={todayFormatted}
/>
</div>
</form>
</ModalBody>
<ModalFooter>
<div class="flex gap-3 w-full">
<Button color="secondary" fullwidth onclick={() => onClose()}>{$t('cancel')}</Button>
<Button type="submit" fullwidth form="set-birth-date-form">{$t('set')}</Button>
</div>
</ModalFooter>
</Modal>

View File

@ -9,13 +9,14 @@
import PeopleCard from '$lib/components/faces-page/people-card.svelte'; import PeopleCard from '$lib/components/faces-page/people-card.svelte';
import PeopleInfiniteScroll from '$lib/components/faces-page/people-infinite-scroll.svelte'; import PeopleInfiniteScroll from '$lib/components/faces-page/people-infinite-scroll.svelte';
import SearchPeople from '$lib/components/faces-page/people-search.svelte'; 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 UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import { import {
notificationController, notificationController,
NotificationType, NotificationType,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { ActionQueryParameterValue, AppRoute, QueryParameter, SessionStorageKey } from '$lib/constants'; import { ActionQueryParameterValue, AppRoute, QueryParameter, SessionStorageKey } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { websocketEvents } from '$lib/stores/websocket'; import { websocketEvents } from '$lib/stores/websocket';
import { handlePromiseError } from '$lib/utils'; import { handlePromiseError } from '$lib/utils';
@ -45,7 +46,6 @@
let selectHidden = $state(false); let selectHidden = $state(false);
let searchName = $state(''); let searchName = $state('');
let showSetBirthDateModal = $state(false);
let showMergeModal = $state(false); let showMergeModal = $state(false);
let newName = $state(''); let newName = $state('');
let currentPage = $state(1); let currentPage = $state(1);
@ -53,7 +53,7 @@
let personMerge1 = $state<PersonResponseDto>(); let personMerge1 = $state<PersonResponseDto>();
let personMerge2 = $state<PersonResponseDto>(); let personMerge2 = $state<PersonResponseDto>();
let potentialMergePeople: PersonResponseDto[] = $state([]); let potentialMergePeople: PersonResponseDto[] = $state([]);
let edittingPerson: PersonResponseDto | null = $state(null); let editingPerson: PersonResponseDto | null = $state(null);
let searchedPeopleLocal: PersonResponseDto[] = $state([]); let searchedPeopleLocal: PersonResponseDto[] = $state([]);
let innerHeight = $state(0); let innerHeight = $state(0);
let searchPeopleElement = $state<ReturnType<typeof SearchPeople>>(); let searchPeopleElement = $state<ReturnType<typeof SearchPeople>>();
@ -135,7 +135,7 @@
const [personToMerge, personToBeMergedIn] = response; const [personToMerge, personToBeMergedIn] = response;
showMergeModal = false; showMergeModal = false;
if (!edittingPerson) { if (!editingPerson) {
return; return;
} }
try { try {
@ -155,7 +155,7 @@
} catch (error) { } catch (error) {
handleError(error, $t('errors.unable_to_save_name')); handleError(error, $t('errors.unable_to_save_name'));
} }
if (personToBeMergedIn.name !== newName && edittingPerson.id === personToBeMergedIn.id) { if (personToBeMergedIn.name !== newName && editingPerson.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 * If the user merges one of the suggested people into the person he's editing it, it's merging the suggested person AND renames
@ -181,11 +181,6 @@
} }
}; };
const handleSetBirthDate = (detail: PersonResponseDto) => {
showSetBirthDateModal = true;
edittingPerson = detail;
};
const handleHidePerson = async (detail: PersonResponseDto) => { const handleHidePerson = async (detail: PersonResponseDto) => {
try { try {
const updatedPerson = await updatePerson({ const updatedPerson = await updatePerson({
@ -234,31 +229,19 @@
); );
}; };
const submitBirthDateChange = async (value: string) => { const handleChangeBirthDate = async (person: PersonResponseDto) => {
showSetBirthDateModal = false; const updatedPerson = await modalManager.show(PersonEditBirthDateModal, { person });
if (!edittingPerson || value === edittingPerson.birthDate) {
if (!updatedPerson) {
return; return;
} }
try { people = people.map((person: PersonResponseDto) => {
const updatedPerson = await updatePerson({ if (person.id === updatedPerson.id) {
id: edittingPerson.id, return updatedPerson;
personUpdateDto: { birthDate: value.length > 0 ? value : null }, }
}); return person;
});
people = people.map((person: PersonResponseDto) => {
if (person.id === updatedPerson.id) {
return updatedPerson;
}
return person;
});
notificationController.show({
message: $t('birthdate_saved'),
type: NotificationType.Info,
});
} catch (error) {
handleError(error, $t('errors.unable_to_save_name'));
}
}; };
const onResetSearchBar = async () => { const onResetSearchBar = async () => {
@ -274,7 +257,7 @@
let showPeople = $derived(searchName ? searchedPeopleLocal : visiblePeople); let showPeople = $derived(searchName ? searchedPeopleLocal : visiblePeople);
const onNameChangeInputFocus = (person: PersonResponseDto) => { const onNameChangeInputFocus = (person: PersonResponseDto) => {
edittingPerson = person; editingPerson = person;
newName = person.name; newName = person.name;
}; };
@ -414,7 +397,7 @@
> >
<PeopleCard <PeopleCard
{person} {person}
onSetBirthDate={() => handleSetBirthDate(person)} onSetBirthDate={() => handleChangeBirthDate(person)}
onMergePeople={() => handleMergePeople(person)} onMergePeople={() => handleMergePeople(person)}
onHidePerson={() => handleHidePerson(person)} onHidePerson={() => handleHidePerson(person)}
onToggleFavorite={() => handleToggleFavorite(person)} onToggleFavorite={() => handleToggleFavorite(person)}
@ -444,14 +427,6 @@
</div> </div>
</div> </div>
{/if} {/if}
{#if showSetBirthDateModal}
<SetBirthDateModal
birthDate={edittingPerson?.birthDate ?? ''}
onClose={() => (showSetBirthDateModal = false)}
onUpdate={submitBirthDateChange}
/>
{/if}
</UserPageLayout> </UserPageLayout>
{#if selectHidden} {#if selectHidden}

View File

@ -8,7 +8,6 @@
import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte'; import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte';
import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte'; import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte';
import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte'; import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte';
import SetBirthDateModal from '$lib/components/faces-page/set-birth-date-modal.svelte';
import UnMergeFaceSelector from '$lib/components/faces-page/unmerge-face-selector.svelte'; import UnMergeFaceSelector from '$lib/components/faces-page/unmerge-face-selector.svelte';
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte'; import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte'; import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
@ -31,6 +30,8 @@
notificationController, notificationController,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants'; import { AppRoute, PersonPageViewMode, QueryParameter, SessionStorageKey } from '$lib/constants';
import { modalManager } from '$lib/managers/modal-manager.svelte';
import PersonEditBirthDateModal from '$lib/modals/PersonEditBirthDateModal.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { AssetStore } from '$lib/stores/assets-store.svelte'; import { AssetStore } from '$lib/stores/assets-store.svelte';
@ -322,27 +323,19 @@
await changeName(); await changeName();
}; };
const handleSetBirthDate = async (birthDate: string) => { const handleSetBirthDate = async () => {
try { const updatedPerson = await modalManager.show(PersonEditBirthDateModal, { person });
viewMode = PersonPageViewMode.VIEW_ASSETS;
person.birthDate = birthDate;
const updatedPerson = await updatePerson({ if (!updatedPerson) {
id: person.id, return;
personUpdateDto: { birthDate: birthDate.length > 0 ? birthDate : null },
});
people = people.map((person: PersonResponseDto) => {
if (person.id === updatedPerson.id) {
return updatedPerson;
}
return person;
});
notificationController.show({ message: $t('date_of_birth_saved'), type: NotificationType.Info });
} catch (error) {
handleError(error, $t('errors.unable_to_save_date_of_birth'));
} }
people = people.map((person: PersonResponseDto) => {
if (person.id === updatedPerson.id) {
return updatedPerson;
}
return person;
});
}; };
const handleGoBack = async () => { const handleGoBack = async () => {
@ -389,7 +382,7 @@
onSelect={handleSelectFeaturePhoto} onSelect={handleSelectFeaturePhoto}
onEscape={handleEscape} onEscape={handleEscape}
> >
{#if viewMode === PersonPageViewMode.VIEW_ASSETS || viewMode === PersonPageViewMode.SUGGEST_MERGE || viewMode === PersonPageViewMode.BIRTH_DATE} {#if viewMode === PersonPageViewMode.VIEW_ASSETS || viewMode === PersonPageViewMode.SUGGEST_MERGE}
<!-- Person information block --> <!-- Person information block -->
<div <div
class="relative w-fit p-4 sm:px-6" class="relative w-fit p-4 sm:px-6"
@ -515,14 +508,6 @@
/> />
{/if} {/if}
{#if viewMode === PersonPageViewMode.BIRTH_DATE}
<SetBirthDateModal
birthDate={person.birthDate ?? ''}
onClose={() => (viewMode = PersonPageViewMode.VIEW_ASSETS)}
onUpdate={handleSetBirthDate}
/>
{/if}
{#if viewMode === PersonPageViewMode.MERGE_PEOPLE} {#if viewMode === PersonPageViewMode.MERGE_PEOPLE}
<MergeFaceSelector {person} onBack={handleGoBack} onMerge={handleMerge} /> <MergeFaceSelector {person} onBack={handleGoBack} onMerge={handleMerge} />
{/if} {/if}
@ -568,7 +553,7 @@
</ButtonContextMenu> </ButtonContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
{#if viewMode === PersonPageViewMode.VIEW_ASSETS || viewMode === PersonPageViewMode.SUGGEST_MERGE || viewMode === PersonPageViewMode.BIRTH_DATE} {#if viewMode === PersonPageViewMode.VIEW_ASSETS || viewMode === PersonPageViewMode.SUGGEST_MERGE}
<ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(previousRoute)}> <ControlAppBar showBackButton backIcon={mdiArrowLeft} onClose={() => goto(previousRoute)}>
{#snippet trailing()} {#snippet trailing()}
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
@ -582,11 +567,7 @@
icon={person.isHidden ? mdiEyeOutline : mdiEyeOffOutline} icon={person.isHidden ? mdiEyeOutline : mdiEyeOffOutline}
onClick={() => toggleHidePerson()} onClick={() => toggleHidePerson()}
/> />
<MenuOption <MenuOption text={$t('set_date_of_birth')} icon={mdiCalendarEditOutline} onClick={handleSetBirthDate} />
text={$t('set_date_of_birth')}
icon={mdiCalendarEditOutline}
onClick={() => (viewMode = PersonPageViewMode.BIRTH_DATE)}
/>
<MenuOption <MenuOption
text={$t('merge_people')} text={$t('merge_people')}
icon={mdiAccountMultipleCheckOutline} icon={mdiAccountMultipleCheckOutline}