From ab35afd3b18c4de9786e89e72241d6e6971b3074 Mon Sep 17 00:00:00 2001 From: Min Idzelis Date: Mon, 1 Dec 2025 09:04:39 -0600 Subject: [PATCH] refactor(web): reimplement operation-support as part of timeline-manager (#24056) * refactor(web): reimplement operation-support as part of timeline-manager Improve clarity of methods. Add inline method documentation. Make return type of AssetOperation optional. * Review comments - self document code. remove optional return from callback --- .../actions/TimelineKeyboardActions.svelte | 5 +- .../timeline-manager/day-group.svelte.ts | 7 +- .../internal/operations-support.svelte.ts | 104 ----------- .../timeline-manager/month-group.svelte.ts | 14 +- .../timeline-manager.svelte.spec.ts | 3 +- .../timeline-manager.svelte.ts | 170 ++++++++++++++---- .../lib/managers/timeline-manager/types.ts | 2 - web/src/lib/utils/actions.ts | 19 +- .../[[assetId=id]]/+page.svelte | 12 +- .../[[assetId=id]]/+page.svelte | 12 +- .../[[assetId=id]]/+page.svelte | 6 +- .../[[assetId=id]]/+page.svelte | 12 +- .../(user)/photos/[[assetId=id]]/+page.svelte | 12 +- 13 files changed, 168 insertions(+), 210 deletions(-) delete mode 100644 web/src/lib/managers/timeline-manager/internal/operations-support.svelte.ts diff --git a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte index d5b1d2ecf6..b731635355 100644 --- a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte +++ b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte @@ -80,10 +80,7 @@ const toggleArchive = async () => { const visibility = assetInteraction.isAllArchived ? AssetVisibility.Timeline : AssetVisibility.Archive; const ids = await archiveAssets(assetInteraction.selectedAssets, visibility); - timelineManager.updateAssetOperation(ids, (asset) => { - asset.visibility = visibility; - return { remove: false }; - }); + timelineManager.update(ids, (asset) => (asset.visibility = visibility)); deselectAllAssets(); }; diff --git a/web/src/lib/managers/timeline-manager/day-group.svelte.ts b/web/src/lib/managers/timeline-manager/day-group.svelte.ts index 934ca1d4ff..e21e54a6e5 100644 --- a/web/src/lib/managers/timeline-manager/day-group.svelte.ts +++ b/web/src/lib/managers/timeline-manager/day-group.svelte.ts @@ -6,7 +6,7 @@ import { plainDateTimeCompare } from '$lib/utils/timeline-util'; import { SvelteSet } from 'svelte/reactivity'; import type { MonthGroup } from './month-group.svelte'; -import type { AssetOperation, Direction, MoveAsset, TimelineAsset } from './types'; +import type { Direction, MoveAsset, TimelineAsset } from './types'; import { ViewerAsset } from './viewer-asset.svelte'; export class DayGroup { @@ -101,7 +101,7 @@ export class DayGroup { return this.viewerAssets.map((viewerAsset) => viewerAsset.asset); } - runAssetOperation(ids: Set, operation: AssetOperation) { + runAssetCallback(ids: Set, callback: (asset: TimelineAsset) => void | { remove?: boolean }) { if (ids.size === 0) { return { moveAssets: [] as MoveAsset[], @@ -122,7 +122,8 @@ export class DayGroup { const asset = this.viewerAssets[index].asset!; const oldTime = { ...asset.localDateTime }; - let { remove } = operation(asset); + const callbackResult = callback(asset); + let remove = (callbackResult as { remove?: boolean } | undefined)?.remove ?? false; const newTime = asset.localDateTime; if (oldTime.year !== newTime.year || oldTime.month !== newTime.month || oldTime.day !== newTime.day) { const { year, month, day } = newTime; diff --git a/web/src/lib/managers/timeline-manager/internal/operations-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/operations-support.svelte.ts deleted file mode 100644 index 4bc99c0315..0000000000 --- a/web/src/lib/managers/timeline-manager/internal/operations-support.svelte.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { setDifference, type TimelineDate } from '$lib/utils/timeline-util'; -import { AssetOrder } from '@immich/sdk'; - -import { SvelteSet } from 'svelte/reactivity'; -import { GroupInsertionCache } from '../group-insertion-cache.svelte'; -import { MonthGroup } from '../month-group.svelte'; -import type { TimelineManager } from '../timeline-manager.svelte'; -import type { AssetOperation, TimelineAsset } from '../types'; -import { updateGeometry } from './layout-support.svelte'; -import { getMonthGroupByDate } from './search-support.svelte'; - -export function addAssetsToMonthGroups( - timelineManager: TimelineManager, - assets: TimelineAsset[], - options: { order: AssetOrder }, -) { - if (assets.length === 0) { - return; - } - - const addContext = new GroupInsertionCache(); - const updatedMonthGroups = new SvelteSet(); - const monthCount = timelineManager.months.length; - for (const asset of assets) { - let month = getMonthGroupByDate(timelineManager, asset.localDateTime); - - if (!month) { - month = new MonthGroup(timelineManager, asset.localDateTime, 1, options.order); - month.isLoaded = true; - timelineManager.months.push(month); - } - - month.addTimelineAsset(asset, addContext); - updatedMonthGroups.add(month); - } - - if (timelineManager.months.length !== monthCount) { - timelineManager.months.sort((a, b) => { - return a.yearMonth.year === b.yearMonth.year - ? b.yearMonth.month - a.yearMonth.month - : b.yearMonth.year - a.yearMonth.year; - }); - } - - for (const group of addContext.existingDayGroups) { - group.sortAssets(options.order); - } - - for (const monthGroup of addContext.bucketsWithNewDayGroups) { - monthGroup.sortDayGroups(); - } - - for (const month of addContext.updatedBuckets) { - month.sortDayGroups(); - updateGeometry(timelineManager, month, { invalidateHeight: true }); - } - timelineManager.updateIntersections(); -} - -export function runAssetOperation( - timelineManager: TimelineManager, - ids: Set, - operation: AssetOperation, - options: { order: AssetOrder }, -) { - if (ids.size === 0) { - return { processedIds: new SvelteSet(), unprocessedIds: ids, changedGeometry: false }; - } - - const changedMonthGroups = new SvelteSet(); - let idsToProcess = new SvelteSet(ids); - const idsProcessed = new SvelteSet(); - const combinedMoveAssets: { asset: TimelineAsset; date: TimelineDate }[][] = []; - for (const month of timelineManager.months) { - if (idsToProcess.size > 0) { - const { moveAssets, processedIds, changedGeometry } = month.runAssetOperation(idsToProcess, operation); - if (moveAssets.length > 0) { - combinedMoveAssets.push(moveAssets); - } - idsToProcess = setDifference(idsToProcess, processedIds); - for (const id of processedIds) { - idsProcessed.add(id); - } - if (changedGeometry) { - changedMonthGroups.add(month); - } - } - } - if (combinedMoveAssets.length > 0) { - addAssetsToMonthGroups( - timelineManager, - combinedMoveAssets.flat().map((a) => a.asset), - options, - ); - } - const changedGeometry = changedMonthGroups.size > 0; - for (const month of changedMonthGroups) { - updateGeometry(timelineManager, month, { invalidateHeight: true }); - } - if (changedGeometry) { - timelineManager.updateIntersections(); - } - return { unprocessedIds: idsToProcess, processedIds: idsProcessed, changedGeometry }; -} diff --git a/web/src/lib/managers/timeline-manager/month-group.svelte.ts b/web/src/lib/managers/timeline-manager/month-group.svelte.ts index 1d9e1bbaa7..3926055cca 100644 --- a/web/src/lib/managers/timeline-manager/month-group.svelte.ts +++ b/web/src/lib/managers/timeline-manager/month-group.svelte.ts @@ -21,7 +21,7 @@ import { SvelteSet } from 'svelte/reactivity'; import { DayGroup } from './day-group.svelte'; import { GroupInsertionCache } from './group-insertion-cache.svelte'; import type { TimelineManager } from './timeline-manager.svelte'; -import type { AssetDescriptor, AssetOperation, Direction, MoveAsset, TimelineAsset } from './types'; +import type { AssetDescriptor, Direction, MoveAsset, TimelineAsset } from './types'; import { ViewerAsset } from './viewer-asset.svelte'; export class MonthGroup { @@ -50,12 +50,13 @@ export class MonthGroup { readonly yearMonth: TimelineYearMonth; constructor( - store: TimelineManager, + timelineManager: TimelineManager, yearMonth: TimelineYearMonth, initialCount: number, + loaded: boolean, order: AssetOrder = AssetOrder.Desc, ) { - this.timelineManager = store; + this.timelineManager = timelineManager; this.#initialCount = initialCount; this.#sortOrder = order; @@ -72,6 +73,9 @@ export class MonthGroup { }, this.#handleLoadError, ); + if (loaded) { + this.isLoaded = true; + } } set intersecting(newValue: boolean) { @@ -112,7 +116,7 @@ export class MonthGroup { return this.dayGroups.sort((a, b) => b.day - a.day); } - runAssetOperation(ids: Set, operation: AssetOperation) { + runAssetCallback(ids: Set, callback: (asset: TimelineAsset) => void | { remove?: boolean }) { if (ids.size === 0) { return { moveAssets: [] as MoveAsset[], @@ -130,7 +134,7 @@ export class MonthGroup { while (index--) { if (idsToProcess.size > 0) { const group = dayGroups[index]; - const { moveAssets, processedIds, changedGeometry } = group.runAssetOperation(ids, operation); + const { moveAssets, processedIds, changedGeometry } = group.runAssetCallback(ids, callback); if (moveAssets.length > 0) { combinedMoveAssets.push(moveAssets); } 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 62053f7a0d..2c63348f88 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 @@ -278,10 +278,11 @@ describe('TimelineManager', () => { }); it('updates existing asset', () => { + const updateAssetsSpy = vi.spyOn(timelineManager, 'upsertAssets'); const asset = deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build()); timelineManager.upsertAssets([asset]); - timelineManager.upsertAssets([asset]); + expect(updateAssetsSpy).toBeCalledWith([asset]); expect(timelineManager.assetCount).toEqual(1); }); 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 e3327663b4..93b8364930 100644 --- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts +++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts @@ -1,12 +1,9 @@ import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; +import { GroupInsertionCache } from '$lib/managers/timeline-manager/group-insertion-cache.svelte'; import { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte'; import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-support.svelte'; import { loadFromTimeBuckets } from '$lib/managers/timeline-manager/internal/load-support.svelte'; -import { - addAssetsToMonthGroups, - runAssetOperation, -} from '$lib/managers/timeline-manager/internal/operations-support.svelte'; import { findClosestGroupForDate, findMonthGroupForAsset as findMonthGroupForAssetUtil, @@ -17,17 +14,22 @@ import { } from '$lib/managers/timeline-manager/internal/search-support.svelte'; import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte'; import { CancellableTask } from '$lib/utils/cancellable-task'; -import { toTimelineAsset, type TimelineDateTime, type TimelineYearMonth } from '$lib/utils/timeline-util'; +import { + setDifference, + toTimelineAsset, + type TimelineDateTime, + type TimelineYearMonth, +} from '$lib/utils/timeline-util'; import { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk'; import { clamp, isEqual } from 'lodash-es'; -import { SvelteDate, SvelteMap, SvelteSet } from 'svelte/reactivity'; +import { SvelteDate, SvelteSet } from 'svelte/reactivity'; import { DayGroup } from './day-group.svelte'; import { isMismatched, updateObject } from './internal/utils.svelte'; import { MonthGroup } from './month-group.svelte'; import type { AssetDescriptor, - AssetOperation, Direction, + MoveAsset, ScrubberMonth, TimelineAsset, TimelineManagerOptions, @@ -218,6 +220,7 @@ export class TimelineManager extends VirtualScrollManager { this, { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1 }, timeBucket.count, + false, this.#options.order, ); }); @@ -323,7 +326,7 @@ export class TimelineManager extends VirtualScrollManager { upsertAssets(assets: TimelineAsset[]) { const notUpdated = this.#updateAssets(assets); const notExcluded = notUpdated.filter((asset) => !this.isExcluded(asset)); - addAssetsToMonthGroups(this, [...notExcluded], { order: this.#options.order ?? AssetOrder.Desc }); + this.addAssetsUpsertSegments([...notExcluded]); } async findMonthGroupForAsset(id: string) { @@ -400,38 +403,107 @@ export class TimelineManager extends VirtualScrollManager { return randomDay.viewerAssets[randomAssetIndex - accumulatedCount].asset; } - updateAssetOperation(ids: string[], operation: AssetOperation) { - runAssetOperation(this, new SvelteSet(ids), operation, { order: this.#options.order ?? AssetOrder.Desc }); - } - - #updateAssets(assets: TimelineAsset[]) { - const lookup = new SvelteMap(assets.map((asset) => [asset.id, asset])); - const { unprocessedIds } = runAssetOperation( - this, - new SvelteSet(lookup.keys()), - (asset) => { - updateObject(asset, lookup.get(asset.id)); - return { remove: false }; - }, - { order: this.#options.order ?? AssetOrder.Desc }, - ); - const result: TimelineAsset[] = []; - for (const id of unprocessedIds.values()) { - result.push(lookup.get(id)!); - } - return result; + /** + * Executes callback on assets, handling moves between groups and removals due to filter criteria. + */ + update(ids: string[], callback: (asset: TimelineAsset) => void) { + // eslint-disable-next-line svelte/prefer-svelte-reactivity + return this.#runAssetCallback(new Set(ids), callback); } removeAssets(ids: string[]) { - const { unprocessedIds } = runAssetOperation( - this, - new SvelteSet(ids), - () => { - return { remove: true }; - }, - { order: this.#options.order ?? AssetOrder.Desc }, - ); - return [...unprocessedIds]; + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const result = this.#runAssetCallback(new Set(ids), () => ({ remove: true })); + return [...result.notUpdated]; + } + + protected upsertSegmentForAsset(asset: TimelineAsset) { + let month = getMonthGroupByDate(this, asset.localDateTime); + + if (!month) { + month = new MonthGroup(this, asset.localDateTime, 1, true, this.#options.order); + this.months.push(month); + } + return month; + } + + /** + * Adds assets to existing segments, creating new segments as needed. + * + * This is an internal method that assumes the provided assets are not already + * present in the timeline. For updating existing assets, use updateAssetOperation(). + */ + protected addAssetsUpsertSegments(assets: TimelineAsset[]) { + if (assets.length === 0) { + return; + } + const context = new GroupInsertionCache(); + const monthCount = this.months.length; + for (const asset of assets) { + this.upsertSegmentForAsset(asset).addTimelineAsset(asset, context); + } + if (this.months.length !== monthCount) { + this.postCreateSegments(); + } + this.postUpsert(context); + } + + #updateAssets(assets: TimelineAsset[]) { + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const cache = new Map(assets.map((asset) => [asset.id, asset])); + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const idsToUpdate = new Set(cache.keys()); + const result = this.#runAssetCallback(idsToUpdate, (asset) => void updateObject(asset, cache.get(asset.id))); + const notUpdated: TimelineAsset[] = []; + for (const assetId of result.notUpdated) { + notUpdated.push(cache.get(assetId)!); + } + return notUpdated; + } + + #runAssetCallback(ids: Set, callback: (asset: TimelineAsset) => void | { remove?: boolean }) { + if (ids.size === 0) { + // eslint-disable-next-line svelte/prefer-svelte-reactivity + return { updated: new Set(), notUpdated: ids, changedGeometry: false }; + } + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const changedMonthGroups = new Set(); + // eslint-disable-next-line svelte/prefer-svelte-reactivity + let notUpdated = new Set(ids); + // eslint-disable-next-line svelte/prefer-svelte-reactivity + const updated = new Set(); + const assetsToMoveSegments: MoveAsset[][] = []; + for (const month of this.months) { + if (notUpdated.size === 0) { + break; + } + const result = month.runAssetCallback(notUpdated, callback); + if (result.moveAssets.length > 0) { + assetsToMoveSegments.push(result.moveAssets); + } + if (result.changedGeometry) { + changedMonthGroups.add(month); + } + notUpdated = setDifference(notUpdated, result.processedIds); + for (const id of result.processedIds) { + updated.add(id); + } + } + const assetsToAdd = []; + for (const segment of assetsToMoveSegments) { + for (const moveAsset of segment) { + assetsToAdd.push(moveAsset.asset); + } + } + this.addAssetsUpsertSegments(assetsToAdd); + const changedGeometry = changedMonthGroups.size > 0; + for (const month of changedMonthGroups) { + updateGeometry(this, month, { invalidateHeight: true }); + } + if (changedGeometry) { + this.updateIntersections(); + } + return { updated, notUpdated, changedGeometry }; } override refreshLayout() { @@ -493,4 +565,28 @@ export class TimelineManager extends VirtualScrollManager { getAssetOrder() { return this.#options.order ?? AssetOrder.Desc; } + + protected postCreateSegments(): void { + this.months.sort((a, b) => { + return a.yearMonth.year === b.yearMonth.year + ? b.yearMonth.month - a.yearMonth.month + : b.yearMonth.year - a.yearMonth.year; + }); + } + + protected postUpsert(context: GroupInsertionCache): void { + for (const group of context.existingDayGroups) { + group.sortAssets(this.#options.order); + } + + for (const monthGroup of context.bucketsWithNewDayGroups) { + monthGroup.sortDayGroups(); + } + + for (const month of context.updatedBuckets) { + month.sortDayGroups(); + updateGeometry(this, month, { invalidateHeight: true }); + } + this.updateIntersections(); + } } diff --git a/web/src/lib/managers/timeline-manager/types.ts b/web/src/lib/managers/timeline-manager/types.ts index 27c27dcb63..35d7178f97 100644 --- a/web/src/lib/managers/timeline-manager/types.ts +++ b/web/src/lib/managers/timeline-manager/types.ts @@ -37,8 +37,6 @@ export type TimelineAsset = { longitude?: number | null; }; -export type AssetOperation = (asset: TimelineAsset) => { remove: boolean }; - export type MoveAsset = { asset: TimelineAsset; date: TimelineDate }; export interface Viewport { diff --git a/web/src/lib/utils/actions.ts b/web/src/lib/utils/actions.ts index 2eb081a490..05de75d3bc 100644 --- a/web/src/lib/utils/actions.ts +++ b/web/src/lib/utils/actions.ts @@ -79,14 +79,15 @@ const undoDeleteAssets = async (onUndoDelete: OnUndoDelete, assets: TimelineAsse */ export function updateStackedAssetInTimeline(timelineManager: TimelineManager, { stack, toDeleteIds }: StackResponse) { if (stack != undefined) { - timelineManager.updateAssetOperation([stack.primaryAssetId], (asset) => { - asset.stack = { - id: stack.id, - primaryAssetId: stack.primaryAssetId, - assetCount: stack.assets.length, - }; - return { remove: false }; - }); + timelineManager.update( + [stack.primaryAssetId], + (asset) => + (asset.stack = { + id: stack.id, + primaryAssetId: stack.primaryAssetId, + assetCount: stack.assets.length, + }), + ); timelineManager.removeAssets(toDeleteIds); } @@ -101,7 +102,7 @@ export function updateStackedAssetInTimeline(timelineManager: TimelineManager, { * @param assets - The array of asset response DTOs to update in the timeline manager. */ export function updateUnstackedAssetInTimeline(timelineManager: TimelineManager, assets: TimelineAsset[]) { - timelineManager.updateAssetOperation( + timelineManager.update( assets.map((asset) => asset.id), (asset) => { asset.stack = null; 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 27cc4a7faa..02dc55ead2 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 @@ -555,11 +555,7 @@ {#if assetInteraction.isAllUserOwned} - timelineManager.updateAssetOperation(ids, (asset) => { - asset.isFavorite = isFavorite; - return { remove: false }; - })} + onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} > {/if} @@ -578,11 +574,7 @@ - timelineManager.updateAssetOperation(ids, (asset) => { - asset.visibility = visibility; - return { remove: false }; - })} + onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> {/if} diff --git a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte index ac1ffc356c..42dcedc106 100644 --- a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -66,11 +66,7 @@ > - timelineManager.updateAssetOperation(ids, (asset) => { - asset.visibility = visibility; - return { remove: false }; - })} + onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> @@ -80,11 +76,7 @@ - timelineManager.updateAssetOperation(ids, (asset) => { - asset.isFavorite = isFavorite; - return { remove: false }; - })} + onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} /> diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte index 7cb3bf8e17..781dc80ec8 100644 --- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -85,11 +85,7 @@ - timelineManager.updateAssetOperation(ids, (asset) => { - asset.visibility = visibility; - return { remove: false }; - })} + onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> {#if $preferences.tags.enabled} 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 5dabd58e76..c822855310 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 @@ -492,11 +492,7 @@ - timelineManager.updateAssetOperation(ids, (asset) => { - asset.isFavorite = isFavorite; - return { remove: false }; - })} + onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} /> @@ -511,11 +507,7 @@ - timelineManager.updateAssetOperation(ids, (asset) => { - asset.visibility = visibility; - return { remove: false }; - })} + onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} diff --git a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte index 669ea23921..8bf8dce94e 100644 --- a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte @@ -120,11 +120,7 @@ - timelineManager.updateAssetOperation(ids, (asset) => { - asset.isFavorite = isFavorite; - return { remove: false }; - })} + onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} > @@ -148,11 +144,7 @@ - timelineManager.updateAssetOperation(ids, (asset) => { - asset.visibility = visibility; - return { remove: false }; - })} + onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> {#if $preferences.tags.enabled}