refactor: TimelineManager is owned by Timeline.svelte (#22839)

feat: TimelineManager is owned by Timeline.svelte
This commit is contained in:
Min Idzelis 2025-10-15 13:27:44 -04:00 committed by GitHub
parent f1e03d0022
commit b3055d2e94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 71 additions and 88 deletions

View file

@ -17,7 +17,6 @@
import type { AlbumResponseDto, SharedLinkResponseDto, UserResponseDto } from '@immich/sdk'; import type { AlbumResponseDto, SharedLinkResponseDto, UserResponseDto } from '@immich/sdk';
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
import { mdiDownload, mdiFileImagePlusOutline } from '@mdi/js'; import { mdiDownload, mdiFileImagePlusOutline } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import ControlAppBar from '../shared-components/control-app-bar.svelte'; import ControlAppBar from '../shared-components/control-app-bar.svelte';
import ImmichLogoSmallLink from '../shared-components/immich-logo-small-link.svelte'; import ImmichLogoSmallLink from '../shared-components/immich-logo-small-link.svelte';
@ -35,9 +34,8 @@
let { isViewing: showAssetViewer } = assetViewingStore; let { isViewing: showAssetViewer } = assetViewingStore;
const timelineManager = new TimelineManager(); const options = $derived({ albumId: album.id, order: album.order });
$effect(() => void timelineManager.updateOptions({ albumId: album.id, order: album.order })); let timelineManager = $state<TimelineManager>() as TimelineManager;
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
@ -61,7 +59,7 @@
/> />
<main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-(--navbar-height-md) pt-(--navbar-height)"> <main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-(--navbar-height-md) pt-(--navbar-height)">
<Timeline enableRouting={true} {album} {timelineManager} {assetInteraction}> <Timeline enableRouting={true} {album} bind:timelineManager {options} {assetInteraction}>
<section class="pt-8 md:pt-24 px-2 md:px-0"> <section class="pt-8 md:pt-24 px-2 md:px-0">
<!-- ALBUM TITLE --> <!-- ALBUM TITLE -->
<h1 class="text-2xl md:text-4xl lg:text-6xl text-primary outline-none transition-all"> <h1 class="text-2xl md:text-4xl lg:text-6xl text-primary outline-none transition-all">

View file

@ -12,7 +12,7 @@
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte'; import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset, ViewportTopMonth } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset, TimelineManagerOptions, ViewportTopMonth } from '$lib/managers/timeline-manager/types';
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte'; import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
@ -22,7 +22,7 @@
import { getTimes, type ScrubberListener } from '$lib/utils/timeline-util'; import { getTimes, type ScrubberListener } from '$lib/utils/timeline-util';
import { type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk'; import { type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { onMount, type Snippet } from 'svelte'; import { onDestroy, onMount, type Snippet } from 'svelte';
import type { UpdatePayload } from 'vite'; import type { UpdatePayload } from 'vite';
import TimelineDateGroup from './TimelineDateGroup.svelte'; import TimelineDateGroup from './TimelineDateGroup.svelte';
@ -33,7 +33,8 @@
`AssetViewingStore.gridScrollTarget` and load and scroll to the asset specified, and `AssetViewingStore.gridScrollTarget` and load and scroll to the asset specified, and
additionally, update the page location/url with the asset as the timeline is scrolled */ additionally, update the page location/url with the asset as the timeline is scrolled */
enableRouting: boolean; enableRouting: boolean;
timelineManager: TimelineManager; timelineManager?: TimelineManager;
options?: TimelineManagerOptions;
assetInteraction: AssetInteraction; assetInteraction: AssetInteraction;
removeAction?: removeAction?:
| AssetAction.UNARCHIVE | AssetAction.UNARCHIVE
@ -71,6 +72,7 @@
singleSelect = false, singleSelect = false,
enableRouting, enableRouting,
timelineManager = $bindable(), timelineManager = $bindable(),
options,
assetInteraction, assetInteraction,
removeAction = null, removeAction = null,
withStacked = false, withStacked = false,
@ -87,6 +89,10 @@
onThumbnailClick, onThumbnailClick,
}: Props = $props(); }: Props = $props();
timelineManager = new TimelineManager();
onDestroy(() => timelineManager.destroy());
$effect(() => options && void timelineManager.updateOptions(options));
let { isViewing: showAssetViewer, asset: viewingAsset, gridScrollTarget } = assetViewingStore; let { isViewing: showAssetViewer, asset: viewingAsset, gridScrollTarget } = assetViewingStore;
let scrollableElement: HTMLElement | undefined = $state(); let scrollableElement: HTMLElement | undefined = $state();
@ -207,13 +213,6 @@
} }
}; };
const handleBeforeUpdate = (payload: UpdatePayload) => {
const timelineUpdate = payload.updates.some((update) => update.path.endsWith('Timeline.svelte'));
if (timelineUpdate) {
timelineManager.destroy();
}
};
const updateIsScrolling = () => (timelineManager.scrolling = true); const updateIsScrolling = () => (timelineManager.scrolling = true);
// note: don't throttle, debounch, or otherwise do this function async - it causes flicker // note: don't throttle, debounch, or otherwise do this function async - it causes flicker
@ -498,7 +497,7 @@
<svelte:document onkeydown={onKeyDown} onkeyup={onKeyUp} /> <svelte:document onkeydown={onKeyDown} onkeyup={onKeyUp} />
<HotModuleReload onAfterUpdate={handleAfterUpdate} onBeforeUpdate={handleBeforeUpdate} /> <HotModuleReload onAfterUpdate={handleAfterUpdate} onBeforeUpdate={() => timelineManager.destroy()} />
<TimelineKeyboardActions <TimelineKeyboardActions
scrollToAsset={(asset) => scrollToAsset(asset) ?? false} scrollToAsset={(asset) => scrollToAsset(asset) ?? false}

View file

@ -100,8 +100,6 @@ export class TimelineManager {
#updatingIntersections = false; #updatingIntersections = false;
#scrollableElement: HTMLElement | undefined = $state(); #scrollableElement: HTMLElement | undefined = $state();
constructor() {}
setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }: TimelineManagerLayoutOptions) { setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }: TimelineManagerLayoutOptions) {
let changed = false; let changed = false;
changed ||= this.#setHeaderHeight(headerHeight); changed ||= this.#setHeaderHeight(headerHeight);

View file

@ -328,18 +328,19 @@
} }
}); });
let timelineManager = new TimelineManager(); let timelineManager = $state<TimelineManager>() as TimelineManager;
const options = $derived.by(() => {
$effect(() => {
if (viewMode === AlbumPageViewMode.VIEW) { if (viewMode === AlbumPageViewMode.VIEW) {
void timelineManager.updateOptions({ albumId, order: albumOrder }); return { albumId, order: albumOrder };
} else if (viewMode === AlbumPageViewMode.SELECT_ASSETS) { }
void timelineManager.updateOptions({ if (viewMode === AlbumPageViewMode.SELECT_ASSETS) {
return {
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
withPartners: true, withPartners: true,
timelineAlbumId: albumId, timelineAlbumId: albumId,
}); };
} }
return {};
}); });
const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 0); const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 0);
@ -352,10 +353,7 @@
handlePromiseError(activityManager.init(album.id)); handlePromiseError(activityManager.init(album.id));
}); });
onDestroy(() => { onDestroy(() => activityManager.reset());
activityManager.reset();
timelineManager.destroy();
});
let isOwned = $derived($user.id == album.ownerId); let isOwned = $derived($user.id == album.ownerId);
@ -446,7 +444,8 @@
<Timeline <Timeline
enableRouting={viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : true} enableRouting={viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : true}
{album} {album}
{timelineManager} bind:timelineManager
{options}
assetInteraction={currentAssetIntersection} assetInteraction={currentAssetIntersection}
{isShared} {isShared}
{isSelectionMode} {isSelectionMode}

View file

@ -18,7 +18,6 @@
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetVisibility } from '@immich/sdk'; import { AssetVisibility } from '@immich/sdk';
import { mdiDotsVertical, mdiPlus } from '@mdi/js'; import { mdiDotsVertical, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -27,9 +26,8 @@
} }
let { data }: Props = $props(); let { data }: Props = $props();
const timelineManager = new TimelineManager(); let timelineManager = $state<TimelineManager>() as TimelineManager;
void timelineManager.updateOptions({ visibility: AssetVisibility.Archive }); const options = { visibility: AssetVisibility.Archive };
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
@ -49,7 +47,8 @@
<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}> <UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
<Timeline <Timeline
enableRouting={true} enableRouting={true}
{timelineManager} bind:timelineManager
{options}
{assetInteraction} {assetInteraction}
removeAction={AssetAction.UNARCHIVE} removeAction={AssetAction.UNARCHIVE}
onEscape={handleEscape} onEscape={handleEscape}

View file

@ -21,7 +21,6 @@
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { preferences } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { mdiDotsVertical, mdiPlus } from '@mdi/js'; import { mdiDotsVertical, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -31,9 +30,8 @@
let { data }: Props = $props(); let { data }: Props = $props();
const timelineManager = new TimelineManager(); let timelineManager = $state<TimelineManager>() as TimelineManager;
void timelineManager.updateOptions({ isFavorite: true, withStacked: true }); const options = { isFavorite: true, withStacked: true };
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
@ -54,7 +52,8 @@
<Timeline <Timeline
enableRouting={true} enableRouting={true}
withStacked={true} withStacked={true}
{timelineManager} bind:timelineManager
{options}
{assetInteraction} {assetInteraction}
removeAction={AssetAction.UNFAVORITE} removeAction={AssetAction.UNFAVORITE}
onEscape={handleEscape} onEscape={handleEscape}

View file

@ -17,7 +17,6 @@
import { AssetVisibility, lockAuthSession } from '@immich/sdk'; import { AssetVisibility, lockAuthSession } from '@immich/sdk';
import { Button } from '@immich/ui'; import { Button } from '@immich/ui';
import { mdiDotsVertical, mdiLockOutline } from '@mdi/js'; import { mdiDotsVertical, mdiLockOutline } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -27,9 +26,8 @@
let { data }: Props = $props(); let { data }: Props = $props();
const timelineManager = new TimelineManager(); let timelineManager = $state<TimelineManager>() as TimelineManager;
void timelineManager.updateOptions({ visibility: AssetVisibility.Locked }); const options = { visibility: AssetVisibility.Locked };
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
@ -60,7 +58,8 @@
<Timeline <Timeline
enableRouting={true} enableRouting={true}
{timelineManager} bind:timelineManager
{options}
{assetInteraction} {assetInteraction}
onEscape={handleEscape} onEscape={handleEscape}
removeAction={AssetAction.SET_VISIBILITY_TIMELINE} removeAction={AssetAction.SET_VISIBILITY_TIMELINE}

View file

@ -8,11 +8,9 @@
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import Timeline from '$lib/components/timeline/Timeline.svelte'; import Timeline from '$lib/components/timeline/Timeline.svelte';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { AssetVisibility } from '@immich/sdk'; import { AssetVisibility } from '@immich/sdk';
import { mdiArrowLeft, mdiPlus } from '@mdi/js'; import { mdiArrowLeft, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -22,16 +20,12 @@
let { data }: Props = $props(); let { data }: Props = $props();
const timelineManager = new TimelineManager(); const options = $derived({
$effect( userId: data.partner.id,
() => visibility: AssetVisibility.Timeline,
void timelineManager.updateOptions({ withStacked: true,
userId: data.partner.id, });
visibility: AssetVisibility.Timeline,
withStacked: true,
}),
);
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
const handleEscape = () => { const handleEscape = () => {
@ -43,7 +37,7 @@
</script> </script>
<main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-(--navbar-height-md) pt-(--navbar-height)"> <main class="relative h-dvh overflow-hidden px-2 md:px-6 max-md:pt-(--navbar-height-md) pt-(--navbar-height)">
<Timeline enableRouting={true} {timelineManager} {assetInteraction} onEscape={handleEscape} /> <Timeline enableRouting={true} {options} {assetInteraction} onEscape={handleEscape} />
</main> </main>
{#if assetInteraction.selectionActive} {#if assetInteraction.selectionActive}

View file

@ -63,7 +63,7 @@
mdiPlus, mdiPlus,
} from '@mdi/js'; } from '@mdi/js';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { onDestroy, onMount } from 'svelte'; import { onMount } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -76,10 +76,8 @@
let numberOfAssets = $state(data.statistics.assets); let numberOfAssets = $state(data.statistics.assets);
let { isViewing: showAssetViewer } = assetViewingStore; let { isViewing: showAssetViewer } = assetViewingStore;
const timelineManager = new TimelineManager(); let timelineManager = $state<TimelineManager>() as TimelineManager;
$effect(() => void timelineManager.updateOptions({ visibility: AssetVisibility.Timeline, personId: data.person.id })); const options = $derived({ visibility: AssetVisibility.Timeline, personId: data.person.id });
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS); let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS);
@ -388,7 +386,8 @@
<Timeline <Timeline
enableRouting={true} enableRouting={true}
{person} {person}
{timelineManager} bind:timelineManager
{options}
{assetInteraction} {assetInteraction}
isSelectionMode={viewMode === PersonPageViewMode.SELECT_PERSON} isSelectionMode={viewMode === PersonPageViewMode.SELECT_PERSON}
singleSelect={viewMode === PersonPageViewMode.SELECT_PERSON} singleSelect={viewMode === PersonPageViewMode.SELECT_PERSON}

View file

@ -37,13 +37,12 @@
import { AssetVisibility } from '@immich/sdk'; import { AssetVisibility } from '@immich/sdk';
import { mdiDotsVertical, mdiPlus } from '@mdi/js'; import { mdiDotsVertical, mdiPlus } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
let { isViewing: showAssetViewer } = assetViewingStore; let { isViewing: showAssetViewer } = assetViewingStore;
const timelineManager = new TimelineManager(); let timelineManager = $state<TimelineManager>() as TimelineManager;
void timelineManager.updateOptions({ visibility: AssetVisibility.Timeline, withStacked: true, withPartners: true }); const options = { visibility: AssetVisibility.Timeline, withStacked: true, withPartners: true };
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
@ -91,7 +90,8 @@
<UserPageLayout hideNavbar={assetInteraction.selectionActive} showUploadButton scrollbar={false}> <UserPageLayout hideNavbar={assetInteraction.selectionActive} showUploadButton scrollbar={false}>
<Timeline <Timeline
enableRouting={true} enableRouting={true}
{timelineManager} bind:timelineManager
{options}
{assetInteraction} {assetInteraction}
removeAction={AssetAction.ARCHIVE} removeAction={AssetAction.ARCHIVE}
onEscape={handleEscape} onEscape={handleEscape}

View file

@ -21,7 +21,6 @@
import TagAction from '$lib/components/timeline/actions/TagAction.svelte'; import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import { AppRoute, QueryParameter } from '$lib/constants'; import { AppRoute, QueryParameter } from '$lib/constants';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
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';
@ -79,8 +78,6 @@
}); });
}); });
let timelineManager = new TimelineManager();
const onEscape = () => { const onEscape = () => {
if ($showAssetViewer) { if ($showAssetViewer) {
return; return;
@ -129,7 +126,6 @@
}; };
const handleSetVisibility = (assetIds: string[]) => { const handleSetVisibility = (assetIds: string[]) => {
timelineManager.removeAssets(assetIds);
assetInteraction.clearMultiselect(); assetInteraction.clearMultiselect();
onAssetDelete(assetIds); onAssetDelete(assetIds);
}; };

View file

@ -16,7 +16,6 @@
import { deleteTag, getAllTags, type TagResponseDto } from '@immich/sdk'; import { deleteTag, getAllTags, type TagResponseDto } from '@immich/sdk';
import { Button, HStack, modalManager, Text } from '@immich/ui'; import { Button, HStack, modalManager, Text } from '@immich/ui';
import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js'; import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -28,14 +27,13 @@
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
const timelineManager = new TimelineManager();
$effect(() => void timelineManager.updateOptions({ deferInit: !tag, tagId: tag?.id }));
onDestroy(() => timelineManager.destroy());
let tags = $derived<TagResponseDto[]>(data.tags); let tags = $derived<TagResponseDto[]>(data.tags);
const tree = $derived(TreeNode.fromTags(tags)); const tree = $derived(TreeNode.fromTags(tags));
const tag = $derived(tree.traverse(data.path)); const tag = $derived(tree.traverse(data.path));
let timelineManager = $state<TimelineManager>() as TimelineManager;
const options = $derived({ deferInit: !tag, tagId: tag?.id });
const handleNavigation = (tag: string) => navigateToView(joinPaths(data.path, tag)); const handleNavigation = (tag: string) => navigateToView(joinPaths(data.path, tag));
const getLink = (path: string) => { const getLink = (path: string) => {
@ -117,7 +115,13 @@
<section class="mt-2 h-[calc(100%-(--spacing(20)))] overflow-auto immich-scrollbar"> <section class="mt-2 h-[calc(100%-(--spacing(20)))] overflow-auto immich-scrollbar">
{#if tag.hasAssets} {#if tag.hasAssets}
<Timeline enableRouting={true} {timelineManager} {assetInteraction} removeAction={AssetAction.UNARCHIVE}> <Timeline
enableRouting={true}
bind:timelineManager
{options}
{assetInteraction}
removeAction={AssetAction.UNARCHIVE}
>
{#snippet empty()} {#snippet empty()}
<TreeItemThumbnails items={tag.children} icon={mdiTag} onClick={handleNavigation} /> <TreeItemThumbnails items={tag.children} icon={mdiTag} onClick={handleNavigation} />
{/snippet} {/snippet}

View file

@ -21,7 +21,6 @@
import { emptyTrash, restoreTrash } from '@immich/sdk'; import { emptyTrash, restoreTrash } from '@immich/sdk';
import { Button, HStack, modalManager, Text } from '@immich/ui'; import { Button, HStack, modalManager, Text } from '@immich/ui';
import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js'; import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -35,9 +34,8 @@
handlePromiseError(goto(AppRoute.PHOTOS)); handlePromiseError(goto(AppRoute.PHOTOS));
} }
const timelineManager = new TimelineManager(); let timelineManager = $state<TimelineManager>() as TimelineManager;
void timelineManager.updateOptions({ isTrashed: true }); const options = { isTrashed: true };
onDestroy(() => timelineManager.destroy());
const assetInteraction = new AssetInteraction(); const assetInteraction = new AssetInteraction();
@ -116,7 +114,7 @@
</HStack> </HStack>
{/snippet} {/snippet}
<Timeline enableRouting={true} {timelineManager} {assetInteraction} onEscape={handleEscape}> <Timeline enableRouting={true} {options} {assetInteraction} onEscape={handleEscape}>
<p class="font-medium text-gray-500/60 dark:text-gray-300/60 p-4"> <p class="font-medium text-gray-500/60 dark:text-gray-300/60 p-4">
{$t('trashed_items_will_be_permanently_deleted_after', { values: { days: $serverConfig.trashDays } })} {$t('trashed_items_will_be_permanently_deleted_after', { values: { days: $serverConfig.trashDays } })}
</p> </p>

View file

@ -30,13 +30,13 @@
let location = $state<{ latitude: number; longitude: number }>({ latitude: 0, longitude: 0 }); let location = $state<{ latitude: number; longitude: number }>({ latitude: 0, longitude: 0 });
let locationUpdated = $state(false); let locationUpdated = $state(false);
const timelineManager = new TimelineManager(); let timelineManager = $state<TimelineManager>() as TimelineManager;
void timelineManager.updateOptions({ const options = {
visibility: AssetVisibility.Timeline, visibility: AssetVisibility.Timeline,
withStacked: true, withStacked: true,
withPartners: true, withPartners: true,
withCoordinates: true, withCoordinates: true,
}); };
const handleUpdate = async () => { const handleUpdate = async () => {
const confirmed = await modalManager.show(GeolocationUpdateConfirmModal, { const confirmed = await modalManager.show(GeolocationUpdateConfirmModal, {
@ -188,7 +188,8 @@
<Timeline <Timeline
isSelectionMode={true} isSelectionMode={true}
enableRouting={true} enableRouting={true}
{timelineManager} bind:timelineManager
{options}
{assetInteraction} {assetInteraction}
removeAction={AssetAction.ARCHIVE} removeAction={AssetAction.ARCHIVE}
onEscape={handleEscape} onEscape={handleEscape}