mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
fix: navigate to time action (#20928)
* fix: navigate to time action * change-date -> DateSelectionModal; use luxon; use handle* for callback fn name * refactor change-date dialogs * Review comments * chore: clean up --------- Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
@@ -1,18 +1,11 @@
|
||||
<script lang="ts">
|
||||
import ChangeDate, {
|
||||
type AbsoluteResult,
|
||||
type RelativeResult,
|
||||
} from '$lib/components/shared-components/change-date.svelte';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { fromTimelinePlainDateTime } from '$lib/utils/timeline-util.js';
|
||||
import { updateAssets } from '@immich/sdk';
|
||||
import AssetSelectionChangeDateModal from '$lib/modals/AssetSelectionChangeDateModal.svelte';
|
||||
import { modalManager } from '@immich/ui';
|
||||
import { mdiCalendarEditOutline } from '@mdi/js';
|
||||
import { DateTime, Duration } from 'luxon';
|
||||
import { DateTime } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||
interface Props {
|
||||
menuItem?: boolean;
|
||||
}
|
||||
@@ -20,66 +13,17 @@
|
||||
let { menuItem = false }: Props = $props();
|
||||
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
||||
|
||||
let isShowChangeDate = $state(false);
|
||||
|
||||
let currentInterval = $derived.by(() => {
|
||||
if (isShowChangeDate) {
|
||||
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
||||
const assets = getOwnedAssets().filter((asset) => ids.includes(asset.id));
|
||||
const imageTimestamps = assets.map((asset) => {
|
||||
let localDateTime = fromTimelinePlainDateTime(asset.localDateTime);
|
||||
let fileCreatedAt = fromTimelinePlainDateTime(asset.fileCreatedAt);
|
||||
let offsetMinutes = localDateTime.diff(fileCreatedAt, 'minutes').shiftTo('minutes').minutes;
|
||||
const timeZone = `UTC${offsetMinutes >= 0 ? '+' : ''}${Duration.fromObject({ minutes: offsetMinutes }).toFormat('hh:mm')}`;
|
||||
return fileCreatedAt.setZone('utc', { keepLocalTime: true }).setZone(timeZone);
|
||||
});
|
||||
let minTimestamp = imageTimestamps[0];
|
||||
let maxTimestamp = imageTimestamps[0];
|
||||
for (let current of imageTimestamps) {
|
||||
if (current < minTimestamp) {
|
||||
minTimestamp = current;
|
||||
}
|
||||
|
||||
if (current > maxTimestamp) {
|
||||
maxTimestamp = current;
|
||||
}
|
||||
}
|
||||
return { start: minTimestamp, end: maxTimestamp };
|
||||
const handleChangeDate = async () => {
|
||||
const success = await modalManager.show(AssetSelectionChangeDateModal, {
|
||||
initialDate: DateTime.now(),
|
||||
assets: getOwnedAssets(),
|
||||
});
|
||||
if (success) {
|
||||
clearSelect();
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const handleConfirm = async (result: AbsoluteResult | RelativeResult) => {
|
||||
isShowChangeDate = false;
|
||||
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
||||
|
||||
try {
|
||||
if (result.mode === 'absolute') {
|
||||
await updateAssets({ assetBulkUpdateDto: { ids, dateTimeOriginal: result.date } });
|
||||
} else if (result.mode === 'relative') {
|
||||
await updateAssets({
|
||||
assetBulkUpdateDto: {
|
||||
ids,
|
||||
dateTimeRelative: result.duration,
|
||||
timeZone: result.timeZone,
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_change_date'));
|
||||
}
|
||||
clearSelect();
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if menuItem}
|
||||
<MenuOption text={$t('change_date')} icon={mdiCalendarEditOutline} onClick={() => (isShowChangeDate = true)} />
|
||||
{/if}
|
||||
{#if isShowChangeDate}
|
||||
<ChangeDate
|
||||
initialDate={DateTime.now()}
|
||||
{currentInterval}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={() => (isShowChangeDate = false)}
|
||||
/>
|
||||
<MenuOption text={$t('change_date')} icon={mdiCalendarEditOutline} onClick={handleChangeDate} />
|
||||
{/if}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||
import AssetUpdateDescriptionConfirmModal from '$lib/modals/AssetUpdateDescriptionConfirmModal.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
||||
import { getOwnedAssetsWithWarning } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { updateAssets } from '@immich/sdk';
|
||||
import { modalManager } from '@immich/ui';
|
||||
@@ -20,7 +20,7 @@
|
||||
const handleUpdateDescription = async () => {
|
||||
const description = await modalManager.show(AssetUpdateDescriptionConfirmModal);
|
||||
if (description) {
|
||||
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
||||
const ids = getOwnedAssetsWithWarning(getOwnedAssets(), $user);
|
||||
|
||||
try {
|
||||
await updateAssets({ assetBulkUpdateDto: { ids, description } });
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
||||
import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
||||
import { getOwnedAssetsWithWarning } from '$lib/utils/asset-utils';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { updateAssets } from '@immich/sdk';
|
||||
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
|
||||
@@ -25,7 +25,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
||||
const ids = getOwnedAssetsWithWarning(getOwnedAssets(), $user);
|
||||
|
||||
try {
|
||||
await updateAssets({ assetBulkUpdateDto: { ids, latitude: point.lat, longitude: point.lng } });
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { shortcuts, type ShortcutOptions } from '$lib/actions/shortcut';
|
||||
import DeleteAssetDialog from '$lib/components/photos-page/delete-asset-dialog.svelte';
|
||||
import ChangeDate, {
|
||||
type AbsoluteResult,
|
||||
type RelativeResult,
|
||||
} from '$lib/components/shared-components/change-date.svelte';
|
||||
import {
|
||||
setFocusToAsset as setFocusAssetInit,
|
||||
setFocusTo as setFocusToInit,
|
||||
@@ -13,6 +9,7 @@
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||
import NavigateToDateModal from '$lib/modals/NavigateToDateModal.svelte';
|
||||
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
|
||||
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
@@ -24,8 +21,6 @@
|
||||
import { archiveAssets, cancelMultiselect, selectAllAssets, stackAssets } from '$lib/utils/asset-utils';
|
||||
import { AssetVisibility } from '@immich/sdk';
|
||||
import { modalManager } from '@immich/ui';
|
||||
import { DateTime } from 'luxon';
|
||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||
|
||||
interface Props {
|
||||
timelineManager: TimelineManager;
|
||||
@@ -43,7 +38,7 @@
|
||||
scrollToAsset,
|
||||
}: Props = $props();
|
||||
|
||||
let isShowSelectDate = $state(false);
|
||||
const { isViewing: showAssetViewer } = assetViewingStore;
|
||||
|
||||
const trashOrDelete = async (force: boolean = false) => {
|
||||
isShowDeleteConfirmation = false;
|
||||
@@ -150,6 +145,13 @@
|
||||
const setFocusTo = setFocusToInit.bind(undefined, scrollToAsset, timelineManager);
|
||||
const setFocusAsset = setFocusAssetInit.bind(undefined, scrollToAsset);
|
||||
|
||||
const handleOpenDateModal = async () => {
|
||||
const asset = await modalManager.show(NavigateToDateModal, { timelineManager });
|
||||
if (asset) {
|
||||
setFocusAsset(asset);
|
||||
}
|
||||
};
|
||||
|
||||
let shortcutList = $derived(
|
||||
(() => {
|
||||
if (searchStore.isSearchEnabled || $showAssetViewer) {
|
||||
@@ -168,7 +170,7 @@
|
||||
{ shortcut: { key: 'M', shift: true }, onShortcut: () => setFocusTo('later', 'month') },
|
||||
{ shortcut: { key: 'Y' }, onShortcut: () => setFocusTo('earlier', 'year') },
|
||||
{ shortcut: { key: 'Y', shift: true }, onShortcut: () => setFocusTo('later', 'year') },
|
||||
{ shortcut: { key: 'G' }, onShortcut: () => (isShowSelectDate = true) },
|
||||
{ shortcut: { key: 'G' }, onShortcut: handleOpenDateModal },
|
||||
];
|
||||
if (onEscape) {
|
||||
shortcuts.push({ shortcut: { key: 'Escape' }, onShortcut: onEscape });
|
||||
@@ -198,24 +200,3 @@
|
||||
onConfirm={() => handlePromiseError(trashOrDelete(true))}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if isShowSelectDate}
|
||||
<ChangeDate
|
||||
withDuration={false}
|
||||
title="Navigate to Time"
|
||||
initialDate={DateTime.now()}
|
||||
timezoneInput={false}
|
||||
onConfirm={async (dateString: AbsoluteResult | RelativeResult) => {
|
||||
isShowSelectDate = false;
|
||||
if (dateString.mode == 'absolute') {
|
||||
const asset = await timelineManager.getClosestAssetToDate(
|
||||
(DateTime.fromISO(dateString.date) as DateTime<true>).toObject(),
|
||||
);
|
||||
if (asset) {
|
||||
setFocusAsset(asset);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onCancel={() => (isShowSelectDate = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user