diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 37ad260324..8d169fbc8f 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -17,7 +17,6 @@ import type { AlbumResponseDto, SharedLinkResponseDto, UserResponseDto } from '@immich/sdk'; import { IconButton } from '@immich/ui'; import { mdiDownload, mdiFileImagePlusOutline } from '@mdi/js'; - import { onDestroy } from 'svelte'; import { t } from 'svelte-i18n'; import ControlAppBar from '../shared-components/control-app-bar.svelte'; import ImmichLogoSmallLink from '../shared-components/immich-logo-small-link.svelte'; @@ -35,9 +34,8 @@ let { isViewing: showAssetViewer } = assetViewingStore; - const timelineManager = new TimelineManager(); - $effect(() => void timelineManager.updateOptions({ albumId: album.id, order: album.order })); - onDestroy(() => timelineManager.destroy()); + const options = $derived({ albumId: album.id, order: album.order }); + let timelineManager = $state() as TimelineManager; const assetInteraction = new AssetInteraction(); @@ -61,7 +59,7 @@ />
- +

diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index ed6e5c2eb4..0b99e55fa5 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -12,7 +12,7 @@ import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte'; import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.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 type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; @@ -22,7 +22,7 @@ import { getTimes, type ScrubberListener } from '$lib/utils/timeline-util'; import { type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk'; import { DateTime } from 'luxon'; - import { onMount, type Snippet } from 'svelte'; + import { onDestroy, onMount, type Snippet } from 'svelte'; import type { UpdatePayload } from 'vite'; import TimelineDateGroup from './TimelineDateGroup.svelte'; @@ -33,7 +33,8 @@ `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 */ enableRouting: boolean; - timelineManager: TimelineManager; + timelineManager?: TimelineManager; + options?: TimelineManagerOptions; assetInteraction: AssetInteraction; removeAction?: | AssetAction.UNARCHIVE @@ -71,6 +72,7 @@ singleSelect = false, enableRouting, timelineManager = $bindable(), + options, assetInteraction, removeAction = null, withStacked = false, @@ -87,6 +89,10 @@ onThumbnailClick, }: Props = $props(); + timelineManager = new TimelineManager(); + onDestroy(() => timelineManager.destroy()); + $effect(() => options && void timelineManager.updateOptions(options)); + let { isViewing: showAssetViewer, asset: viewingAsset, gridScrollTarget } = assetViewingStore; 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); // note: don't throttle, debounch, or otherwise do this function async - it causes flicker @@ -498,7 +497,7 @@ - + timelineManager.destroy()} /> scrollToAsset(asset) ?? false} diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts index 6b4de52467..2938485bc1 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -100,8 +100,6 @@ export class TimelineManager { #updatingIntersections = false; #scrollableElement: HTMLElement | undefined = $state(); - constructor() {} - setLayoutOptions({ headerHeight = 48, rowHeight = 235, gap = 12 }: TimelineManagerLayoutOptions) { let changed = false; changed ||= this.#setHeaderHeight(headerHeight); diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 8c8b84ce16..8441362b64 100644 --- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -328,18 +328,19 @@ } }); - let timelineManager = new TimelineManager(); - - $effect(() => { + let timelineManager = $state() as TimelineManager; + const options = $derived.by(() => { if (viewMode === AlbumPageViewMode.VIEW) { - void timelineManager.updateOptions({ albumId, order: albumOrder }); - } else if (viewMode === AlbumPageViewMode.SELECT_ASSETS) { - void timelineManager.updateOptions({ + return { albumId, order: albumOrder }; + } + if (viewMode === AlbumPageViewMode.SELECT_ASSETS) { + return { visibility: AssetVisibility.Timeline, withPartners: true, timelineAlbumId: albumId, - }); + }; } + return {}; }); const isShared = $derived(viewMode === AlbumPageViewMode.SELECT_ASSETS ? false : album.albumUsers.length > 0); @@ -352,10 +353,7 @@ handlePromiseError(activityManager.init(album.id)); }); - onDestroy(() => { - activityManager.reset(); - timelineManager.destroy(); - }); + onDestroy(() => activityManager.reset()); let isOwned = $derived($user.id == album.ownerId); @@ -446,7 +444,8 @@ timelineManager.destroy()); + let timelineManager = $state() as TimelineManager; + const options = { visibility: AssetVisibility.Archive }; const assetInteraction = new AssetInteraction(); @@ -49,7 +47,8 @@ timelineManager.destroy()); + let timelineManager = $state() as TimelineManager; + const options = { isFavorite: true, withStacked: true }; const assetInteraction = new AssetInteraction(); @@ -54,7 +52,8 @@ timelineManager.destroy()); + let timelineManager = $state() as TimelineManager; + const options = { visibility: AssetVisibility.Locked }; const assetInteraction = new AssetInteraction(); @@ -60,7 +58,8 @@ - void timelineManager.updateOptions({ - userId: data.partner.id, - visibility: AssetVisibility.Timeline, - withStacked: true, - }), - ); - onDestroy(() => timelineManager.destroy()); + const options = $derived({ + userId: data.partner.id, + visibility: AssetVisibility.Timeline, + withStacked: true, + }); + const assetInteraction = new AssetInteraction(); const handleEscape = () => { @@ -43,7 +37,7 @@
- +
{#if assetInteraction.selectionActive} 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 5de52fc689..31c599f19b 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 @@ -63,7 +63,7 @@ mdiPlus, } from '@mdi/js'; import { DateTime } from 'luxon'; - import { onDestroy, onMount } from 'svelte'; + import { onMount } from 'svelte'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; @@ -76,10 +76,8 @@ let numberOfAssets = $state(data.statistics.assets); let { isViewing: showAssetViewer } = assetViewingStore; - const timelineManager = new TimelineManager(); - $effect(() => void timelineManager.updateOptions({ visibility: AssetVisibility.Timeline, personId: data.person.id })); - onDestroy(() => timelineManager.destroy()); - + let timelineManager = $state() as TimelineManager; + const options = $derived({ visibility: AssetVisibility.Timeline, personId: data.person.id }); const assetInteraction = new AssetInteraction(); let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS); @@ -388,7 +386,8 @@ timelineManager.destroy()); + let timelineManager = $state() as TimelineManager; + const options = { visibility: AssetVisibility.Timeline, withStacked: true, withPartners: true }; const assetInteraction = new AssetInteraction(); @@ -91,7 +90,8 @@ { if ($showAssetViewer) { return; @@ -129,7 +126,6 @@ }; const handleSetVisibility = (assetIds: string[]) => { - timelineManager.removeAssets(assetIds); assetInteraction.clearMultiselect(); onAssetDelete(assetIds); }; diff --git a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte index 20db2b23bb..442d3cef6c 100644 --- a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -16,7 +16,6 @@ import { deleteTag, getAllTags, type TagResponseDto } from '@immich/sdk'; import { Button, HStack, modalManager, Text } from '@immich/ui'; import { mdiPencil, mdiPlus, mdiTag, mdiTagMultiple, mdiTrashCanOutline } from '@mdi/js'; - import { onDestroy } from 'svelte'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; @@ -28,14 +27,13 @@ const assetInteraction = new AssetInteraction(); - const timelineManager = new TimelineManager(); - $effect(() => void timelineManager.updateOptions({ deferInit: !tag, tagId: tag?.id })); - onDestroy(() => timelineManager.destroy()); - let tags = $derived(data.tags); const tree = $derived(TreeNode.fromTags(tags)); const tag = $derived(tree.traverse(data.path)); + let timelineManager = $state() as TimelineManager; + const options = $derived({ deferInit: !tag, tagId: tag?.id }); + const handleNavigation = (tag: string) => navigateToView(joinPaths(data.path, tag)); const getLink = (path: string) => { @@ -117,7 +115,13 @@
{#if tag.hasAssets} - + {#snippet empty()} {/snippet} diff --git a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte index 20a9746a4b..9615618445 100644 --- a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -21,7 +21,6 @@ import { emptyTrash, restoreTrash } from '@immich/sdk'; import { Button, HStack, modalManager, Text } from '@immich/ui'; import { mdiDeleteForeverOutline, mdiHistory } from '@mdi/js'; - import { onDestroy } from 'svelte'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; @@ -35,9 +34,8 @@ handlePromiseError(goto(AppRoute.PHOTOS)); } - const timelineManager = new TimelineManager(); - void timelineManager.updateOptions({ isTrashed: true }); - onDestroy(() => timelineManager.destroy()); + let timelineManager = $state() as TimelineManager; + const options = { isTrashed: true }; const assetInteraction = new AssetInteraction(); @@ -116,7 +114,7 @@ {/snippet} - +

{$t('trashed_items_will_be_permanently_deleted_after', { values: { days: $serverConfig.trashDays } })}

diff --git a/web/src/routes/(user)/utilities/geolocation/+page.svelte b/web/src/routes/(user)/utilities/geolocation/+page.svelte index 5441257df4..732e0625ab 100644 --- a/web/src/routes/(user)/utilities/geolocation/+page.svelte +++ b/web/src/routes/(user)/utilities/geolocation/+page.svelte @@ -30,13 +30,13 @@ let location = $state<{ latitude: number; longitude: number }>({ latitude: 0, longitude: 0 }); let locationUpdated = $state(false); - const timelineManager = new TimelineManager(); - void timelineManager.updateOptions({ + let timelineManager = $state() as TimelineManager; + const options = { visibility: AssetVisibility.Timeline, withStacked: true, withPartners: true, withCoordinates: true, - }); + }; const handleUpdate = async () => { const confirmed = await modalManager.show(GeolocationUpdateConfirmModal, { @@ -188,7 +188,8 @@