feat: timeline-manager improvement to use AssetResponseDto efficiently (#24421)

pull/19178/merge
Min Idzelis 2025-12-10 19:07:31 +07:00 committed by GitHub
parent cbdf5011f9
commit 161147af51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 30 additions and 16 deletions

@ -188,7 +188,7 @@
// the performance benefits of deferred layouts while still supporting deep linking // the performance benefits of deferred layouts while still supporting deep linking
// to assets at the end of the timeline. // to assets at the end of the timeline.
timelineManager.isScrollingOnLoad = true; timelineManager.isScrollingOnLoad = true;
const monthGroup = await timelineManager.findMonthGroupForAsset(assetId); const monthGroup = await timelineManager.findMonthGroupForAsset({ id: assetId });
if (!monthGroup) { if (!monthGroup) {
return false; return false;
} }

@ -1,5 +1,5 @@
import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timeline-util'; 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 { DateTime } from 'luxon';
import type { MonthGroup } from '../month-group.svelte'; import type { MonthGroup } from '../month-group.svelte';
import { TimelineManager } from '../timeline-manager.svelte'; import { TimelineManager } from '../timeline-manager.svelte';
@ -7,12 +7,16 @@ import type { AssetDescriptor, Direction, TimelineAsset } from '../types';
export async function getAssetWithOffset( export async function getAssetWithOffset(
timelineManager: TimelineManager, timelineManager: TimelineManager,
assetDescriptor: AssetDescriptor, assetDescriptor: AssetDescriptor | AssetResponseDto,
interval: 'asset' | 'day' | 'month' | 'year' = 'asset', interval: 'asset' | 'day' | 'month' | 'year' = 'asset',
direction: Direction, direction: Direction,
): Promise<TimelineAsset | undefined> { ): Promise<TimelineAsset | undefined> {
const { asset, monthGroup } = findMonthGroupForAsset(timelineManager, assetDescriptor.id) ?? {}; const monthGroup = await timelineManager.findMonthGroupForAsset(assetDescriptor);
if (!monthGroup || !asset) { if (!monthGroup) {
return;
}
const asset = monthGroup.findAssetById(assetDescriptor);
if (!asset) {
return; return;
} }

@ -524,6 +524,7 @@ describe('TimelineManager', () => {
{ count: 3, timeBucket: '2024-01-01T00:00:00.000Z' }, { count: 3, timeBucket: '2024-01-01T00:00:00.000Z' },
]); ]);
sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssetsResponse[timeBucket])); sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssetsResponse[timeBucket]));
sdkMock.getAssetInfo.mockRejectedValue(new Error('Asset not found'));
await timelineManager.updateViewport({ width: 1588, height: 1000 }); await timelineManager.updateViewport({ width: 1588, height: 1000 });
}); });

@ -16,12 +16,13 @@ import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websoc
import { CancellableTask } from '$lib/utils/cancellable-task'; import { CancellableTask } from '$lib/utils/cancellable-task';
import { PersistedLocalStorage } from '$lib/utils/persisted'; import { PersistedLocalStorage } from '$lib/utils/persisted';
import { import {
isAssetResponseDto,
setDifference, setDifference,
toTimelineAsset, toTimelineAsset,
type TimelineDateTime, type TimelineDateTime,
type TimelineYearMonth, type TimelineYearMonth,
} from '$lib/utils/timeline-util'; } 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 { clamp, isEqual } from 'lodash-es';
import { SvelteDate, SvelteSet } from 'svelte/reactivity'; import { SvelteDate, SvelteSet } from 'svelte/reactivity';
import { DayGroup } from './day-group.svelte'; import { DayGroup } from './day-group.svelte';
@ -343,27 +344,30 @@ export class TimelineManager extends VirtualScrollManager {
this.addAssetsUpsertSegments([...notExcluded]); this.addAssetsUpsertSegments([...notExcluded]);
} }
async findMonthGroupForAsset(id: string) { async findMonthGroupForAsset(asset: AssetDescriptor | AssetResponseDto) {
if (!this.isInitialized) { if (!this.isInitialized) {
await this.initTask.waitUntilCompletion(); await this.initTask.waitUntilCompletion();
} }
const { id } = asset;
let { monthGroup } = findMonthGroupForAssetUtil(this, id) ?? {}; let { monthGroup } = findMonthGroupForAssetUtil(this, id) ?? {};
if (monthGroup) { if (monthGroup) {
return 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) { if (!response) {
return; return;
} }
const asset = toTimelineAsset(response); const timelineAsset = toTimelineAsset(response);
if (!asset || this.isExcluded(asset)) { if (this.isExcluded(timelineAsset)) {
return; return;
} }
monthGroup = await this.#loadMonthGroupAtTime(asset.localDateTime, { cancelable: false }); monthGroup = await this.#loadMonthGroupAtTime(timelineAsset.localDateTime, { cancelable: false });
if (monthGroup?.findAssetById({ id })) { if (monthGroup?.findAssetById({ id })) {
return monthGroup; return monthGroup;
} }
@ -532,14 +536,14 @@ export class TimelineManager extends VirtualScrollManager {
} }
async getLaterAsset( async getLaterAsset(
assetDescriptor: AssetDescriptor, assetDescriptor: AssetDescriptor | AssetResponseDto,
interval: 'asset' | 'day' | 'month' | 'year' = 'asset', interval: 'asset' | 'day' | 'month' | 'year' = 'asset',
): Promise<TimelineAsset | undefined> { ): Promise<TimelineAsset | undefined> {
return await getAssetWithOffset(this, assetDescriptor, interval, 'later'); return await getAssetWithOffset(this, assetDescriptor, interval, 'later');
} }
async getEarlierAsset( async getEarlierAsset(
assetDescriptor: AssetDescriptor, assetDescriptor: AssetDescriptor | AssetResponseDto,
interval: 'asset' | 'day' | 'month' | 'year' = 'asset', interval: 'asset' | 'day' | 'month' | 'year' = 'asset',
): Promise<TimelineAsset | undefined> { ): Promise<TimelineAsset | undefined> {
return await getAssetWithOffset(this, assetDescriptor, interval, 'earlier'); return await getAssetWithOffset(this, assetDescriptor, interval, 'earlier');

@ -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 { locale } from '$lib/stores/preferences.store';
import { getAssetRatio } from '$lib/utils/asset-utils'; import { getAssetRatio } from '$lib/utils/asset-utils';
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; 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 => export const isTimelineAsset = (
(unknownAsset as TimelineAsset).ratio !== undefined; 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[] => export const isTimelineAssets = (assets: AssetResponseDto[] | TimelineAsset[]): assets is TimelineAsset[] =>
assets.length === 0 || 'ratio' in assets[0]; assets.length === 0 || 'ratio' in assets[0];