refactor: load support

This commit is contained in:
wuzihao051119
2025-06-30 06:35:03 +08:00
parent b8dc1a4b1f
commit ab988f3be6
7 changed files with 250 additions and 127 deletions
@@ -1,20 +1,10 @@
import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import type { AssetPackage } from '$lib/managers/asset-manager/asset-package.svelte';
import { loadFromAssetPackage } from '$lib/managers/asset-manager/internal/load-support.svelte';
import { CancellableTask } from '$lib/utils/cancellable-task';
import type { AssetGridRouteSearchParams } from '$lib/utils/navigation';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import {
getAllAlbums,
getAssetInfo,
getAssetOriginalPath,
getAssetPlaybackPath,
getAssetThumbnailPath,
getBaseUrl,
type AlbumResponseDto,
type AssetResponseDto,
} from '@immich/sdk';
import { type ZoomImageWheelState } from '@zoom-image/core';
import { isEqual } from 'lodash-es';
import { LRUCache } from 'lru-cache';
export enum AssetMediaSize {
Original = 'original',
@@ -24,28 +14,38 @@ export enum AssetMediaSize {
Playback = 'playback',
}
export type AssetManagerOptions = {
assetId?: string;
preloadAssetIds?: string[];
export type LoadAssetOptions = {
loadAlbums?: boolean;
size?: AssetMediaSize;
loadStack?: boolean;
};
export type AssetManagerOptions = {};
export class AssetManager {
isInitialized = $state(false);
isLoaded = $state(false);
loadError = $state(false);
asset: AssetResponseDto | undefined = $state();
preloadAssets: TimelineAsset[] = $state([]);
albums: AlbumResponseDto[] = $state([]);
// The queue waited for load. The first is the currect and the next is preload.
// The preload asset is not need to loading immediately.
assetLoadingQueue: AssetPackage[] = $state([]);
cacheKey: string | null = $derived(this.asset?.thumbhash ?? null);
// url: string | undefined = $derived.by(() => {
// if (this.asset) {
// return this.#getAssetUrl(toTimelineAsset(this.asset!));
// }
// });
url: string | undefined = $derived.by(() => {
if (this.asset) {
return this.#getAssetUrl(toTimelineAsset(this.asset!));
}
});
#maximumLRUCache: number = $state(10);
// TODO: This function is used to test.
dispose(value: AssetPackage, key: string) {
console.log(key);
console.log(value);
}
assetCache: LRUCache<string, AssetPackage> = $state(
new LRUCache({ max: this.#maximumLRUCache, dispose: this.dispose }),
);
showAssetViewer: boolean = $state(false);
gridScrollTarget: AssetGridRouteSearchParams | undefined = $state();
@@ -54,9 +54,8 @@ export class AssetManager {
initTask = new CancellableTask(
() => (this.isInitialized = true),
() => {
this.asset = undefined;
this.preloadAssets = [];
this.albums = [];
this.assetLoadingQueue = [];
this.assetCache.clear();
this.isInitialized = false;
},
() => void 0,
@@ -65,28 +64,33 @@ export class AssetManager {
static #INIT_OPTIONS = {};
#options: AssetManagerOptions = AssetManager.#INIT_OPTIONS;
static #DEFAULT_LOAD_ASSET_OPTIONS: LoadAssetOptions = {
loadAlbums: false,
loadStack: false,
};
constructor() {}
async loadAssetPackage(options?: LoadAssetOptions, cancelable?: boolean): Promise<void> {
cancelable = cancelable ?? true;
options = options ?? AssetManager.#DEFAULT_LOAD_ASSET_OPTIONS;
const assetPackage = this.assetLoadingQueue[0];
if (!assetPackage) {
return;
}
if (assetPackage.loader?.executed) {
return;
}
const result = await assetPackage.loader?.execute(async (signal: AbortSignal) => {
await loadFromAssetPackage(this, assetPackage, options, signal);
}, cancelable);
}
async #initializeAsset() {
if (this.#options.assetId) {
const assetResponse = await getAssetInfo({ id: this.#options.assetId, key: authManager.key });
if (!assetResponse) {
return;
}
this.asset = assetResponse;
} else {
throw new Error('The assetId in required in options.');
}
// TODO: Preload assets.
if (this.#options.loadAlbums ?? true) {
const albumsResponse = await getAllAlbums({ assetId: this.#options.assetId });
if (!albumsResponse) {
return;
}
this.albums = albumsResponse;
}
}
async updateOptions(options: AssetManagerOptions) {
@@ -97,27 +101,13 @@ export class AssetManager {
await this.#init(options);
}
#checkOptions() {
this.#options.size = AssetMediaSize.Original;
if (!this.asset || !this.zoomImageState) {
return;
}
if (this.asset.originalMimeType === 'image/gif' || this.zoomImageState.currentZoom > 1) {
// TODO: use original image forcely and according to the setting.
}
}
async #init(options: AssetManagerOptions) {
this.isInitialized = false;
this.asset = undefined;
this.preloadAssets = [];
this.albums = [];
this.assetLoadingQueue = [];
this.assetCache.clear();
await this.initTask.execute(async () => {
this.#options = options;
await this.#initializeAsset();
this.#checkOptions();
}, true);
}
@@ -125,63 +115,71 @@ export class AssetManager {
this.isInitialized = false;
}
async refreshAlbums() {}
// #checkOptions() {
// this.#options.size = AssetMediaSize.Original;
async refreshAsset() {}
// if (!this.asset || !this.zoomImageState) {
// return;
// }
#preload() {
for (const preloadAsset of this.preloadAssets) {
if (preloadAsset.isImage) {
let img = new Image();
const preloadUrl = this.#getAssetUrl(preloadAsset);
if (preloadUrl) {
img.src = preloadUrl;
} else {
throw new Error('AssetManager is not initialized.');
}
}
}
}
// if (this.asset.originalMimeType === 'image/gif' || this.zoomImageState.currentZoom > 1) {
// // TODO: use original image forcely and according to the setting.
// }
// }
#getAssetUrl(asset: TimelineAsset) {
if (!this.asset) {
return;
}
// #preload() {
// for (const preloadAsset of this.preloadAssets) {
// if (preloadAsset.isImage) {
// let img = new Image();
// const preloadUrl = this.#getAssetUrl(preloadAsset);
// if (preloadUrl) {
// img.src = preloadUrl;
// } else {
// throw new Error('AssetManager is not initialized.');
// }
// }
// }
// }
let path = undefined;
const searchParameters = new URLSearchParams();
if (authManager.key) {
searchParameters.set('key', authManager.key);
}
if (this.cacheKey) {
searchParameters.set('c', this.cacheKey);
}
// #getAssetUrl(asset: TimelineAsset) {
// if (!this.asset) {
// return;
// }
switch (this.#options.size) {
case AssetMediaSize.Original: {
path = getAssetOriginalPath(this.asset.id);
break;
}
case AssetMediaSize.Fullsize:
case AssetMediaSize.Thumbnail:
case AssetMediaSize.Preview: {
path = getAssetThumbnailPath(this.asset.id);
break;
}
case AssetMediaSize.Playback: {
path = getAssetPlaybackPath(this.asset.id);
break;
}
default:
// TODO: default AssetMediaSize
}
// let path = undefined;
// const searchParameters = new URLSearchParams();
// if (authManager.key) {
// searchParameters.set('key', authManager.key);
// }
// if (this.cacheKey) {
// searchParameters.set('c', this.cacheKey);
// }
return getBaseUrl() + path + '?' + searchParameters.toString();
}
// switch (this.#options.size) {
// case AssetMediaSize.Original: {
// path = getAssetOriginalPath(this.asset.id);
// break;
// }
// case AssetMediaSize.Fullsize:
// case AssetMediaSize.Thumbnail:
// case AssetMediaSize.Preview: {
// path = getAssetThumbnailPath(this.asset.id);
// break;
// }
// case AssetMediaSize.Playback: {
// path = getAssetPlaybackPath(this.asset.id);
// break;
// }
// default:
// // TODO: default AssetMediaSize
// }
get isOriginalImage() {
return this.#options.size === AssetMediaSize.Original || this.#options.size === AssetMediaSize.Fullsize;
}
// return getBaseUrl() + path + '?' + searchParameters.toString();
// }
// get isOriginalImage() {
// return this.#options.size === AssetMediaSize.Original || this.#options.size === AssetMediaSize.Fullsize;
// }
}
// const getAssetUrl = (id: string, targetSize: AssetMediaSize | 'original', cacheKey: string | null) => {