Compare commits

...

4 Commits

Author SHA1 Message Date
Michel Heusschen
ff7dca35f5 perf(web): speed up asset selection (#26216) 2026-02-14 15:31:04 -05:00
Alex
49ba833e4c fix(web): Revert "add checkerboard background for transparent images (#26091)" (#26220)
Revert "fix(web): add checkerboard background for transparent images (#26091)"

This reverts commit bc7a1c838c.
2026-02-14 20:25:14 +00:00
Michel Heusschen
9ab887d5d2 perf(web): speed up multi asset operations (#26217) 2026-02-14 15:24:47 -05:00
Min Idzelis
d264e78d3f chore: pnpm workspace protocol for sibling packagages (#26218) 2026-02-14 15:03:08 -05:00
8 changed files with 31 additions and 42 deletions

View File

@@ -14,7 +14,7 @@
],
"devDependencies": {
"@eslint/js": "^9.8.0",
"@immich/sdk": "file:../open-api/typescript-sdk",
"@immich/sdk": "workspace:*",
"@types/byte-size": "^8.1.0",
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",

View File

@@ -21,9 +21,9 @@
"devDependencies": {
"@eslint/js": "^9.8.0",
"@faker-js/faker": "^10.1.0",
"@immich/cli": "file:../cli",
"@immich/e2e-auth-server": "file:../e2e-auth-server",
"@immich/sdk": "file:../open-api/typescript-sdk",
"@immich/cli": "workspace:*",
"@immich/e2e-auth-server": "workspace:*",
"@immich/sdk": "workspace:*",
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",

10
pnpm-lock.yaml generated
View File

@@ -45,7 +45,7 @@ importers:
specifier: ^9.8.0
version: 9.39.2
'@immich/sdk':
specifier: file:../open-api/typescript-sdk
specifier: workspace:*
version: link:../open-api/typescript-sdk
'@types/byte-size':
specifier: ^8.1.0
@@ -202,13 +202,13 @@ importers:
specifier: ^10.1.0
version: 10.3.0
'@immich/cli':
specifier: file:../cli
specifier: workspace:*
version: link:../cli
'@immich/e2e-auth-server':
specifier: file:../e2e-auth-server
specifier: workspace:*
version: link:../e2e-auth-server
'@immich/sdk':
specifier: file:../open-api/typescript-sdk
specifier: workspace:*
version: link:../open-api/typescript-sdk
'@playwright/test':
specifier: ^1.44.1
@@ -738,7 +738,7 @@ importers:
specifier: ^0.4.3
version: 0.4.3
'@immich/sdk':
specifier: file:../open-api/typescript-sdk
specifier: workspace:*
version: link:../open-api/typescript-sdk
'@immich/ui':
specifier: ^0.62.1

View File

@@ -26,7 +26,7 @@
"dependencies": {
"@formatjs/icu-messageformat-parser": "^3.0.0",
"@immich/justified-layout-wasm": "^0.4.3",
"@immich/sdk": "file:../open-api/typescript-sdk",
"@immich/sdk": "workspace:*",
"@immich/ui": "^0.62.1",
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
"@mdi/js": "^7.4.47",

View File

@@ -226,7 +226,7 @@
alt={$getAltText(toTimelineAsset(asset))}
class="h-full w-full {$slideshowState === SlideshowState.None
? 'object-contain'
: slideshowLookCssMapping[$slideshowLook]} checkerboard"
: slideshowLookCssMapping[$slideshowLook]}"
draggable="false"
/>
<!-- eslint-disable-next-line svelte/require-each-key -->
@@ -259,8 +259,4 @@
visibility: hidden;
animation: 0s linear 0.4s forwards delayedVisibility;
}
.checkerboard {
background-image: conic-gradient(#808080 25%, #b0b0b0 25% 50%, #808080 50% 75%, #b0b0b0 75%);
background-size: 20px 20px;
}
</style>

View File

@@ -102,25 +102,21 @@ export class DayGroup {
}
runAssetCallback(ids: Set<string>, callback: (asset: TimelineAsset) => void | { remove?: boolean }) {
if (ids.size === 0) {
return {
moveAssets: [] as MoveAsset[],
processedIds: new SvelteSet<string>(),
unprocessedIds: ids,
changedGeometry: false,
};
}
const unprocessedIds = new SvelteSet<string>(ids);
const processedIds = new SvelteSet<string>();
const moveAssets: MoveAsset[] = [];
let changedGeometry = false;
for (const assetId of unprocessedIds) {
const index = this.viewerAssets.findIndex((viewAsset) => viewAsset.id == assetId);
if (index === -1) {
if (ids.size === 0) {
return { moveAssets, processedIds, unprocessedIds, changedGeometry };
}
for (let index = this.viewerAssets.length - 1; index >= 0; index--) {
const { id: assetId, asset } = this.viewerAssets[index];
if (!ids.has(assetId)) {
continue;
}
const asset = this.viewerAssets[index].asset!;
const oldTime = { ...asset.localDateTime };
const callbackResult = callback(asset);
let remove = (callbackResult as { remove?: boolean } | undefined)?.remove ?? false;

View File

@@ -1,14 +1,15 @@
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { user } from '$lib/stores/user.store';
import { AssetVisibility, type UserAdminResponseDto } from '@immich/sdk';
import { SvelteSet } from 'svelte/reactivity';
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
import { fromStore } from 'svelte/store';
export class AssetInteraction {
selectedAssets = $state<TimelineAsset[]>([]);
private selectedAssetsMap = new SvelteMap<string, TimelineAsset>();
selectedAssets = $derived(Array.from(this.selectedAssetsMap.values()));
selectAll = $state(false);
hasSelectedAsset(assetId: string) {
return this.selectedAssets.some((asset) => asset.id === assetId);
return this.selectedAssetsMap.has(assetId);
}
selectedGroup = new SvelteSet<string>();
assetSelectionCandidates = $state<TimelineAsset[]>([]);
@@ -16,7 +17,7 @@ export class AssetInteraction {
return this.assetSelectionCandidates.some((asset) => asset.id === assetId);
}
assetSelectionStart = $state<TimelineAsset | null>(null);
selectionActive = $derived(this.selectedAssets.length > 0);
selectionActive = $derived(this.selectedAssetsMap.size > 0);
private user = fromStore<UserAdminResponseDto | undefined>(user);
private userId = $derived(this.user.current?.id);
@@ -27,9 +28,7 @@ export class AssetInteraction {
isAllUserOwned = $derived(this.selectedAssets.every((asset) => asset.ownerId === this.userId));
selectAsset(asset: TimelineAsset) {
if (!this.hasSelectedAsset(asset.id)) {
this.selectedAssets.push(asset);
}
this.selectedAssetsMap.set(asset.id, asset);
}
selectAssets(assets: TimelineAsset[]) {
@@ -39,10 +38,7 @@ export class AssetInteraction {
}
removeAssetFromMultiselectGroup(assetId: string) {
const index = this.selectedAssets.findIndex((a) => a.id == assetId);
if (index !== -1) {
this.selectedAssets.splice(index, 1);
}
this.selectedAssetsMap.delete(assetId);
}
addGroupToMultiselectGroup(group: string) {
@@ -69,7 +65,7 @@ export class AssetInteraction {
this.selectAll = false;
// Multi-selection
this.selectedAssets = [];
this.selectedAssetsMap.clear();
this.selectedGroup.clear();
// Range selection

View File

@@ -4,7 +4,6 @@ import { authManager } from '$lib/managers/auth-manager.svelte';
import { downloadManager } from '$lib/managers/download-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { assetsSnapshot } from '$lib/managers/timeline-manager/utils.svelte';
import { Route } from '$lib/route';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { preferences } from '$lib/stores/user.store';
@@ -443,13 +442,15 @@ export const selectAllAssets = async (timelineManager: TimelineManager, assetInt
try {
for (const monthGroup of timelineManager.months) {
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
if (!monthGroup.isLoaded) {
await timelineManager.loadMonthGroup(monthGroup.yearMonth);
}
if (!assetInteraction.selectAll) {
assetInteraction.clearMultiselect();
break; // Cancelled
}
assetInteraction.selectAssets(assetsSnapshot([...monthGroup.assetsIterator()]));
assetInteraction.selectAssets([...monthGroup.assetsIterator()]);
for (const dateGroup of monthGroup.dayGroups) {
assetInteraction.addGroupToMultiselectGroup(dateGroup.groupTitle);