From 966ab220656850d0173b4f58d2807fec0f2e0885 Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Tue, 7 Oct 2025 10:01:06 -0400 Subject: [PATCH] refactor(web): extract asset viewer logic from Timeline into TimelineAssetViewer component (#22268) refactor(web): extract asset viewer logic from Timeline into TimelineAssetViewer component - Extracted asset viewer navigation and action handling logic from Timeline.svelte into a dedicated TimelineAssetViewer component - Reduces Timeline.svelte complexity by ~150 lines and improves separation of concerns - No functional changes - purely a refactoring to improve code organization ## Changes - Created new TimelineAssetViewer.svelte component containing all asset viewer-related logic - Moved handlePrevious, handleNext, handleRandom, handleClose, handlePreAction, and handleAction methods - Timeline.svelte now only passes required props to the new component - Maintained all existing functionality including navigation, asset actions, and stack management --- .../lib/components/timeline/Timeline.svelte | 147 +-------------- .../timeline/TimelineAssetViewer.svelte | 177 ++++++++++++++++++ 2 files changed, 182 insertions(+), 142 deletions(-) create mode 100644 web/src/lib/components/timeline/TimelineAssetViewer.svelte diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index ff8386aae9..f323235a13 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -2,14 +2,13 @@ import { afterNavigate, beforeNavigate } from '$app/navigation'; import { page } from '$app/stores'; import { resizeObserver, type OnResizeCallback } from '$lib/actions/resize-observer'; - import type { Action } from '$lib/components/asset-viewer/actions/action'; import Scrubber from '$lib/components/timeline/Scrubber.svelte'; + import TimelineAssetViewer from '$lib/components/timeline/TimelineAssetViewer.svelte'; import TimelineKeyboardActions from '$lib/components/timeline/actions/TimelineKeyboardActions.svelte'; import { AssetAction } from '$lib/constants'; import HotModuleReload from '$lib/elements/HotModuleReload.svelte'; import Portal from '$lib/elements/Portal.svelte'; import Skeleton from '$lib/elements/Skeleton.svelte'; - import { authManager } from '$lib/managers/auth-manager.svelte'; 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'; @@ -19,10 +18,9 @@ import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { isSelectingAllAssets } from '$lib/stores/assets-store.svelte'; import { mobileDevice } from '$lib/stores/mobile-device.svelte'; - import { updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions'; import { navigate } from '$lib/utils/navigation'; - import { getTimes, toTimelineAsset, type ScrubberListener, type TimelineYearMonth } from '$lib/utils/timeline-util'; - import { getAssetInfo, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk'; + import { getTimes, type ScrubberListener, type TimelineYearMonth } from '$lib/utils/timeline-util'; + import { type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk'; import { DateTime } from 'luxon'; import { onMount, type Snippet } from 'svelte'; import type { UpdatePayload } from 'vite'; @@ -89,7 +87,7 @@ onThumbnailClick, }: Props = $props(); - let { isViewing: showAssetViewer, asset: viewingAsset, preloadAssets, gridScrollTarget, mutex } = assetViewingStore; + let { isViewing: showAssetViewer, asset: viewingAsset, gridScrollTarget } = assetViewingStore; let element: HTMLElement | undefined = $state(); @@ -392,126 +390,6 @@ } }; - const handlePrevious = async () => { - const release = await mutex.acquire(); - const laterAsset = await timelineManager.getLaterAsset($viewingAsset); - - if (laterAsset) { - const preloadAsset = await timelineManager.getLaterAsset(laterAsset); - const asset = await getAssetInfo({ ...authManager.params, id: laterAsset.id }); - assetViewingStore.setAsset(asset, preloadAsset ? [preloadAsset] : []); - await navigate({ targetRoute: 'current', assetId: laterAsset.id }); - } - - release(); - return !!laterAsset; - }; - - const handleNext = async () => { - const release = await mutex.acquire(); - const earlierAsset = await timelineManager.getEarlierAsset($viewingAsset); - - if (earlierAsset) { - const preloadAsset = await timelineManager.getEarlierAsset(earlierAsset); - const asset = await getAssetInfo({ ...authManager.params, id: earlierAsset.id }); - assetViewingStore.setAsset(asset, preloadAsset ? [preloadAsset] : []); - await navigate({ targetRoute: 'current', assetId: earlierAsset.id }); - } - - release(); - return !!earlierAsset; - }; - - const handleRandom = async () => { - const randomAsset = await timelineManager.getRandomAsset(); - - if (randomAsset) { - const asset = await getAssetInfo({ ...authManager.params, id: randomAsset.id }); - assetViewingStore.setAsset(asset); - await navigate({ targetRoute: 'current', assetId: randomAsset.id }); - return asset; - } - }; - - const handleClose = async (asset: { id: string }) => { - assetViewingStore.showAssetViewer(false); - showSkeleton = true; - $gridScrollTarget = { at: asset.id }; - await navigate({ targetRoute: 'current', assetId: null, assetGridRouteSearchParams: $gridScrollTarget }); - }; - - const handlePreAction = async (action: Action) => { - switch (action.type) { - case removeAction: - case AssetAction.TRASH: - case AssetAction.RESTORE: - case AssetAction.DELETE: - case AssetAction.ARCHIVE: - case AssetAction.SET_VISIBILITY_LOCKED: - case AssetAction.SET_VISIBILITY_TIMELINE: { - // find the next asset to show or close the viewer - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - (await handleNext()) || (await handlePrevious()) || (await handleClose(action.asset)); - - // delete after find the next one - timelineManager.removeAssets([action.asset.id]); - break; - } - } - }; - const handleAction = (action: Action) => { - switch (action.type) { - case AssetAction.ARCHIVE: - case AssetAction.UNARCHIVE: - case AssetAction.FAVORITE: - case AssetAction.UNFAVORITE: { - timelineManager.updateAssets([action.asset]); - break; - } - - case AssetAction.ADD: { - timelineManager.addAssets([action.asset]); - break; - } - - case AssetAction.UNSTACK: { - updateUnstackedAssetInTimeline(timelineManager, action.assets); - break; - } - case AssetAction.REMOVE_ASSET_FROM_STACK: { - timelineManager.addAssets([toTimelineAsset(action.asset)]); - if (action.stack) { - //Have to unstack then restack assets in timeline in order to update the stack count in the timeline. - updateUnstackedAssetInTimeline( - timelineManager, - action.stack.assets.map((asset) => toTimelineAsset(asset)), - ); - updateStackedAssetInTimeline(timelineManager, { - stack: action.stack, - toDeleteIds: action.stack.assets - .filter((asset) => asset.id !== action.stack?.primaryAssetId) - .map((asset) => asset.id), - }); - } - break; - } - case AssetAction.SET_STACK_PRIMARY_ASSET: { - //Have to unstack then restack assets in timeline in order for the currently removed new primary asset to be made visible. - updateUnstackedAssetInTimeline( - timelineManager, - action.stack.assets.map((asset) => toTimelineAsset(asset)), - ); - updateStackedAssetInTimeline(timelineManager, { - stack: action.stack, - toDeleteIds: action.stack.assets - .filter((asset) => asset.id !== action.stack.primaryAssetId) - .map((asset) => asset.id), - }); - break; - } - } - }; - let lastAssetMouseEvent: TimelineAsset | null = $state(null); let shiftKeyIsDown = $state(false); @@ -808,22 +686,7 @@ {#if $showAssetViewer} - {#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} - - {/await} + {/if} diff --git a/web/src/lib/components/timeline/TimelineAssetViewer.svelte b/web/src/lib/components/timeline/TimelineAssetViewer.svelte new file mode 100644 index 0000000000..17e3ed06f3 --- /dev/null +++ b/web/src/lib/components/timeline/TimelineAssetViewer.svelte @@ -0,0 +1,177 @@ + + +{#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} + +{/await}