mirror of
https://github.com/immich-app/immich.git
synced 2026-02-14 12:58:17 +03:00
feat: improve asset-viewer next/prev perf and standardize preloading behavior (#24422)
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
@@ -1,15 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import type { AssetCursor } from '$lib/components/asset-viewer/asset-viewer.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import { AppRoute, timeToLoadTheMap } from '$lib/constants';
|
||||
import Portal from '$lib/elements/Portal.svelte';
|
||||
import { authManager } from '$lib/managers/auth-manager.svelte';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { delay } from '$lib/utils/asset-utils';
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
import { getAssetInfo, type AssetResponseDto } from '@immich/sdk';
|
||||
import { LoadingSpinner } from '@immich/ui';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { onDestroy, untrack } from 'svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
interface Props {
|
||||
@@ -64,6 +67,59 @@
|
||||
await navigate({ targetRoute: 'current', assetId: $viewingAsset.id });
|
||||
return asset;
|
||||
}
|
||||
|
||||
const getNextAsset = async (currentAsset: AssetResponseDto | undefined, preload: boolean = true) => {
|
||||
if (!currentAsset) {
|
||||
return;
|
||||
}
|
||||
const cursor = viewingAssets.indexOf(currentAsset.id);
|
||||
if (cursor < viewingAssets.length - 1) {
|
||||
const id = viewingAssets[cursor + 1];
|
||||
const asset = await getAssetInfo({ ...authManager.params, id });
|
||||
if (preload) {
|
||||
void getNextAsset(asset, false);
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
};
|
||||
|
||||
const getPreviousAsset = async (currentAsset: AssetResponseDto | undefined, preload: boolean = true) => {
|
||||
if (!currentAsset) {
|
||||
return;
|
||||
}
|
||||
const cursor = viewingAssets.indexOf(currentAsset.id);
|
||||
if (cursor <= 0) {
|
||||
return;
|
||||
}
|
||||
const id = viewingAssets[cursor - 1];
|
||||
const asset = await getAssetInfo({ ...authManager.params, id });
|
||||
if (preload) {
|
||||
void getPreviousAsset(asset, false);
|
||||
}
|
||||
return asset;
|
||||
};
|
||||
|
||||
let assetCursor = $state<AssetCursor>({
|
||||
current: $viewingAsset,
|
||||
previousAsset: undefined,
|
||||
nextAsset: undefined,
|
||||
});
|
||||
|
||||
const loadCloseAssets = async (currentAsset: AssetResponseDto) => {
|
||||
const [nextAsset, previousAsset] = await Promise.all([getNextAsset(currentAsset), getPreviousAsset(currentAsset)]);
|
||||
assetCursor = {
|
||||
current: currentAsset,
|
||||
nextAsset,
|
||||
previousAsset,
|
||||
};
|
||||
};
|
||||
|
||||
//TODO: replace this with async derived in svelte 6
|
||||
$effect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
$viewingAsset;
|
||||
untrack(() => void loadCloseAssets($viewingAsset));
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if featureFlagsManager.value.map}
|
||||
@@ -85,7 +141,7 @@
|
||||
{#if $showAssetViewer}
|
||||
{#await import('$lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
||||
<AssetViewer
|
||||
asset={$viewingAsset}
|
||||
cursor={assetCursor}
|
||||
showNavigation={viewingAssets.length > 1}
|
||||
onNext={navigateNext}
|
||||
onPrevious={navigatePrevious}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
|
||||
import type { Viewport } from '$lib/managers/timeline-manager/types';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { lang, locale } from '$lib/stores/preferences.store';
|
||||
@@ -35,6 +35,7 @@
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import {
|
||||
type AlbumResponseDto,
|
||||
type AssetResponseDto,
|
||||
getPerson,
|
||||
getTagById,
|
||||
type MetadataSearchDto,
|
||||
@@ -58,7 +59,7 @@
|
||||
|
||||
let nextPage = $state(1);
|
||||
let searchResultAlbums: AlbumResponseDto[] = $state([]);
|
||||
let searchResultAssets: TimelineAsset[] = $state([]);
|
||||
let searchResultAssets: AssetResponseDto[] = $state([]);
|
||||
let isLoading = $state(true);
|
||||
let scrollY = $state(0);
|
||||
let scrollYHistory = 0;
|
||||
@@ -121,7 +122,7 @@
|
||||
|
||||
const onAssetDelete = (assetIds: string[]) => {
|
||||
const assetIdSet = new Set(assetIds);
|
||||
searchResultAssets = searchResultAssets.filter((asset: TimelineAsset) => !assetIdSet.has(asset.id));
|
||||
searchResultAssets = searchResultAssets.filter((asset: AssetResponseDto) => !assetIdSet.has(asset.id));
|
||||
};
|
||||
|
||||
const handleSetVisibility = (assetIds: string[]) => {
|
||||
@@ -130,7 +131,7 @@
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
assetInteraction.selectAssets(searchResultAssets);
|
||||
assetInteraction.selectAssets(searchResultAssets.map((asset) => toTimelineAsset(asset)));
|
||||
};
|
||||
|
||||
async function onSearchQueryUpdate() {
|
||||
@@ -162,7 +163,7 @@
|
||||
: await searchAssets({ metadataSearchDto: searchDto });
|
||||
|
||||
searchResultAlbums.push(...albums.items);
|
||||
searchResultAssets.push(...assets.items.map((asset) => toTimelineAsset(asset)));
|
||||
searchResultAssets.push(...assets.items);
|
||||
|
||||
nextPage = Number(assets.nextPage) || 0;
|
||||
} catch (error) {
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
import Portal from '$lib/elements/Portal.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import { getNextAsset, getPreviousAsset } from '$lib/utils/asset-utils';
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
@@ -65,6 +66,12 @@
|
||||
const onViewAsset = async (asset: AssetResponseDto) => {
|
||||
await navigate({ targetRoute: 'current', assetId: asset.id });
|
||||
};
|
||||
|
||||
const assetCursor = $derived({
|
||||
current: $viewingAsset,
|
||||
nextAsset: getNextAsset(assets, $viewingAsset),
|
||||
previousAsset: getPreviousAsset(assets, $viewingAsset),
|
||||
});
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title} scrollbar={true}>
|
||||
@@ -85,7 +92,7 @@
|
||||
{#await import('$lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
|
||||
<Portal target="body">
|
||||
<AssetViewer
|
||||
asset={$viewingAsset}
|
||||
cursor={assetCursor}
|
||||
showNavigation={assets.length > 1}
|
||||
{onNext}
|
||||
{onPrevious}
|
||||
|
||||
Reference in New Issue
Block a user