ocrManager.toggleOcrBoundingBox()}
+/>
diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte
index e37773fca5..261f194d34 100644
--- a/web/src/lib/components/asset-viewer/photo-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte
@@ -2,12 +2,14 @@
import { shortcuts } from '$lib/actions/shortcut';
import { zoomImageAction } from '$lib/actions/zoom-image';
import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte';
+ import OcrBoundingBox from '$lib/components/asset-viewer/ocr-bounding-box.svelte';
import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
import { assetViewerFadeDuration } from '$lib/constants';
import { castManager } from '$lib/managers/cast-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
+ import { ocrManager } from '$lib/stores/ocr.svelte';
import { boundingBoxesArray } from '$lib/stores/people.store';
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
import { SlideshowLook, SlideshowState, slideshowLookCssMapping, slideshowStore } from '$lib/stores/slideshow.store';
@@ -15,6 +17,7 @@
import { getAssetOriginalUrl, getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
import { canCopyImageToClipboard, copyImageToClipboard, isWebCompatibleImage } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
+ import { getOcrBoundingBoxes } from '$lib/utils/ocr-utils';
import { getBoundingBox } from '$lib/utils/people-utils';
import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { getAltText } from '$lib/utils/thumbnail-util';
@@ -71,6 +74,14 @@
$boundingBoxesArray = [];
});
+ let ocrBoxes = $derived(
+ ocrManager.showOverlay && $photoViewerImgElement
+ ? getOcrBoundingBoxes(ocrManager.data, $photoZoomState, $photoViewerImgElement)
+ : [],
+ );
+
+ let isOcrActive = $derived(ocrManager.showOverlay);
+
const preload = (targetSize: AssetMediaSize | 'original', preloadAssets?: TimelineAsset[]) => {
for (const preloadAsset of preloadAssets || []) {
if (preloadAsset.isImage) {
@@ -130,9 +141,15 @@
if ($photoZoomState.currentZoom > 1) {
return;
}
+
+ if (ocrManager.showOverlay) {
+ return;
+ }
+
if (onNextAsset && event.detail.direction === 'left') {
onNextAsset();
}
+
if (onPreviousAsset && event.detail.direction === 'right') {
onPreviousAsset();
}
@@ -235,7 +252,7 @@
{:else if !imageError}
{/each}
+
+ {#each ocrBoxes as ocrBox (ocrBox.id)}
+
+ {/each}
{#if isFaceEditMode.value}
diff --git a/web/src/lib/components/timeline/TimelineAssetViewer.svelte b/web/src/lib/components/timeline/TimelineAssetViewer.svelte
index ccdd8bd5b4..a121bd1938 100644
--- a/web/src/lib/components/timeline/TimelineAssetViewer.svelte
+++ b/web/src/lib/components/timeline/TimelineAssetViewer.svelte
@@ -110,13 +110,9 @@
case AssetAction.ARCHIVE:
case AssetAction.UNARCHIVE:
case AssetAction.FAVORITE:
- case AssetAction.UNFAVORITE: {
- timelineManager.updateAssets([action.asset]);
- break;
- }
-
+ case AssetAction.UNFAVORITE:
case AssetAction.ADD: {
- timelineManager.addAssets([action.asset]);
+ timelineManager.upsertAssets([action.asset]);
break;
}
@@ -135,7 +131,7 @@
break;
}
case AssetAction.REMOVE_ASSET_FROM_STACK: {
- timelineManager.addAssets([toTimelineAsset(action.asset)]);
+ timelineManager.upsertAssets([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(
diff --git a/web/src/lib/components/timeline/TimelineDateGroup.svelte b/web/src/lib/components/timeline/TimelineDateGroup.svelte
index cd0dc9a212..c662c16e72 100644
--- a/web/src/lib/components/timeline/TimelineDateGroup.svelte
+++ b/web/src/lib/components/timeline/TimelineDateGroup.svelte
@@ -2,7 +2,7 @@
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
import type { MonthGroup } from '$lib/managers/timeline-manager/month-group.svelte';
- import type { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
+ import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { assetSnapshot, assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
diff --git a/web/src/lib/components/timeline/actions/ArchiveAction.svelte b/web/src/lib/components/timeline/actions/ArchiveAction.svelte
index 05ef9c99ff..e11da0b2f0 100644
--- a/web/src/lib/components/timeline/actions/ArchiveAction.svelte
+++ b/web/src/lib/components/timeline/actions/ArchiveAction.svelte
@@ -24,12 +24,12 @@
const { clearSelect, getOwnedAssets } = getAssetControlContext();
const handleArchive = async () => {
- const isArchived = unarchive ? AssetVisibility.Timeline : AssetVisibility.Archive;
- const assets = [...getOwnedAssets()].filter((asset) => asset.visibility !== isArchived);
+ const visibility = unarchive ? AssetVisibility.Timeline : AssetVisibility.Archive;
+ const assets = [...getOwnedAssets()].filter((asset) => asset.visibility !== visibility);
loading = true;
- const ids = await archiveAssets(assets, isArchived as AssetVisibility);
+ const ids = await archiveAssets(assets, visibility as AssetVisibility);
if (ids) {
- onArchive?.(ids, isArchived ? AssetVisibility.Archive : AssetVisibility.Timeline);
+ onArchive?.(ids, visibility);
clearSelect();
}
loading = false;
diff --git a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte
index 74b058a74f..d5b1d2ecf6 100644
--- a/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte
+++ b/web/src/lib/components/timeline/actions/TimelineKeyboardActions.svelte
@@ -46,7 +46,7 @@
!(isTrashEnabled && !force),
(assetIds) => timelineManager.removeAssets(assetIds),
assetInteraction.selectedAssets,
- !isTrashEnabled || force ? undefined : (assets) => timelineManager.addAssets(assets),
+ !isTrashEnabled || force ? undefined : (assets) => timelineManager.upsertAssets(assets),
);
assetInteraction.clearMultiselect();
};
diff --git a/web/src/lib/components/utilities-page/duplicates/duplicate-asset.svelte b/web/src/lib/components/utilities-page/duplicates/duplicate-asset.svelte
index 8a8395d792..c0f5428c81 100644
--- a/web/src/lib/components/utilities-page/duplicates/duplicate-asset.svelte
+++ b/web/src/lib/components/utilities-page/duplicates/duplicate-asset.svelte
@@ -12,6 +12,7 @@
mdiClock,
mdiFile,
mdiFitToScreen,
+ mdiFolderOutline,
mdiHeart,
mdiImageMultipleOutline,
mdiImageOutline,
@@ -51,6 +52,7 @@
fileName: isDifferent((a) => a.originalFileName),
fileSize: isDifferent((a) => getFileSize(a)),
resolution: isDifferent((a) => getAssetResolution(a)),
+ originalPath: isDifferent((a) => a.originalPath ?? $t('unknown')),
date: isDifferent((a) => {
const tz = a.exifInfo?.timeZone;
const dt =
@@ -79,6 +81,24 @@
(a) => [a.exifInfo?.city, a.exifInfo?.state, a.exifInfo?.country].filter(Boolean).join(', ') || 'unknown',
),
});
+
+ const getBasePath = (fullpath: string, fileName: string): string => {
+ if (fileName && fullpath.endsWith(fileName)) {
+ return fullpath.slice(0, -(fileName.length + 1));
+ }
+ return fullpath;
+ };
+
+ function truncateMiddle(path: string, maxLength: number = 50): string {
+ if (path.length <= maxLength) {
+ return path;
+ }
+
+ const start = Math.floor(maxLength / 2) - 2;
+ const end = Math.floor(maxLength / 2) - 2;
+
+ return path.slice(0, Math.max(0, start)) + '...' + path.slice(Math.max(0, path.length - end));
+ }
@@ -152,6 +172,14 @@
{asset.originalFileName}
+
+ {truncateMiddle(getBasePath(asset.originalPath, asset.originalFileName)) || $t('unknown')}
+
+
{getFileSize(asset)}
diff --git a/web/src/lib/managers/event-manager.svelte.ts b/web/src/lib/managers/event-manager.svelte.ts
index 62fc9df8da..a2a4c27850 100644
--- a/web/src/lib/managers/event-manager.svelte.ts
+++ b/web/src/lib/managers/event-manager.svelte.ts
@@ -16,6 +16,8 @@ export type Events = {
LanguageChange: [{ name: string; code: string; rtl?: boolean }];
ThemeChange: [ThemeSetting];
+ AssetReplace: [{ oldAssetId: string; newAssetId: string }];
+
AlbumDelete: [AlbumResponseDto];
SharedLinkCreate: [SharedLinkResponseDto];
diff --git a/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts
index bdf2b17cbe..3c6f2d8256 100644
--- a/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts
+++ b/web/src/lib/managers/timeline-manager/internal/intersection-support.svelte.ts
@@ -1,6 +1,6 @@
import { TUNABLES } from '$lib/utils/tunables';
import type { MonthGroup } from '../month-group.svelte';
-import type { TimelineManager } from '../timeline-manager.svelte';
+import { TimelineManager } from '../timeline-manager.svelte';
const {
TIMELINE: { INTERSECTION_EXPAND_TOP, INTERSECTION_EXPAND_BOTTOM },
diff --git a/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts
index 0f6ca112d1..71dc168971 100644
--- a/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts
+++ b/web/src/lib/managers/timeline-manager/internal/layout-support.svelte.ts
@@ -1,5 +1,5 @@
import type { MonthGroup } from '../month-group.svelte';
-import type { TimelineManager } from '../timeline-manager.svelte';
+import { TimelineManager } from '../timeline-manager.svelte';
import type { UpdateGeometryOptions } from '../types';
export function updateGeometry(timelineManager: TimelineManager, month: MonthGroup, options: UpdateGeometryOptions) {
diff --git a/web/src/lib/managers/timeline-manager/internal/load-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/load-support.svelte.ts
index 0d966c9cee..ec50e3d75e 100644
--- a/web/src/lib/managers/timeline-manager/internal/load-support.svelte.ts
+++ b/web/src/lib/managers/timeline-manager/internal/load-support.svelte.ts
@@ -2,7 +2,7 @@ import { authManager } from '$lib/managers/auth-manager.svelte';
import { toISOYearMonthUTC } from '$lib/utils/timeline-util';
import { getTimeBucket } from '@immich/sdk';
import type { MonthGroup } from '../month-group.svelte';
-import type { TimelineManager } from '../timeline-manager.svelte';
+import { TimelineManager } from '../timeline-manager.svelte';
import type { TimelineManagerOptions } from '../types';
export async function loadFromTimeBuckets(
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 52a37b52d0..f889456c20 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
@@ -2,7 +2,7 @@ import { plainDateTimeCompare, type TimelineYearMonth } from '$lib/utils/timelin
import { AssetOrder } from '@immich/sdk';
import { DateTime } from 'luxon';
import type { MonthGroup } from '../month-group.svelte';
-import type { TimelineManager } from '../timeline-manager.svelte';
+import { TimelineManager } from '../timeline-manager.svelte';
import type { AssetDescriptor, Direction, TimelineAsset } from '../types';
export async function getAssetWithOffset(
diff --git a/web/src/lib/managers/timeline-manager/internal/websocket-support.svelte.ts b/web/src/lib/managers/timeline-manager/internal/websocket-support.svelte.ts
index 4ba237c50c..bff2f15cb9 100644
--- a/web/src/lib/managers/timeline-manager/internal/websocket-support.svelte.ts
+++ b/web/src/lib/managers/timeline-manager/internal/websocket-support.svelte.ts
@@ -13,10 +13,10 @@ export class WebsocketSupport {
#processPendingChanges = throttle(() => {
const { add, update, remove } = this.#getPendingChangeBatches();
if (add.length > 0) {
- this.#timelineManager.addAssets(add);
+ this.#timelineManager.upsertAssets(add);
}
if (update.length > 0) {
- this.#timelineManager.updateAssets(update);
+ this.#timelineManager.upsertAssets(update);
}
if (remove.length > 0) {
this.#timelineManager.removeAssets(remove);
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 e6eddef9b6..62053f7a0d 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
@@ -2,7 +2,7 @@ import { sdkMock } from '$lib/__mocks__/sdk.mock';
import { getMonthGroupByDate } from '$lib/managers/timeline-manager/internal/search-support.svelte';
import { AbortError } from '$lib/utils';
import { fromISODateTimeUTCToObject } from '$lib/utils/timeline-util';
-import { type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
+import { AssetVisibility, type AssetResponseDto, type TimeBucketAssetResponseDto } from '@immich/sdk';
import { timelineAssetFactory, toResponseDto } from '@test-data/factories/asset-factory';
import { tick } from 'svelte';
import { TimelineManager } from './timeline-manager.svelte';
@@ -175,7 +175,7 @@ describe('TimelineManager', () => {
});
});
- describe('addAssets', () => {
+ describe('upsertAssets', () => {
let timelineManager: TimelineManager;
beforeEach(async () => {
@@ -196,7 +196,7 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-01-20T12:00:00.000Z'),
}),
);
- timelineManager.addAssets([asset]);
+ timelineManager.upsertAssets([asset]);
expect(timelineManager.months.length).toEqual(1);
expect(timelineManager.assetCount).toEqual(1);
@@ -212,8 +212,8 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-01-20T12:00:00.000Z'),
})
.map((asset) => deriveLocalDateTimeFromFileCreatedAt(asset));
- timelineManager.addAssets([assetOne]);
- timelineManager.addAssets([assetTwo]);
+ timelineManager.upsertAssets([assetOne]);
+ timelineManager.upsertAssets([assetTwo]);
expect(timelineManager.months.length).toEqual(1);
expect(timelineManager.assetCount).toEqual(2);
@@ -238,7 +238,7 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-01-16T12:00:00.000Z'),
}),
);
- timelineManager.addAssets([assetOne, assetTwo, assetThree]);
+ timelineManager.upsertAssets([assetOne, assetTwo, assetThree]);
const month = getMonthGroupByDate(timelineManager, { year: 2024, month: 1 });
expect(month).not.toBeNull();
@@ -264,7 +264,7 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2023-01-20T12:00:00.000Z'),
}),
);
- timelineManager.addAssets([assetOne, assetTwo, assetThree]);
+ timelineManager.upsertAssets([assetOne, assetTwo, assetThree]);
expect(timelineManager.months.length).toEqual(3);
expect(timelineManager.months[0].yearMonth.year).toEqual(2024);
@@ -278,12 +278,10 @@ describe('TimelineManager', () => {
});
it('updates existing asset', () => {
- const updateAssetsSpy = vi.spyOn(timelineManager, 'updateAssets');
const asset = deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build());
- timelineManager.addAssets([asset]);
+ timelineManager.upsertAssets([asset]);
- timelineManager.addAssets([asset]);
- expect(updateAssetsSpy).toBeCalledWith([asset]);
+ timelineManager.upsertAssets([asset]);
expect(timelineManager.assetCount).toEqual(1);
});
@@ -294,12 +292,12 @@ describe('TimelineManager', () => {
const timelineManager = new TimelineManager();
await timelineManager.updateOptions({ isTrashed: true });
- timelineManager.addAssets([asset, trashedAsset]);
+ timelineManager.upsertAssets([asset, trashedAsset]);
expect(await getAssets(timelineManager)).toEqual([trashedAsset]);
});
});
- describe('updateAssets', () => {
+ describe('upsertAssets - updating existing', () => {
let timelineManager: TimelineManager;
beforeEach(async () => {
@@ -309,22 +307,15 @@ describe('TimelineManager', () => {
await timelineManager.updateViewport({ width: 1588, height: 1000 });
});
- it('ignores non-existing assets', () => {
- timelineManager.updateAssets([deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build())]);
-
- expect(timelineManager.months.length).toEqual(0);
- expect(timelineManager.assetCount).toEqual(0);
- });
-
it('updates an asset', () => {
const asset = deriveLocalDateTimeFromFileCreatedAt(timelineAssetFactory.build({ isFavorite: false }));
const updatedAsset = { ...asset, isFavorite: true };
- timelineManager.addAssets([asset]);
+ timelineManager.upsertAssets([asset]);
expect(timelineManager.assetCount).toEqual(1);
expect(timelineManager.months[0].getFirstAsset().isFavorite).toEqual(false);
- timelineManager.updateAssets([updatedAsset]);
+ timelineManager.upsertAssets([updatedAsset]);
expect(timelineManager.assetCount).toEqual(1);
expect(timelineManager.months[0].getFirstAsset().isFavorite).toEqual(true);
});
@@ -340,18 +331,80 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-03-20T12:00:00.000Z'),
});
- timelineManager.addAssets([asset]);
+ timelineManager.upsertAssets([asset]);
expect(timelineManager.months.length).toEqual(1);
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })).not.toBeUndefined();
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(1);
- timelineManager.updateAssets([updatedAsset]);
+ timelineManager.upsertAssets([updatedAsset]);
expect(timelineManager.months.length).toEqual(2);
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })).not.toBeUndefined();
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 1 })?.getAssets().length).toEqual(0);
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 3 })).not.toBeUndefined();
expect(getMonthGroupByDate(timelineManager, { year: 2024, month: 3 })?.getAssets().length).toEqual(1);
});
+ it('asset is removed during upsert when TimelineManager if visibility changes', async () => {
+ await timelineManager.updateOptions({
+ visibility: AssetVisibility.Archive,
+ });
+ const fixture = deriveLocalDateTimeFromFileCreatedAt(
+ timelineAssetFactory.build({
+ visibility: AssetVisibility.Archive,
+ }),
+ );
+
+ timelineManager.upsertAssets([fixture]);
+ expect(timelineManager.assetCount).toEqual(1);
+
+ const updated = Object.freeze({ ...fixture, visibility: AssetVisibility.Timeline });
+ timelineManager.upsertAssets([updated]);
+ expect(timelineManager.assetCount).toEqual(0);
+
+ timelineManager.upsertAssets([{ ...fixture, visibility: AssetVisibility.Archive }]);
+ expect(timelineManager.assetCount).toEqual(1);
+ });
+
+ it('asset is removed during upsert when TimelineManager if isFavorite changes', async () => {
+ await timelineManager.updateOptions({
+ isFavorite: true,
+ });
+ const fixture = deriveLocalDateTimeFromFileCreatedAt(
+ timelineAssetFactory.build({
+ isFavorite: true,
+ }),
+ );
+
+ timelineManager.upsertAssets([fixture]);
+ expect(timelineManager.assetCount).toEqual(1);
+
+ const updated = Object.freeze({ ...fixture, isFavorite: false });
+ timelineManager.upsertAssets([updated]);
+ expect(timelineManager.assetCount).toEqual(0);
+
+ timelineManager.upsertAssets([{ ...fixture, isFavorite: true }]);
+ expect(timelineManager.assetCount).toEqual(1);
+ });
+
+ it('asset is removed during upsert when TimelineManager if isTrashed changes', async () => {
+ await timelineManager.updateOptions({
+ isTrashed: true,
+ });
+ const fixture = deriveLocalDateTimeFromFileCreatedAt(
+ timelineAssetFactory.build({
+ isTrashed: true,
+ }),
+ );
+
+ timelineManager.upsertAssets([fixture]);
+ expect(timelineManager.assetCount).toEqual(1);
+
+ const updated = Object.freeze({ ...fixture, isTrashed: false });
+ timelineManager.upsertAssets([updated]);
+ expect(timelineManager.assetCount).toEqual(0);
+
+ timelineManager.upsertAssets([{ ...fixture, isTrashed: true }]);
+ expect(timelineManager.assetCount).toEqual(1);
+ });
});
describe('removeAssets', () => {
@@ -365,7 +418,7 @@ describe('TimelineManager', () => {
});
it('ignores invalid IDs', () => {
- timelineManager.addAssets(
+ timelineManager.upsertAssets(
timelineAssetFactory
.buildList(2, {
fileCreatedAt: fromISODateTimeUTCToObject('2024-01-20T12:00:00.000Z'),
@@ -385,7 +438,7 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-01-20T12:00:00.000Z'),
})
.map((asset) => deriveLocalDateTimeFromFileCreatedAt(asset));
- timelineManager.addAssets([assetOne, assetTwo]);
+ timelineManager.upsertAssets([assetOne, assetTwo]);
timelineManager.removeAssets([assetOne.id]);
expect(timelineManager.assetCount).toEqual(1);
@@ -399,7 +452,7 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-01-20T12:00:00.000Z'),
})
.map((asset) => deriveLocalDateTimeFromFileCreatedAt(asset));
- timelineManager.addAssets(assets);
+ timelineManager.upsertAssets(assets);
timelineManager.removeAssets(assets.map((asset) => asset.id));
expect(timelineManager.assetCount).toEqual(0);
@@ -431,7 +484,7 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-01-15T12:00:00.000Z'),
}),
);
- timelineManager.addAssets([assetOne, assetTwo]);
+ timelineManager.upsertAssets([assetOne, assetTwo]);
expect(timelineManager.getFirstAsset()).toEqual(assetOne);
});
});
@@ -556,7 +609,7 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-02-15T12:00:00.000Z'),
}),
);
- timelineManager.addAssets([assetOne, assetTwo]);
+ timelineManager.upsertAssets([assetOne, assetTwo]);
expect(timelineManager.getMonthGroupByAssetId(assetTwo.id)?.yearMonth.year).toEqual(2024);
expect(timelineManager.getMonthGroupByAssetId(assetTwo.id)?.yearMonth.month).toEqual(2);
@@ -575,7 +628,7 @@ describe('TimelineManager', () => {
fileCreatedAt: fromISODateTimeUTCToObject('2024-02-15T12:00:00.000Z'),
}),
);
- timelineManager.addAssets([assetOne, assetTwo]);
+ timelineManager.upsertAssets([assetOne, assetTwo]);
timelineManager.removeAssets([assetTwo.id]);
expect(timelineManager.getMonthGroupByAssetId(assetOne.id)?.yearMonth.year).toEqual(2024);
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 24523ce9e7..e3327663b4 100644
--- a/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts
+++ b/web/src/lib/managers/timeline-manager/timeline-manager.svelte.ts
@@ -320,10 +320,10 @@ export class TimelineManager extends VirtualScrollManager {
}
}
- addAssets(assets: TimelineAsset[]) {
- const assetsToUpdate = assets.filter((asset) => !this.isExcluded(asset));
- const notUpdated = this.updateAssets(assetsToUpdate);
- addAssetsToMonthGroups(this, [...notUpdated], { order: this.#options.order ?? AssetOrder.Desc });
+ 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 });
}
async findMonthGroupForAsset(id: string) {
@@ -404,7 +404,7 @@ export class TimelineManager extends VirtualScrollManager {
runAssetOperation(this, new SvelteSet(ids), operation, { order: this.#options.order ?? AssetOrder.Desc });
}
- updateAssets(assets: TimelineAsset[]) {
+ #updateAssets(assets: TimelineAsset[]) {
const lookup = new SvelteMap(assets.map((asset) => [asset.id, asset]));
const { unprocessedIds } = runAssetOperation(
this,
diff --git a/web/src/lib/modals/NavigateToDateModal.svelte b/web/src/lib/modals/NavigateToDateModal.svelte
index 4b83c66bc6..365cbdb21c 100644
--- a/web/src/lib/modals/NavigateToDateModal.svelte
+++ b/web/src/lib/modals/NavigateToDateModal.svelte
@@ -1,6 +1,6 @@