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
pull/24319/head^2
Min Idzelis 2025-12-01 09:04:39 -06:00 committed by GitHub
parent 65e4fdf98d
commit ab35afd3b1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 168 additions and 210 deletions

View File

@ -80,10 +80,7 @@
const toggleArchive = async () => { const toggleArchive = async () => {
const visibility = assetInteraction.isAllArchived ? AssetVisibility.Timeline : AssetVisibility.Archive; const visibility = assetInteraction.isAllArchived ? AssetVisibility.Timeline : AssetVisibility.Archive;
const ids = await archiveAssets(assetInteraction.selectedAssets, visibility); const ids = await archiveAssets(assetInteraction.selectedAssets, visibility);
timelineManager.updateAssetOperation(ids, (asset) => { timelineManager.update(ids, (asset) => (asset.visibility = visibility));
asset.visibility = visibility;
return { remove: false };
});
deselectAllAssets(); deselectAllAssets();
}; };

View File

@ -6,7 +6,7 @@ import { plainDateTimeCompare } from '$lib/utils/timeline-util';
import { SvelteSet } from 'svelte/reactivity'; import { SvelteSet } from 'svelte/reactivity';
import type { MonthGroup } from './month-group.svelte'; 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'; import { ViewerAsset } from './viewer-asset.svelte';
export class DayGroup { export class DayGroup {
@ -101,7 +101,7 @@ export class DayGroup {
return this.viewerAssets.map((viewerAsset) => viewerAsset.asset); return this.viewerAssets.map((viewerAsset) => viewerAsset.asset);
} }
runAssetOperation(ids: Set<string>, operation: AssetOperation) { runAssetCallback(ids: Set<string>, callback: (asset: TimelineAsset) => void | { remove?: boolean }) {
if (ids.size === 0) { if (ids.size === 0) {
return { return {
moveAssets: [] as MoveAsset[], moveAssets: [] as MoveAsset[],
@ -122,7 +122,8 @@ export class DayGroup {
const asset = this.viewerAssets[index].asset!; const asset = this.viewerAssets[index].asset!;
const oldTime = { ...asset.localDateTime }; 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; const newTime = asset.localDateTime;
if (oldTime.year !== newTime.year || oldTime.month !== newTime.month || oldTime.day !== newTime.day) { if (oldTime.year !== newTime.year || oldTime.month !== newTime.month || oldTime.day !== newTime.day) {
const { year, month, day } = newTime; const { year, month, day } = newTime;

View File

@ -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<MonthGroup>();
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<string>,
operation: AssetOperation,
options: { order: AssetOrder },
) {
if (ids.size === 0) {
return { processedIds: new SvelteSet(), unprocessedIds: ids, changedGeometry: false };
}
const changedMonthGroups = new SvelteSet<MonthGroup>();
let idsToProcess = new SvelteSet(ids);
const idsProcessed = new SvelteSet<string>();
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 };
}

View File

@ -21,7 +21,7 @@ import { SvelteSet } from 'svelte/reactivity';
import { DayGroup } from './day-group.svelte'; import { DayGroup } from './day-group.svelte';
import { GroupInsertionCache } from './group-insertion-cache.svelte'; import { GroupInsertionCache } from './group-insertion-cache.svelte';
import type { TimelineManager } from './timeline-manager.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'; import { ViewerAsset } from './viewer-asset.svelte';
export class MonthGroup { export class MonthGroup {
@ -50,12 +50,13 @@ export class MonthGroup {
readonly yearMonth: TimelineYearMonth; readonly yearMonth: TimelineYearMonth;
constructor( constructor(
store: TimelineManager, timelineManager: TimelineManager,
yearMonth: TimelineYearMonth, yearMonth: TimelineYearMonth,
initialCount: number, initialCount: number,
loaded: boolean,
order: AssetOrder = AssetOrder.Desc, order: AssetOrder = AssetOrder.Desc,
) { ) {
this.timelineManager = store; this.timelineManager = timelineManager;
this.#initialCount = initialCount; this.#initialCount = initialCount;
this.#sortOrder = order; this.#sortOrder = order;
@ -72,6 +73,9 @@ export class MonthGroup {
}, },
this.#handleLoadError, this.#handleLoadError,
); );
if (loaded) {
this.isLoaded = true;
}
} }
set intersecting(newValue: boolean) { set intersecting(newValue: boolean) {
@ -112,7 +116,7 @@ export class MonthGroup {
return this.dayGroups.sort((a, b) => b.day - a.day); return this.dayGroups.sort((a, b) => b.day - a.day);
} }
runAssetOperation(ids: Set<string>, operation: AssetOperation) { runAssetCallback(ids: Set<string>, callback: (asset: TimelineAsset) => void | { remove?: boolean }) {
if (ids.size === 0) { if (ids.size === 0) {
return { return {
moveAssets: [] as MoveAsset[], moveAssets: [] as MoveAsset[],
@ -130,7 +134,7 @@ export class MonthGroup {
while (index--) { while (index--) {
if (idsToProcess.size > 0) { if (idsToProcess.size > 0) {
const group = dayGroups[index]; const group = dayGroups[index];
const { moveAssets, processedIds, changedGeometry } = group.runAssetOperation(ids, operation); const { moveAssets, processedIds, changedGeometry } = group.runAssetCallback(ids, callback);
if (moveAssets.length > 0) { if (moveAssets.length > 0) {
combinedMoveAssets.push(moveAssets); combinedMoveAssets.push(moveAssets);
} }

View File

@ -278,10 +278,11 @@ describe('TimelineManager', () => {
}); });
it('updates existing asset', () => { it('updates existing asset', () => {
const updateAssetsSpy = vi.spyOn(timelineManager, 'upsertAssets');
const asset = deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build()); const asset = deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build());
timelineManager.upsertAssets([asset]); timelineManager.upsertAssets([asset]);
timelineManager.upsertAssets([asset]); expect(updateAssetsSpy).toBeCalledWith([asset]);
expect(timelineManager.assetCount).toEqual(1); expect(timelineManager.assetCount).toEqual(1);
}); });

View File

@ -1,12 +1,9 @@
import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte'; import { VirtualScrollManager } from '$lib/managers/VirtualScrollManager/VirtualScrollManager.svelte';
import { authManager } from '$lib/managers/auth-manager.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 { updateIntersectionMonthGroup } from '$lib/managers/timeline-manager/internal/intersection-support.svelte';
import { updateGeometry } from '$lib/managers/timeline-manager/internal/layout-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 { loadFromTimeBuckets } from '$lib/managers/timeline-manager/internal/load-support.svelte';
import {
addAssetsToMonthGroups,
runAssetOperation,
} from '$lib/managers/timeline-manager/internal/operations-support.svelte';
import { import {
findClosestGroupForDate, findClosestGroupForDate,
findMonthGroupForAsset as findMonthGroupForAssetUtil, findMonthGroupForAsset as findMonthGroupForAssetUtil,
@ -17,17 +14,22 @@ import {
} from '$lib/managers/timeline-manager/internal/search-support.svelte'; } from '$lib/managers/timeline-manager/internal/search-support.svelte';
import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte'; import { WebsocketSupport } from '$lib/managers/timeline-manager/internal/websocket-support.svelte';
import { CancellableTask } from '$lib/utils/cancellable-task'; 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 { AssetOrder, getAssetInfo, getTimeBuckets } from '@immich/sdk';
import { clamp, isEqual } from 'lodash-es'; 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 { DayGroup } from './day-group.svelte';
import { isMismatched, updateObject } from './internal/utils.svelte'; import { isMismatched, updateObject } from './internal/utils.svelte';
import { MonthGroup } from './month-group.svelte'; import { MonthGroup } from './month-group.svelte';
import type { import type {
AssetDescriptor, AssetDescriptor,
AssetOperation,
Direction, Direction,
MoveAsset,
ScrubberMonth, ScrubberMonth,
TimelineAsset, TimelineAsset,
TimelineManagerOptions, TimelineManagerOptions,
@ -218,6 +220,7 @@ export class TimelineManager extends VirtualScrollManager {
this, this,
{ year: date.getUTCFullYear(), month: date.getUTCMonth() + 1 }, { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1 },
timeBucket.count, timeBucket.count,
false,
this.#options.order, this.#options.order,
); );
}); });
@ -323,7 +326,7 @@ export class TimelineManager extends VirtualScrollManager {
upsertAssets(assets: TimelineAsset[]) { upsertAssets(assets: TimelineAsset[]) {
const notUpdated = this.#updateAssets(assets); const notUpdated = this.#updateAssets(assets);
const notExcluded = notUpdated.filter((asset) => !this.isExcluded(asset)); const notExcluded = notUpdated.filter((asset) => !this.isExcluded(asset));
addAssetsToMonthGroups(this, [...notExcluded], { order: this.#options.order ?? AssetOrder.Desc }); this.addAssetsUpsertSegments([...notExcluded]);
} }
async findMonthGroupForAsset(id: string) { async findMonthGroupForAsset(id: string) {
@ -400,38 +403,107 @@ export class TimelineManager extends VirtualScrollManager {
return randomDay.viewerAssets[randomAssetIndex - accumulatedCount].asset; return randomDay.viewerAssets[randomAssetIndex - accumulatedCount].asset;
} }
updateAssetOperation(ids: string[], operation: AssetOperation) { /**
runAssetOperation(this, new SvelteSet(ids), operation, { order: this.#options.order ?? AssetOrder.Desc }); * Executes callback on assets, handling moves between groups and removals due to filter criteria.
} */
update(ids: string[], callback: (asset: TimelineAsset) => void) {
#updateAssets(assets: TimelineAsset[]) { // eslint-disable-next-line svelte/prefer-svelte-reactivity
const lookup = new SvelteMap<string, TimelineAsset>(assets.map((asset) => [asset.id, asset])); return this.#runAssetCallback(new Set(ids), callback);
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;
} }
removeAssets(ids: string[]) { removeAssets(ids: string[]) {
const { unprocessedIds } = runAssetOperation( // eslint-disable-next-line svelte/prefer-svelte-reactivity
this, const result = this.#runAssetCallback(new Set(ids), () => ({ remove: true }));
new SvelteSet(ids), return [...result.notUpdated];
() => { }
return { remove: true };
}, protected upsertSegmentForAsset(asset: TimelineAsset) {
{ order: this.#options.order ?? AssetOrder.Desc }, let month = getMonthGroupByDate(this, asset.localDateTime);
);
return [...unprocessedIds]; 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<string, TimelineAsset>(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<string>, callback: (asset: TimelineAsset) => void | { remove?: boolean }) {
if (ids.size === 0) {
// eslint-disable-next-line svelte/prefer-svelte-reactivity
return { updated: new Set<string>(), notUpdated: ids, changedGeometry: false };
}
// eslint-disable-next-line svelte/prefer-svelte-reactivity
const changedMonthGroups = new Set<MonthGroup>();
// 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<string>();
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() { override refreshLayout() {
@ -493,4 +565,28 @@ export class TimelineManager extends VirtualScrollManager {
getAssetOrder() { getAssetOrder() {
return this.#options.order ?? AssetOrder.Desc; 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();
}
} }

View File

@ -37,8 +37,6 @@ export type TimelineAsset = {
longitude?: number | null; longitude?: number | null;
}; };
export type AssetOperation = (asset: TimelineAsset) => { remove: boolean };
export type MoveAsset = { asset: TimelineAsset; date: TimelineDate }; export type MoveAsset = { asset: TimelineAsset; date: TimelineDate };
export interface Viewport { export interface Viewport {

View File

@ -79,14 +79,15 @@ const undoDeleteAssets = async (onUndoDelete: OnUndoDelete, assets: TimelineAsse
*/ */
export function updateStackedAssetInTimeline(timelineManager: TimelineManager, { stack, toDeleteIds }: StackResponse) { export function updateStackedAssetInTimeline(timelineManager: TimelineManager, { stack, toDeleteIds }: StackResponse) {
if (stack != undefined) { if (stack != undefined) {
timelineManager.updateAssetOperation([stack.primaryAssetId], (asset) => { timelineManager.update(
asset.stack = { [stack.primaryAssetId],
id: stack.id, (asset) =>
primaryAssetId: stack.primaryAssetId, (asset.stack = {
assetCount: stack.assets.length, id: stack.id,
}; primaryAssetId: stack.primaryAssetId,
return { remove: false }; assetCount: stack.assets.length,
}); }),
);
timelineManager.removeAssets(toDeleteIds); 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. * @param assets - The array of asset response DTOs to update in the timeline manager.
*/ */
export function updateUnstackedAssetInTimeline(timelineManager: TimelineManager, assets: TimelineAsset[]) { export function updateUnstackedAssetInTimeline(timelineManager: TimelineManager, assets: TimelineAsset[]) {
timelineManager.updateAssetOperation( timelineManager.update(
assets.map((asset) => asset.id), assets.map((asset) => asset.id),
(asset) => { (asset) => {
asset.stack = null; asset.stack = null;

View File

@ -555,11 +555,7 @@
{#if assetInteraction.isAllUserOwned} {#if assetInteraction.isAllUserOwned}
<FavoriteAction <FavoriteAction
removeFavorite={assetInteraction.isAllFavorite} removeFavorite={assetInteraction.isAllFavorite}
onFavorite={(ids, isFavorite) => onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.isFavorite = isFavorite;
return { remove: false };
})}
></FavoriteAction> ></FavoriteAction>
{/if} {/if}
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')} offset={{ x: 175, y: 25 }}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')} offset={{ x: 175, y: 25 }}>
@ -578,11 +574,7 @@
<ArchiveAction <ArchiveAction
menuItem menuItem
unarchive={assetInteraction.isAllArchived} unarchive={assetInteraction.isAllArchived}
onArchive={(ids, visibility) => onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.visibility = visibility;
return { remove: false };
})}
/> />
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} /> <SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
{/if} {/if}

View File

@ -66,11 +66,7 @@
> >
<ArchiveAction <ArchiveAction
unarchive unarchive
onArchive={(ids, visibility) => onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.visibility = visibility;
return { remove: false };
})}
/> />
<CreateSharedLink /> <CreateSharedLink />
<SelectAllAssets {timelineManager} {assetInteraction} /> <SelectAllAssets {timelineManager} {assetInteraction} />
@ -80,11 +76,7 @@
</ButtonContextMenu> </ButtonContextMenu>
<FavoriteAction <FavoriteAction
removeFavorite={assetInteraction.isAllFavorite} removeFavorite={assetInteraction.isAllFavorite}
onFavorite={(ids, isFavorite) => onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.isFavorite = isFavorite;
return { remove: false };
})}
/> />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem /> <DownloadAction menuItem />

View File

@ -85,11 +85,7 @@
<ArchiveAction <ArchiveAction
menuItem menuItem
unarchive={assetInteraction.isAllArchived} unarchive={assetInteraction.isAllArchived}
onArchive={(ids, visibility) => onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.visibility = visibility;
return { remove: false };
})}
/> />
{#if $preferences.tags.enabled} {#if $preferences.tags.enabled}
<TagAction menuItem /> <TagAction menuItem />

View File

@ -492,11 +492,7 @@
</ButtonContextMenu> </ButtonContextMenu>
<FavoriteAction <FavoriteAction
removeFavorite={assetInteraction.isAllFavorite} removeFavorite={assetInteraction.isAllFavorite}
onFavorite={(ids, isFavorite) => onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.isFavorite = isFavorite;
return { remove: false };
})}
/> />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem filename="{person.name || 'immich'}.zip" /> <DownloadAction menuItem filename="{person.name || 'immich'}.zip" />
@ -511,11 +507,7 @@
<ArchiveAction <ArchiveAction
menuItem menuItem
unarchive={assetInteraction.isAllArchived} unarchive={assetInteraction.isAllArchived}
onArchive={(ids, visibility) => onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.visibility = visibility;
return { remove: false };
})}
/> />
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
<TagAction menuItem /> <TagAction menuItem />

View File

@ -120,11 +120,7 @@
</ButtonContextMenu> </ButtonContextMenu>
<FavoriteAction <FavoriteAction
removeFavorite={assetInteraction.isAllFavorite} removeFavorite={assetInteraction.isAllFavorite}
onFavorite={(ids, isFavorite) => onFavorite={(ids, isFavorite) => timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.isFavorite = isFavorite;
return { remove: false };
})}
></FavoriteAction> ></FavoriteAction>
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
<DownloadAction menuItem /> <DownloadAction menuItem />
@ -148,11 +144,7 @@
<ChangeLocation menuItem /> <ChangeLocation menuItem />
<ArchiveAction <ArchiveAction
menuItem menuItem
onArchive={(ids, visibility) => onArchive={(ids, visibility) => timelineManager.update(ids, (asset) => (asset.visibility = visibility))}
timelineManager.updateAssetOperation(ids, (asset) => {
asset.visibility = visibility;
return { remove: false };
})}
/> />
{#if $preferences.tags.enabled} {#if $preferences.tags.enabled}
<TagAction menuItem /> <TagAction menuItem />