fix: enter now submits the date modals (#25053)

* fix: enter now submits the date modals

* use FormModal

* apply prettier

* fix unit test
This commit is contained in:
fabb 2026-01-06 16:08:54 +01:00 committed by GitHub
parent c411151560
commit 4cb56edebf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 111 additions and 114 deletions

View file

@ -5,7 +5,7 @@
import { getPreferredTimeZone, getTimezones, toIsoDate } from '$lib/modals/timezone-utils'; import { getPreferredTimeZone, getTimezones, toIsoDate } from '$lib/modals/timezone-utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { updateAsset } from '@immich/sdk'; import { updateAsset } from '@immich/sdk';
import { Button, HStack, Label, Modal, ModalBody, ModalFooter } from '@immich/ui'; import { FormModal, Label } from '@immich/ui';
import { mdiCalendarEdit } from '@mdi/js'; import { mdiCalendarEdit } from '@mdi/js';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -28,7 +28,7 @@
// the offsets (and validity) for time zones may change if the date is changed, which is why we recompute the list // the offsets (and validity) for time zones may change if the date is changed, which is why we recompute the list
let selectedOption = $derived(getPreferredTimeZone(initialDate, initialTimeZone, timezones, lastSelectedTimezone)); let selectedOption = $derived(getPreferredTimeZone(initialDate, initialTimeZone, timezones, lastSelectedTimezone));
const handleClose = async () => { const onSubmit = async () => {
if (!date.isValid || !selectedOption) { if (!date.isValid || !selectedOption) {
onClose(false); onClose(false);
return; return;
@ -49,25 +49,25 @@
const date = $derived(DateTime.fromISO(selectedDate, { zone: selectedOption?.value, setZone: true })); const date = $derived(DateTime.fromISO(selectedDate, { zone: selectedOption?.value, setZone: true }));
</script> </script>
<Modal title={$t('edit_date_and_time')} icon={mdiCalendarEdit} onClose={() => onClose(false)} size="small"> <FormModal
<ModalBody> title={$t('edit_date_and_time')}
<Label for="datetime" class="block mb-1">{$t('date_and_time')}</Label> icon={mdiCalendarEdit}
<DateInput onClose={() => onClose(false)}
class="immich-form-input text-gray-700 w-full mb-2" {onSubmit}
id="datetime" submitText={$t('confirm')}
type="datetime-local" disabled={!date.isValid || !selectedOption}
bind:value={selectedDate} size="small"
/> >
{#if timezoneInput} <Label for="datetime" class="block mb-1">{$t('date_and_time')}</Label>
<div class="w-full"> <DateInput
<Combobox bind:selectedOption label={$t('timezone')} options={timezones} placeholder={$t('search_timezone')} /> class="immich-form-input text-gray-700 w-full mb-2"
</div> id="datetime"
{/if} type="datetime-local"
</ModalBody> bind:value={selectedDate}
<ModalFooter> />
<HStack fullWidth> {#if timezoneInput}
<Button shape="round" color="secondary" fullWidth onclick={() => onClose(false)}>{$t('cancel')}</Button> <div class="w-full">
<Button shape="round" type="submit" fullWidth onclick={handleClose}>{$t('confirm')}</Button> <Combobox bind:selectedOption label={$t('timezone')} options={timezones} placeholder={$t('search_timezone')} />
</HStack> </div>
</ModalFooter> {/if}
</Modal> </FormModal>

View file

@ -17,8 +17,8 @@ describe('DateSelectionModal component', () => {
const getRelativeInputToggle = () => screen.getByTestId('edit-by-offset-switch'); const getRelativeInputToggle = () => screen.getByTestId('edit-by-offset-switch');
const getDateInput = () => screen.getByLabelText('date_and_time') as HTMLInputElement; const getDateInput = () => screen.getByLabelText('date_and_time') as HTMLInputElement;
const getTimeZoneInput = () => screen.getByLabelText('timezone') as HTMLInputElement; const getTimeZoneInput = () => screen.getByLabelText('timezone') as HTMLInputElement;
const getCancelButton = () => screen.getByText('cancel'); const getCancelButton = () => screen.getByRole('button', { name: /cancel/i });
const getConfirmButton = () => screen.getByText('confirm'); const getConfirmButton = () => screen.getByRole('button', { name: /confirm/i });
beforeEach(() => { beforeEach(() => {
vi.stubGlobal('IntersectionObserver', getIntersectionObserverMock()); vi.stubGlobal('IntersectionObserver', getIntersectionObserverMock());

View file

@ -8,7 +8,7 @@
import { getOwnedAssetsWithWarning } from '$lib/utils/asset-utils'; import { getOwnedAssetsWithWarning } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { updateAssets } from '@immich/sdk'; import { updateAssets } from '@immich/sdk';
import { Button, Field, HStack, Label, Modal, ModalBody, ModalFooter, Switch } from '@immich/ui'; import { Field, FormModal, Label, Switch } from '@immich/ui';
import { mdiCalendarEdit } from '@mdi/js'; import { mdiCalendarEdit } from '@mdi/js';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -30,7 +30,7 @@
// the offsets (and validity) for time zones may change if the date is changed, which is why we recompute the list // the offsets (and validity) for time zones may change if the date is changed, which is why we recompute the list
let selectedOption = $derived(getPreferredTimeZone(initialDate, initialTimeZone, timezones, lastSelectedTimezone)); let selectedOption = $derived(getPreferredTimeZone(initialDate, initialTimeZone, timezones, lastSelectedTimezone));
const handleConfirm = async () => { const onSubmit = async () => {
const ids = getOwnedAssetsWithWarning(assets, $user); const ids = getOwnedAssetsWithWarning(assets, $user);
try { try {
if (showRelative && (selectedDuration || selectedOption)) { if (showRelative && (selectedDuration || selectedOption)) {
@ -63,66 +63,62 @@
const date = $derived(DateTime.fromISO(selectedDate, { zone: selectedOption?.value, setZone: true })); const date = $derived(DateTime.fromISO(selectedDate, { zone: selectedOption?.value, setZone: true }));
</script> </script>
<Modal title={$t('edit_date_and_time')} icon={mdiCalendarEdit} onClose={() => onClose(false)} size="small"> <FormModal
<ModalBody> title={$t('edit_date_and_time')}
<Field label={$t('edit_date_and_time_by_offset')}> icon={mdiCalendarEdit}
<Switch data-testid="edit-by-offset-switch" bind:checked={showRelative} class="mb-2" /> onClose={() => onClose(false)}
</Field> {onSubmit}
{#if showRelative} submitText={$t('confirm')}
<Label for="relativedatetime" class="block mb-1">{$t('offset')}</Label> disabled={!date.isValid}
<DurationInput size="small"
class="immich-form-input w-full text-gray-700 mb-2" >
id="relativedatetime" <Field label={$t('edit_date_and_time_by_offset')}>
bind:value={selectedDuration} <Switch data-testid="edit-by-offset-switch" bind:checked={showRelative} class="mb-2" />
/> </Field>
{:else} {#if showRelative}
<Label for="datetime" class="block mb-1">{$t('date_and_time')}</Label> <Label for="relativedatetime" class="block mb-1">{$t('offset')}</Label>
<DateInput class="immich-form-input w-full mb-2" id="datetime" type="datetime-local" bind:value={selectedDate} /> <DurationInput
{/if} class="immich-form-input w-full text-gray-700 mb-2"
<div class="w-full"> id="relativedatetime"
<Combobox bind:value={selectedDuration}
bind:selectedOption />
label={$t('timezone')} {:else}
options={timezones} <Label for="datetime" class="block mb-1">{$t('date_and_time')}</Label>
placeholder={$t('search_timezone')} <DateInput class="immich-form-input w-full mb-2" id="datetime" type="datetime-local" bind:value={selectedDate} />
onSelect={(option) => (lastSelectedTimezone = option as ZoneOption)} {/if}
></Combobox> <div class="w-full">
</div> <Combobox
<!-- <Card color="secondary" class={!showRelative || !currentInterval ? 'invisible' : ''}> bind:selectedOption
<CardBody class="p-2"> label={$t('timezone')}
<div class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-3 items-center"> options={timezones}
<div class="col-span-2 immich-form-label" data-testid="interval-preview">Preview</div> placeholder={$t('search_timezone')}
<Text size="small" class="-mt-2 immich-form-label col-span-2" onSelect={(option) => (lastSelectedTimezone = option as ZoneOption)}
>Showing changes for first selected asset only</Text ></Combobox>
> </div>
<label class="immich-form-label" for="from">Before</label> <!-- <Card color="secondary" class={!showRelative || !currentInterval ? 'invisible' : ''}>
<DateInput <CardBody class="p-2">
class="dark:text-gray-300 text-gray-700 text-base" <div class="grid grid-cols-[auto_1fr] gap-x-4 gap-y-3 items-center">
id="from" <div class="col-span-2 immich-form-label" data-testid="interval-preview">Preview</div>
type="datetime-local" <Text size="small" class="-mt-2 immich-form-label col-span-2"
readonly >Showing changes for first selected asset only</Text
bind:value={before} >
/> <label class="immich-form-label" for="from">Before</label>
<label class="immich-form-label" for="to">After</label> <DateInput
<DateInput class="dark:text-gray-300 text-gray-700 text-base"
class="dark:text-gray-300 text-gray-700 text-base" id="from"
id="to" type="datetime-local"
type="datetime-local" readonly
readonly bind:value={before}
bind:value={after} />
/> <label class="immich-form-label" for="to">After</label>
</div> <DateInput
</CardBody> class="dark:text-gray-300 text-gray-700 text-base"
</Card> --> id="to"
</ModalBody> type="datetime-local"
<ModalFooter> readonly
<HStack fullWidth> bind:value={after}
<Button shape="round" color="secondary" fullWidth onclick={() => onClose(false)}> />
{$t('cancel')} </div>
</Button> </CardBody>
<Button shape="round" color="primary" fullWidth onclick={handleConfirm} disabled={!date.isValid}> </Card> -->
{$t('confirm')} </FormModal>
</Button>
</HStack>
</ModalFooter>
</Modal>

View file

@ -3,10 +3,11 @@
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { getPreferredTimeZone, getTimezones, toDatetime, type ZoneOption } from '$lib/modals/timezone-utils'; import { getPreferredTimeZone, getTimezones, toDatetime, type ZoneOption } from '$lib/modals/timezone-utils';
import { Button, HStack, Modal, ModalBody, ModalFooter, VStack } from '@immich/ui'; import { FormModal, HStack, VStack } from '@immich/ui';
import { mdiNavigationVariantOutline } from '@mdi/js'; import { mdiNavigationVariantOutline } from '@mdi/js';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
interface Props { interface Props {
timelineManager: TimelineManager; timelineManager: TimelineManager;
onClose: (asset?: TimelineAsset) => void; onClose: (asset?: TimelineAsset) => void;
@ -20,7 +21,7 @@
// the offsets (and validity) for time zones may change if the date is changed, which is why we recompute the list // the offsets (and validity) for time zones may change if the date is changed, which is why we recompute the list
let selectedOption: ZoneOption | undefined = $derived(getPreferredTimeZone(initialDate, undefined, timezones)); let selectedOption: ZoneOption | undefined = $derived(getPreferredTimeZone(initialDate, undefined, timezones));
const handleClose = async () => { const onSubmit = async () => {
if (!date.isValid || !selectedOption) { if (!date.isValid || !selectedOption) {
onClose(); onClose();
return; return;
@ -36,26 +37,26 @@
const date = $derived(DateTime.fromISO(selectedDate, { zone: selectedOption?.value, setZone: true })); const date = $derived(DateTime.fromISO(selectedDate, { zone: selectedOption?.value, setZone: true }));
</script> </script>
<Modal title={$t('navigate_to_time')} icon={mdiNavigationVariantOutline} onClose={() => onClose()}> <FormModal
<ModalBody> title={$t('navigate_to_time')}
<VStack fullWidth> icon={mdiNavigationVariantOutline}
<HStack fullWidth> onClose={() => onClose()}
<label class="immich-form-label" for="datetime">{$t('date_and_time')}</label> {onSubmit}
</HStack> submitText={$t('confirm')}
<HStack fullWidth> disabled={!date.isValid || !selectedOption}
<DateInput size="medium"
class="immich-form-input text-gray-700 w-full" >
id="datetime" <VStack fullWidth>
type="datetime-local"
bind:value={selectedDate}
/>
</HStack>
</VStack>
</ModalBody>
<ModalFooter>
<HStack fullWidth> <HStack fullWidth>
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button> <label class="immich-form-label" for="datetime">{$t('date_and_time')}</label>
<Button shape="round" type="submit" fullWidth onclick={handleClose}>{$t('confirm')}</Button>
</HStack> </HStack>
</ModalFooter> <HStack fullWidth>
</Modal> <DateInput
class="immich-form-input text-gray-700 w-full"
id="datetime"
type="datetime-local"
bind:value={selectedDate}
/>
</HStack>
</VStack>
</FormModal>