diff --git a/web/src/lib/components/timeline/Timeline.svelte b/web/src/lib/components/timeline/Timeline.svelte index ba9cf37bffc49..6e21479accb97 100644 --- a/web/src/lib/components/timeline/Timeline.svelte +++ b/web/src/lib/components/timeline/Timeline.svelte @@ -188,7 +188,7 @@ // the performance benefits of deferred layouts while still supporting deep linking // to assets at the end of the timeline. timelineManager.isScrollingOnLoad = true; - const monthGroup = await timelineManager.findMonthGroupForAsset(assetId); + const monthGroup = await timelineManager.findMonthGroupForAsset({ id: assetId }); if (!monthGroup) { return false; } diff --git a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts index f889456c2066c..708267370050e 100644 --- a/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts +++ b/web/src/lib/managers/timeline-manager/internal/search-support.svelte.ts @@ -1,5 +1,5 @@ import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util'; -import { AssetOrder } from '@immich/sdk'; +import { AssetOrder, type AssetResponseDto } from '@immich/sdk'; import { DateTime } from 'luxon'; import type { MonthGroup } from '../month-group.svelte'; import { TimelineManager } from '../timeline-manager.svelte'; @@ -7,12 +7,16 @@ import type { AssetDescriptor, Direction, TimelineAsset } from '../types'; export async function getAssetWithOffset( timelineManager: TimelineManager, - assetDescriptor: AssetDescriptor, + assetDescriptor: AssetDescriptor | AssetResponseDto, interval: 'asset' | 'day' | 'month' | 'year' = 'asset', direction: Direction, ): Promise { - const { asset, monthGroup } = findMonthGroupForAsset(timelineManager, assetDescriptor.id) ?? {}; - if (!monthGroup || !asset) { + const monthGroup = await timelineManager.findMonthGroupForAsset(assetDescriptor); + if (!monthGroup) { + return; + } + const asset = monthGroup.findAssetById(assetDescriptor); + if (!asset) { return; } diff --git a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts index bb58704214cec..8e31f281382b5 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.spec.ts @@ -524,6 +524,7 @@ describe('TimelineManager', () => { { count: 3, timeBucket: '2024-01-01T00:00:00.000Z' }, ]); sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssetsResponse[timeBucket])); + sdkMock.getAssetInfo.mockRejectedValue(new Error('Asset not found')); await timelineManager.updateViewport({ width: 1588, height: 1000 }); }); 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 feba73a0f8730..b6c43480ef6b6 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -16,12 +16,13 @@ import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websoc import { CancellableTask } from '$lib/utils/cancellable-task'; import { PersistedLocalStorage } from '$lib/utils/persisted'; import { + isAssetResponseDto, setDifference, toTimelineAsset, type TimelineDateTime, type TimelineYearMonth, } from '$lib/utils/timeline-util'; -import { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk'; +import { AssetOrder, getAssetInfo, getTimeBuckets, type AssetResponseDto } from '@immich/sdk'; import { clamp, isEqual } from 'lodash-es'; import { SvelteDate, SvelteSet } from 'svelte/reactivity'; import { DayGroup } from './day-group.svelte'; @@ -343,27 +344,30 @@ export class TimelineManager extends VirtualScrollManager { this.addAssetsUpsertSegments([...notExcluded]); } - async findMonthGroupForAsset(id: string) { + async findMonthGroupForAsset(asset: AssetDescriptor | AssetResponseDto) { if (!this.isInitialized) { await this.initTask.waitUntilCompletion(); } + const { id } = asset; let { monthGroup } = findMonthGroupForAssetUtil(this, id) ?? {}; if (monthGroup) { return monthGroup; } - const response = await getAssetInfo({ ...authManager.params, id }).catch(() => null); + const response = isAssetResponseDto(asset) + ? asset + : await getAssetInfo({ ...authManager.params, id }).catch(() => null); if (!response) { return; } - const asset = toTimelineAsset(response); - if (!asset || this.isExcluded(asset)) { + const timelineAsset = toTimelineAsset(response); + if (this.isExcluded(timelineAsset)) { return; } - monthGroup = await this.#loadMonthGroupAtTime(asset.localDateTime, { cancelable: false }); + monthGroup = await this.#loadMonthGroupAtTime(timelineAsset.localDateTime, { cancelable: false }); if (monthGroup?.findAssetById({ id })) { return monthGroup; } @@ -532,14 +536,14 @@ export class TimelineManager extends VirtualScrollManager { } async getLaterAsset( - assetDescriptor: AssetDescriptor, + assetDescriptor: AssetDescriptor | AssetResponseDto, interval: 'asset' | 'day' | 'month' | 'year' = 'asset', ): Promise { return await getAssetWithOffset(this, assetDescriptor, interval, 'later'); } async getEarlierAsset( - assetDescriptor: AssetDescriptor, + assetDescriptor: AssetDescriptor | AssetResponseDto, interval: 'asset' | 'day' | 'month' | 'year' = 'asset', ): Promise { return await getAssetWithOffset(this, assetDescriptor, interval, 'earlier'); diff --git a/web/src/lib/utils/timeline-util.ts b/web/src/lib/utils/timeline-util.ts index 3326676f3ceb2..f8fb49b61f8e1 100644 --- a/web/src/lib/utils/timeline-util.ts +++ b/web/src/lib/utils/timeline-util.ts @@ -1,4 +1,4 @@ -import type { TimelineAsset, ViewportTopMonth } from '$lib/managers/timeline-manager/types'; +import type { AssetDescriptor, TimelineAsset, ViewportTopMonth } from '$lib/managers/timeline-manager/types'; import { locale } from '$lib/stores/preferences.store'; import { getAssetRatio } from '$lib/utils/asset-utils'; import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; @@ -192,8 +192,13 @@ export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): }; }; -export const isTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): unknownAsset is TimelineAsset => - (unknownAsset as TimelineAsset).ratio !== undefined; +export const isTimelineAsset = ( + unknownAsset: AssetDescriptor | AssetResponseDto | TimelineAsset, +): unknownAsset is TimelineAsset => (unknownAsset as TimelineAsset).ratio !== undefined; + +export const isAssetResponseDto = ( + unknownAsset: AssetDescriptor | AssetResponseDto | TimelineAsset, +): unknownAsset is AssetResponseDto => (unknownAsset as AssetResponseDto).type !== undefined; export const isTimelineAssets = (assets: AssetResponseDto[] | TimelineAsset[]): assets is TimelineAsset[] => assets.length === 0 || 'ratio' in assets[0];