refactor: rename preloadManager to imageManager (#25436)

rename: preloadManager to imageManager
This commit is contained in:
Min Idzelis 2026-01-22 22:11:57 -05:00 committed by GitHub
parent 9b2939d778
commit a96a08939e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 145 additions and 152 deletions

Binary file not shown.

View file

@ -16301,6 +16301,7 @@
}, },
"AssetMediaSize": { "AssetMediaSize": {
"enum": [ "enum": [
"original",
"fullsize", "fullsize",
"preview", "preview",
"thumbnail" "thumbnail"

View file

@ -5660,6 +5660,7 @@ export enum MirrorAxis {
Vertical = "vertical" Vertical = "vertical"
} }
export enum AssetMediaSize { export enum AssetMediaSize {
Original = "original",
Fullsize = "fullsize", Fullsize = "fullsize",
Preview = "preview", Preview = "preview",
Thumbnail = "thumbnail" Thumbnail = "thumbnail"

View file

@ -7,6 +7,7 @@ import { AssetVisibility } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
export enum AssetMediaSize { export enum AssetMediaSize {
Original = 'original',
/** /**
* An full-sized image extracted/converted from non-web-friendly formats like RAW/HIF. * An full-sized image extracted/converted from non-web-friendly formats like RAW/HIF.
* or otherwise the original image itself. * or otherwise the original image itself.

View file

@ -1,5 +1,5 @@
import AlbumCover from '$lib/components/album-page/album-cover.svelte'; import AlbumCover from '$lib/components/album-page/album-cover.svelte';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { albumFactory } from '@test-data/factories/album-factory'; import { albumFactory } from '@test-data/factories/album-factory';
import { render } from '@testing-library/svelte'; import { render } from '@testing-library/svelte';
@ -7,7 +7,7 @@ vi.mock('$lib/utils');
describe('AlbumCover component', () => { describe('AlbumCover component', () => {
it('renders an image when the album has a thumbnail', () => { it('renders an image when the album has a thumbnail', () => {
vi.mocked(getAssetThumbnailUrl).mockReturnValue('/asdf'); vi.mocked(getAssetMediaUrl).mockReturnValue('/asdf');
const component = render(AlbumCover, { const component = render(AlbumCover, {
album: albumFactory.build({ album: albumFactory.build({
albumName: 'someName', albumName: 'someName',
@ -21,7 +21,7 @@ describe('AlbumCover component', () => {
expect(img.getAttribute('loading')).toBe('lazy'); expect(img.getAttribute('loading')).toBe('lazy');
expect(img.className).toBe('size-full rounded-xl object-cover aspect-square text'); expect(img.className).toBe('size-full rounded-xl object-cover aspect-square text');
expect(img.getAttribute('src')).toBe('/asdf'); expect(img.getAttribute('src')).toBe('/asdf');
expect(getAssetThumbnailUrl).toHaveBeenCalledWith({ id: '123' }); expect(getAssetMediaUrl).toHaveBeenCalledWith({ id: '123' });
}); });
it('renders an image when the album has no thumbnail', () => { it('renders an image when the album has no thumbnail', () => {

View file

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { getAssetThumbnailUrl } from '$lib/utils';
import { type AlbumResponseDto } from '@immich/sdk';
import NoCover from '$lib/components/sharedlinks-page/covers/no-cover.svelte';
import AssetCover from '$lib/components/sharedlinks-page/covers/asset-cover.svelte'; import AssetCover from '$lib/components/sharedlinks-page/covers/asset-cover.svelte';
import NoCover from '$lib/components/sharedlinks-page/covers/no-cover.svelte';
import { getAssetMediaUrl } from '$lib/utils';
import { type AlbumResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
interface Props { interface Props {
@ -15,7 +15,7 @@
let alt = $derived(album.albumName || $t('unnamed_album')); let alt = $derived(album.albumName || $t('unnamed_album'));
let thumbnailUrl = $derived( let thumbnailUrl = $derived(
album.albumThumbnailAssetId ? getAssetThumbnailUrl({ id: album.albumThumbnailAssetId }) : null, album.albumThumbnailAssetId ? getAssetMediaUrl({ id: album.albumThumbnailAssetId }) : null,
); );
</script> </script>

View file

@ -7,7 +7,7 @@
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { getAssetType } from '$lib/utils/asset-utils'; import { getAssetType } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { isTenMinutesApart } from '$lib/utils/timesince'; import { isTenMinutesApart } from '$lib/utils/timesince';
@ -142,7 +142,7 @@
<a class="aspect-square w-19 h-19" href={Route.viewAlbumAsset({ albumId, assetId: reaction.assetId })}> <a class="aspect-square w-19 h-19" href={Route.viewAlbumAsset({ albumId, assetId: reaction.assetId })}>
<img <img
class="rounded-lg w-19 h-19 object-cover" class="rounded-lg w-19 h-19 object-cover"
src={getAssetThumbnailUrl(reaction.assetId)} src={getAssetMediaUrl({ id: reaction.assetId })}
alt="Profile picture of {reaction.user.name}, who commented on this asset" alt="Profile picture of {reaction.user.name}, who commented on this asset"
/> />
</a> </a>
@ -195,7 +195,7 @@
> >
<img <img
class="rounded-lg w-19 h-19 object-cover" class="rounded-lg w-19 h-19 object-cover"
src={getAssetThumbnailUrl(reaction.assetId)} src={getAssetMediaUrl({ id: reaction.assetId })}
alt="Profile picture of {reaction.user.name}, who liked this asset" alt="Profile picture of {reaction.user.name}, who liked this asset"
/> />
</a> </a>

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { SCROLL_PROPERTIES } from '$lib/components/shared-components/album-selection/album-selection-utils'; import { SCROLL_PROPERTIES } from '$lib/components/shared-components/album-selection/album-selection-utils';
import { mobileDevice } from '$lib/stores/mobile-device.svelte'; import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { normalizeSearchString } from '$lib/utils/string-utils.js'; import { normalizeSearchString } from '$lib/utils/string-utils.js';
import { type AlbumResponseDto } from '@immich/sdk'; import { type AlbumResponseDto } from '@immich/sdk';
import { Icon } from '@immich/ui'; import { Icon } from '@immich/ui';
@ -134,7 +134,7 @@
<span class="h-16 w-16 shrink-0 rounded-xl bg-slate-300"> <span class="h-16 w-16 shrink-0 rounded-xl bg-slate-300">
{#if album.albumThumbnailAssetId} {#if album.albumThumbnailAssetId}
<img <img
src={getAssetThumbnailUrl(album.albumThumbnailAssetId)} src={getAssetMediaUrl({ id: album.albumThumbnailAssetId })}
alt={album.albumName} alt={album.albumName}
class={['h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg']} class={['h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg']}
data-testid="album-image" data-testid="album-image"

View file

@ -12,20 +12,19 @@
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { editManager, EditToolType } from '$lib/managers/edit/edit-manager.svelte'; import { editManager, EditToolType } from '$lib/managers/edit/edit-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte'; import { eventManager } from '$lib/managers/event-manager.svelte';
import { preloadManager } from '$lib/managers/PreloadManager.svelte'; import { imageManager } from '$lib/managers/ImageManager.svelte';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { ocrManager } from '$lib/stores/ocr.svelte'; import { ocrManager } from '$lib/stores/ocr.svelte';
import { alwaysLoadOriginalVideo } from '$lib/stores/preferences.store'; import { alwaysLoadOriginalVideo } from '$lib/stores/preferences.store';
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
import { getAssetUrl, getSharedLink, handlePromiseError } from '$lib/utils'; import { getSharedLink, handlePromiseError } from '$lib/utils';
import type { OnUndoDelete } from '$lib/utils/actions'; import type { OnUndoDelete } from '$lib/utils/actions';
import { navigateToAsset } from '$lib/utils/asset-utils'; import { navigateToAsset } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { InvocationTracker } from '$lib/utils/invocationTracker'; import { InvocationTracker } from '$lib/utils/invocationTracker';
import { SlideshowHistory } from '$lib/utils/slideshow-history'; import { SlideshowHistory } from '$lib/utils/slideshow-history';
import { preloadImageUrl } from '$lib/utils/sw-messaging';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toTimelineAsset } from '$lib/utils/timeline-util';
import { import {
AssetTypeEnum, AssetTypeEnum,
@ -133,9 +132,7 @@
} }
untrack(() => { untrack(() => {
if (stack && stack?.assets.length > 1) { imageManager.preload(stack?.assets[1]);
preloadImageUrl(getAssetUrl({ asset: stack.assets[1] }));
}
}); });
}; };
@ -220,7 +217,7 @@
} }
e?.stopPropagation(); e?.stopPropagation();
preloadManager.cancel(asset); imageManager.cancel(asset);
if (tracker.isActive()) { if (tracker.isActive()) {
return; return;
} }
@ -380,8 +377,8 @@
// eslint-disable-next-line @typescript-eslint/no-unused-expressions // eslint-disable-next-line @typescript-eslint/no-unused-expressions
asset; asset;
untrack(() => handlePromiseError(refresh())); untrack(() => handlePromiseError(refresh()));
preloadManager.preload(cursor.nextAsset); imageManager.preload(cursor.nextAsset);
preloadManager.preload(cursor.previousAsset); imageManager.preload(cursor.previousAsset);
}); });
const onAssetReplace = async ({ oldAssetId, newAssetId }: { oldAssetId: string; newAssetId: string }) => { const onAssetReplace = async ({ oldAssetId, newAssetId }: { oldAssetId: string; newAssetId: string }) => {
@ -503,7 +500,7 @@
/> />
{:else if viewerKind === 'StackVideoViewer'} {:else if viewerKind === 'StackVideoViewer'}
<VideoViewer <VideoViewer
assetId={previewStackedAsset!.id} asset={previewStackedAsset!}
cacheKey={previewStackedAsset!.thumbhash} cacheKey={previewStackedAsset!.thumbhash}
projectionType={previewStackedAsset!.exifInfo?.projectionType} projectionType={previewStackedAsset!.exifInfo?.projectionType}
loopVideo={true} loopVideo={true}
@ -516,6 +513,7 @@
/> />
{:else if viewerKind === 'LiveVideoViewer'} {:else if viewerKind === 'LiveVideoViewer'}
<VideoViewer <VideoViewer
{asset}
assetId={asset.livePhotoVideoId!} assetId={asset.livePhotoVideoId!}
cacheKey={asset.thumbhash} cacheKey={asset.thumbhash}
projectionType={asset.exifInfo?.projectionType} projectionType={asset.exifInfo?.projectionType}
@ -541,7 +539,7 @@
/> />
{:else if viewerKind === 'VideoViewer'} {:else if viewerKind === 'VideoViewer'}
<VideoViewer <VideoViewer
assetId={asset.id} {asset}
cacheKey={asset.thumbhash} cacheKey={asset.thumbhash}
projectionType={asset.exifInfo?.projectionType} projectionType={asset.exifInfo?.projectionType}
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow} loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}

View file

@ -14,7 +14,7 @@
import { boundingBoxesArray } from '$lib/stores/people.store'; import { boundingBoxesArray } from '$lib/stores/people.store';
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { preferences, user } from '$lib/stores/user.store'; import { preferences, user } from '$lib/stores/user.store';
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl, getPeopleThumbnailUrl } from '$lib/utils';
import { delay, getDimensions } from '$lib/utils/asset-utils'; import { delay, getDimensions } from '$lib/utils/asset-utils';
import { getByteUnitString } from '$lib/utils/byte-units'; import { getByteUnitString } from '$lib/utils/byte-units';
import { fromISODateTime, fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util'; import { fromISODateTime, fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util';
@ -515,7 +515,7 @@
alt={album.albumName} alt={album.albumName}
class="h-12.5 w-12.5 rounded object-cover" class="h-12.5 w-12.5 rounded object-cover"
src={album.albumThumbnailAssetId && src={album.albumThumbnailAssetId &&
getAssetThumbnailUrl({ id: album.albumThumbnailAssetId, size: AssetMediaSize.Preview })} getAssetMediaUrl({ id: album.albumThumbnailAssetId, size: AssetMediaSize.Preview })}
draggable="false" draggable="false"
/> />
</div> </div>

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { transformManager } from '$lib/managers/edit/transform-manager.svelte'; import { transformManager } from '$lib/managers/edit/transform-manager.svelte';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { toTimelineAsset } from '$lib/utils/timeline-util'; import { toTimelineAsset } from '$lib/utils/timeline-util';
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk'; import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
@ -14,7 +14,7 @@
let canvasContainer = $state<HTMLElement | null>(null); let canvasContainer = $state<HTMLElement | null>(null);
let imageSrc = $derived( let imageSrc = $derived(
getAssetThumbnailUrl({ id: asset.id, cacheKey: asset.thumbhash, edited: false, size: AssetMediaSize.Preview }), getAssetMediaUrl({ id: asset.id, cacheKey: asset.thumbhash, edited: false, size: AssetMediaSize.Preview }),
); );
let imageTransform = $derived.by(() => { let imageTransform = $derived.by(() => {

View file

@ -1,7 +1,6 @@
<script lang="ts"> <script lang="ts">
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import { getAssetOriginalUrl, getAssetThumbnailUrl } from '$lib/utils'; import { getAssetUrl } from '$lib/utils';
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
import { AssetMediaSize, viewAsset, type AssetResponseDto } from '@immich/sdk'; import { AssetMediaSize, viewAsset, type AssetResponseDto } from '@immich/sdk';
import { LoadingSpinner } from '@immich/ui'; import { LoadingSpinner } from '@immich/ui';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -24,13 +23,7 @@
{#await Promise.all([loadAssetData(asset.id), import('./photo-sphere-viewer-adapter.svelte')])} {#await Promise.all([loadAssetData(asset.id), import('./photo-sphere-viewer-adapter.svelte')])}
<LoadingSpinner /> <LoadingSpinner />
{:then [data, { default: PhotoSphereViewer }]} {:then [data, { default: PhotoSphereViewer }]}
<PhotoSphereViewer <PhotoSphereViewer bind:zoomToggle panorama={data} originalPanorama={getAssetUrl({ asset, forceOriginal: true })} />
bind:zoomToggle
panorama={data}
originalPanorama={isWebCompatibleImage(asset)
? getAssetOriginalUrl(asset.id)
: getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Fullsize, cacheKey: asset.thumbhash })}
/>
{:catch} {:catch}
{$t('errors.failed_to_load_asset')} {$t('errors.failed_to_load_asset')}
{/await} {/await}

View file

@ -6,7 +6,7 @@
import BrokenAsset from '$lib/components/assets/broken-asset.svelte'; import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
import { assetViewerFadeDuration } from '$lib/constants'; import { assetViewerFadeDuration } from '$lib/constants';
import { castManager } from '$lib/managers/cast-manager.svelte'; import { castManager } from '$lib/managers/cast-manager.svelte';
import { preloadManager } from '$lib/managers/PreloadManager.svelte'; import { imageManager } from '$lib/managers/ImageManager.svelte';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte'; import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { ocrManager } from '$lib/stores/ocr.svelte'; import { ocrManager } from '$lib/stores/ocr.svelte';
@ -164,7 +164,7 @@
imageError = imageLoaded = true; imageError = imageLoaded = true;
}; };
onDestroy(() => preloadManager.cancelPreloadUrl(imageLoaderUrl)); onDestroy(() => imageManager.cancelPreloadUrl(imageLoaderUrl));
let imageLoaderUrl = $derived( let imageLoaderUrl = $derived(
getAssetUrl({ asset, sharedLink, forceOriginal: originalImageLoaded || $photoZoomState.currentZoom > 1 }), getAssetUrl({ asset, sharedLink, forceOriginal: originalImageLoaded || $photoZoomState.currentZoom > 1 }),

View file

@ -10,7 +10,7 @@
videoViewerMuted, videoViewerMuted,
videoViewerVolume, videoViewerVolume,
} from '$lib/stores/preferences.store'; } from '$lib/stores/preferences.store';
import { getAssetOriginalUrl, getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
import { AssetMediaSize } from '@immich/sdk'; import { AssetMediaSize } from '@immich/sdk';
import { LoadingSpinner } from '@immich/ui'; import { LoadingSpinner } from '@immich/ui';
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
@ -44,7 +44,9 @@
let videoPlayer: HTMLVideoElement | undefined = $state(); let videoPlayer: HTMLVideoElement | undefined = $state();
let isLoading = $state(true); let isLoading = $state(true);
let assetFileUrl = $derived( let assetFileUrl = $derived(
playOriginalVideo ? getAssetOriginalUrl({ id: assetId, cacheKey }) : getAssetPlaybackUrl({ id: assetId, cacheKey }), playOriginalVideo
? getAssetMediaUrl({ id: assetId, size: AssetMediaSize.Original, cacheKey })
: getAssetPlaybackUrl({ id: assetId, cacheKey }),
); );
let isScrubbing = $state(false); let isScrubbing = $state(false);
let showVideo = $state(false); let showVideo = $state(false);
@ -127,7 +129,7 @@
{#if castManager.isCasting} {#if castManager.isCasting}
<div class="place-content-center h-full place-items-center"> <div class="place-content-center h-full place-items-center">
<VideoRemoteViewer <VideoRemoteViewer
poster={getAssetThumbnailUrl({ id: assetId, size: AssetMediaSize.Preview, cacheKey })} poster={getAssetMediaUrl({ id: assetId, size: AssetMediaSize.Preview, cacheKey })}
{onVideoStarted} {onVideoStarted}
{onVideoEnded} {onVideoEnded}
{assetFileUrl} {assetFileUrl}
@ -154,7 +156,7 @@
onclose={() => onClose()} onclose={() => onClose()}
muted={$videoViewerMuted} muted={$videoViewerMuted}
bind:volume={$videoViewerVolume} bind:volume={$videoViewerVolume}
poster={getAssetThumbnailUrl({ id: assetId, size: AssetMediaSize.Preview, cacheKey })} poster={getAssetMediaUrl({ id: assetId, size: AssetMediaSize.Preview, cacheKey })}
src={assetFileUrl} src={assetFileUrl}
> >
</video> </video>

View file

@ -1,14 +1,15 @@
<script lang="ts"> <script lang="ts">
import { getAssetOriginalUrl, getAssetPlaybackUrl } from '$lib/utils'; import { getAssetPlaybackUrl, getAssetUrl } from '$lib/utils';
import type { AssetResponseDto } from '@immich/sdk';
import { LoadingSpinner } from '@immich/ui'; import { LoadingSpinner } from '@immich/ui';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
interface Props { interface Props {
assetId: string; asset: AssetResponseDto;
} }
const { assetId }: Props = $props(); const { asset }: Props = $props();
const modules = Promise.all([ const modules = Promise.all([
import('./photo-sphere-viewer-adapter.svelte').then((module) => module.default), import('./photo-sphere-viewer-adapter.svelte').then((module) => module.default),
@ -23,8 +24,8 @@
<LoadingSpinner /> <LoadingSpinner />
{:then [PhotoSphereViewer, adapter, videoPlugin]} {:then [PhotoSphereViewer, adapter, videoPlugin]}
<PhotoSphereViewer <PhotoSphereViewer
panorama={{ source: getAssetPlaybackUrl(assetId) }} panorama={{ source: getAssetPlaybackUrl({ id: asset.id }) }}
originalPanorama={{ source: getAssetOriginalUrl(assetId) }} originalPanorama={{ source: getAssetUrl({ asset, forceOriginal: true })! }}
plugins={[videoPlugin]} plugins={[videoPlugin]}
{adapter} {adapter}
navbar navbar

View file

@ -2,9 +2,11 @@
import VideoNativeViewer from '$lib/components/asset-viewer/video-native-viewer.svelte'; import VideoNativeViewer from '$lib/components/asset-viewer/video-native-viewer.svelte';
import VideoPanoramaViewer from '$lib/components/asset-viewer/video-panorama-viewer.svelte'; import VideoPanoramaViewer from '$lib/components/asset-viewer/video-panorama-viewer.svelte';
import { ProjectionType } from '$lib/constants'; import { ProjectionType } from '$lib/constants';
import type { AssetResponseDto } from '@immich/sdk';
interface Props { interface Props {
assetId: string; asset: AssetResponseDto;
assetId?: string;
projectionType: string | null | undefined; projectionType: string | null | undefined;
cacheKey: string | null; cacheKey: string | null;
loopVideo: boolean; loopVideo: boolean;
@ -17,6 +19,7 @@
} }
let { let {
asset,
assetId, assetId,
projectionType, projectionType,
cacheKey, cacheKey,
@ -28,15 +31,17 @@
onVideoEnded, onVideoEnded,
onVideoStarted, onVideoStarted,
}: Props = $props(); }: Props = $props();
const effectiveAssetId = $derived(assetId ?? asset.id);
</script> </script>
{#if projectionType === ProjectionType.EQUIRECTANGULAR} {#if projectionType === ProjectionType.EQUIRECTANGULAR}
<VideoPanoramaViewer {assetId} /> <VideoPanoramaViewer {asset} />
{:else} {:else}
<VideoNativeViewer <VideoNativeViewer
{loopVideo} {loopVideo}
{cacheKey} {cacheKey}
{assetId} assetId={effectiveAssetId}
{playOriginalVideo} {playOriginalVideo}
{onPreviousAsset} {onPreviousAsset}
{onNextAsset} {onNextAsset}

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import BrokenAsset from '$lib/components/assets/broken-asset.svelte'; import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
import { preloadManager } from '$lib/managers/PreloadManager.svelte'; import { imageManager } from '$lib/managers/ImageManager.svelte';
import { Icon } from '@immich/ui'; import { Icon } from '@immich/ui';
import { mdiEyeOffOutline } from '@mdi/js'; import { mdiEyeOffOutline } from '@mdi/js';
import type { ActionReturn } from 'svelte/action'; import type { ActionReturn } from 'svelte/action';
@ -60,7 +60,7 @@
onComplete?.(false); onComplete?.(false);
} }
return { return {
destroy: () => preloadManager.cancelPreloadUrl(url), destroy: () => imageManager.cancelPreloadUrl(url),
}; };
} }

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { ProjectionType } from '$lib/constants'; import { ProjectionType } from '$lib/constants';
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store'; import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
import { getAssetOriginalUrl, getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
import { timeToSeconds } from '$lib/utils/date-time'; import { timeToSeconds } from '$lib/utils/date-time';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { AssetMediaSize, AssetVisibility, type UserResponseDto } from '@immich/sdk'; import { AssetMediaSize, AssetVisibility, type UserResponseDto } from '@immich/sdk';
@ -335,7 +335,7 @@
<ImageThumbnail <ImageThumbnail
class={imageClass} class={imageClass}
{brokenAssetClass} {brokenAssetClass}
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })} url={getAssetMediaUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })}
altText={$getAltText(asset)} altText={$getAltText(asset)}
widthStyle="{width}px" widthStyle="{width}px"
heightStyle="{height}px" heightStyle="{height}px"
@ -371,7 +371,7 @@
<ImageThumbnail <ImageThumbnail
class={imageClass} class={imageClass}
{brokenAssetClass} {brokenAssetClass}
url={getAssetOriginalUrl({ id: asset.id, cacheKey: asset.thumbhash })} url={getAssetMediaUrl({ id: asset.id, size: AssetMediaSize.Original, cacheKey: asset.thumbhash })}
altText={$getAltText(asset)} altText={$getAltText(asset)}
widthStyle="{width}px" widthStyle="{width}px"
heightStyle="{height}px" heightStyle="{height}px"

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { assetViewerFadeDuration } from '$lib/constants'; import { assetViewerFadeDuration } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { AssetMediaSize } from '@immich/sdk'; import { AssetMediaSize } from '@immich/sdk';
import { LoadingSpinner } from '@immich/ui'; import { LoadingSpinner } from '@immich/ui';
@ -35,7 +35,7 @@
}; };
}); });
const imageLoaderUrl = $derived(getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Preview })); const imageLoaderUrl = $derived(getAssetMediaUrl({ id: asset.id, size: AssetMediaSize.Preview }));
</script> </script>
{#if !imageLoaded} {#if !imageLoaded}

View file

@ -2,7 +2,7 @@
import { assetViewerFadeDuration } from '$lib/constants'; import { assetViewerFadeDuration } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { autoPlayVideo } from '$lib/stores/preferences.store'; import { autoPlayVideo } from '$lib/stores/preferences.store';
import { getAssetPlaybackUrl, getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
import { AssetMediaSize } from '@immich/sdk'; import { AssetMediaSize } from '@immich/sdk';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
@ -32,7 +32,7 @@
playsinline playsinline
class="h-full w-full rounded-2xl object-contain transition-all" class="h-full w-full rounded-2xl object-contain transition-all"
src={getAssetPlaybackUrl({ id: asset.id })} src={getAssetPlaybackUrl({ id: asset.id })}
poster={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Preview })} poster={getAssetMediaUrl({ id: asset.id, size: AssetMediaSize.Preview })}
draggable="false" draggable="false"
muted={videoViewerMuted} muted={videoViewerMuted}
volume={videoViewerVolume} volume={videoViewerVolume}

View file

@ -30,7 +30,7 @@
import { memoryStore, type MemoryAsset } from '$lib/stores/memory.store.svelte'; import { memoryStore, type MemoryAsset } from '$lib/stores/memory.store.svelte';
import { locale, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store'; import { locale, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store';
import { preferences } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils'; import { getAssetMediaUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils';
import { cancelMultiselect } from '$lib/utils/asset-utils'; import { cancelMultiselect } from '$lib/utils/asset-utils';
import { fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util'; import { fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util';
import { AssetMediaSize, AssetTypeEnum, getAssetInfo } from '@immich/sdk'; import { AssetMediaSize, AssetTypeEnum, getAssetInfo } from '@immich/sdk';
@ -449,7 +449,7 @@
{#if current.previousMemory && current.previousMemory.assets.length > 0} {#if current.previousMemory && current.previousMemory.assets.length > 0}
<img <img
class="h-full w-full rounded-2xl object-cover" class="h-full w-full rounded-2xl object-cover"
src={getAssetThumbnailUrl({ id: current.previousMemory.assets[0].id, size: AssetMediaSize.Preview })} src={getAssetMediaUrl({ id: current.previousMemory.assets[0].id, size: AssetMediaSize.Preview })}
alt={$t('previous_memory')} alt={$t('previous_memory')}
draggable="false" draggable="false"
/> />
@ -598,7 +598,7 @@
{#if current.nextMemory && current.nextMemory.assets.length > 0} {#if current.nextMemory && current.nextMemory.assets.length > 0}
<img <img
class="h-full w-full rounded-2xl object-cover" class="h-full w-full rounded-2xl object-cover"
src={getAssetThumbnailUrl({ id: current.nextMemory.assets[0].id, size: AssetMediaSize.Preview })} src={getAssetMediaUrl({ id: current.nextMemory.assets[0].id, size: AssetMediaSize.Preview })}
alt={$t('next_memory')} alt={$t('next_memory')}
draggable="false" draggable="false"
/> />

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { placesViewSettings } from '$lib/stores/preferences.store'; import { placesViewSettings } from '$lib/stores/preferences.store';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { type PlacesGroup, isPlacesGroupCollapsed, togglePlacesGroupCollapsing } from '$lib/utils/places-utils'; import { type PlacesGroup, isPlacesGroupCollapsed, togglePlacesGroupCollapsing } from '$lib/utils/places-utils';
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk'; import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
import { Icon } from '@immich/ui'; import { Icon } from '@immich/ui';
@ -45,7 +45,7 @@
class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-39 justify-center overflow-hidden rounded-xl brightness-75 filter" class="flex w-[calc((100vw-(72px+5rem))/2)] max-w-39 justify-center overflow-hidden rounded-xl brightness-75 filter"
> >
<img <img
src={getAssetThumbnailUrl({ id: item.id, size: AssetMediaSize.Thumbnail })} src={getAssetMediaUrl({ id: item.id, size: AssetMediaSize.Thumbnail })}
alt={city} alt={city}
class="object-cover w-39 h-39" class="object-cover w-39 h-39"
loading="lazy" loading="lazy"

View file

@ -16,7 +16,7 @@
import { themeManager } from '$lib/managers/theme-manager.svelte'; import { themeManager } from '$lib/managers/theme-manager.svelte';
import MapSettingsModal from '$lib/modals/MapSettingsModal.svelte'; import MapSettingsModal from '$lib/modals/MapSettingsModal.svelte';
import { mapSettings } from '$lib/stores/preferences.store'; import { mapSettings } from '$lib/stores/preferences.store';
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils'; import { getAssetMediaUrl, handlePromiseError } from '$lib/utils';
import { getMapMarkers, type MapMarkerResponseDto } from '@immich/sdk'; import { getMapMarkers, type MapMarkerResponseDto } from '@immich/sdk';
import { Icon, modalManager } from '@immich/ui'; import { Icon, modalManager } from '@immich/ui';
import { mdiCog, mdiMap, mdiMapMarker } from '@mdi/js'; import { mdiCog, mdiMap, mdiMapMarker } from '@mdi/js';
@ -388,7 +388,7 @@
<Icon icon={mdiMapMarker} size="50px" class="text-primary -translate-y-[50%]" /> <Icon icon={mdiMapMarker} size="50px" class="text-primary -translate-y-[50%]" />
{:else} {:else}
<img <img
src={getAssetThumbnailUrl(feature.properties?.id)} src={getAssetMediaUrl({ id: feature.properties?.id })}
class="rounded-full w-15 h-15 border-2 border-immich-primary shadow-lg hover:border-immich-dark-primary transition-all duration-200 hover:scale-150 object-cover bg-immich-primary" class="rounded-full w-15 h-15 border-2 border-immich-primary shadow-lg hover:border-immich-dark-primary transition-all duration-200 hover:scale-150 object-cover bg-immich-primary"
alt={feature.properties?.city && feature.properties.country alt={feature.properties?.city && feature.properties.country
? $t('map_marker_for_images', { ? $t('map_marker_for_images', {

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { userInteraction } from '$lib/stores/user.svelte'; import { userInteraction } from '$lib/stores/user.svelte';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { getAllAlbums, type AlbumResponseDto } from '@immich/sdk'; import { getAllAlbums, type AlbumResponseDto } from '@immich/sdk';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@ -34,7 +34,7 @@
<div <div
class="h-6 w-6 bg-cover rounded bg-gray-200 dark:bg-gray-600" class="h-6 w-6 bg-cover rounded bg-gray-200 dark:bg-gray-600"
style={album.albumThumbnailAssetId style={album.albumThumbnailAssetId
? `background-image:url('${getAssetThumbnailUrl({ id: album.albumThumbnailAssetId })}')` ? `background-image:url('${getAssetMediaUrl({ id: album.albumThumbnailAssetId })}')`
: ''} : ''}
></div> ></div>
</div> </div>

View file

@ -1,5 +1,5 @@
import ShareCover from '$lib/components/sharedlinks-page/covers/share-cover.svelte'; import ShareCover from '$lib/components/sharedlinks-page/covers/share-cover.svelte';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { albumFactory } from '@test-data/factories/album-factory'; import { albumFactory } from '@test-data/factories/album-factory';
import { assetFactory } from '@test-data/factories/asset-factory'; import { assetFactory } from '@test-data/factories/asset-factory';
import { sharedLinkFactory } from '@test-data/factories/shared-link-factory'; import { sharedLinkFactory } from '@test-data/factories/shared-link-factory';
@ -21,7 +21,7 @@ describe('ShareCover component', () => {
}); });
it('renders an image when the shared link is an individual share', () => { it('renders an image when the shared link is an individual share', () => {
vi.mocked(getAssetThumbnailUrl).mockReturnValue('/asdf'); vi.mocked(getAssetMediaUrl).mockReturnValue('/asdf');
const component = render(ShareCover, { const component = render(ShareCover, {
sharedLink: sharedLinkFactory.build({ assets: [assetFactory.build({ id: 'someId' })] }), sharedLink: sharedLinkFactory.build({ assets: [assetFactory.build({ id: 'someId' })] }),
preload: false, preload: false,
@ -32,7 +32,7 @@ describe('ShareCover component', () => {
expect(img.getAttribute('loading')).toBe('lazy'); expect(img.getAttribute('loading')).toBe('lazy');
expect(img.className).toBe('size-full rounded-xl object-cover aspect-square text'); expect(img.className).toBe('size-full rounded-xl object-cover aspect-square text');
expect(img.getAttribute('src')).toBe('/asdf'); expect(img.getAttribute('src')).toBe('/asdf');
expect(getAssetThumbnailUrl).toHaveBeenCalledWith('someId'); expect(getAssetMediaUrl).toHaveBeenCalledWith({ id: 'someId' });
}); });
it('renders an image when the shared link has no album or assets', () => { it('renders an image when the shared link has no album or assets', () => {

View file

@ -2,7 +2,7 @@
import AlbumCover from '$lib/components/album-page/album-cover.svelte'; import AlbumCover from '$lib/components/album-page/album-cover.svelte';
import AssetCover from '$lib/components/sharedlinks-page/covers/asset-cover.svelte'; import AssetCover from '$lib/components/sharedlinks-page/covers/asset-cover.svelte';
import NoCover from '$lib/components/sharedlinks-page/covers/no-cover.svelte'; import NoCover from '$lib/components/sharedlinks-page/covers/no-cover.svelte';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import type { SharedLinkResponseDto } from '@immich/sdk'; import type { SharedLinkResponseDto } from '@immich/sdk';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@ -23,7 +23,7 @@
alt={$t('individual_share')} alt={$t('individual_share')}
class={className} class={className}
{preload} {preload}
src={getAssetThumbnailUrl(sharedLink.assets[0].id)} src={getAssetMediaUrl({ id: sharedLink.assets[0].id })}
/> />
{:else} {:else}
<NoCover alt={$t('unnamed_share')} class={className} {preload} /> <NoCover alt={$t('unnamed_share')} class={className} {preload} />

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { locale } from '$lib/stores/preferences.store'; import { locale } from '$lib/stores/preferences.store';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { getAssetResolution, getFileSize } from '$lib/utils/asset-utils'; import { getAssetResolution, getFileSize } from '$lib/utils/asset-utils';
import { getAltText } from '$lib/utils/thumbnail-util'; import { getAltText } from '$lib/utils/thumbnail-util';
import { fromISODateTime, fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util'; import { fromISODateTime, fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util';
@ -112,7 +112,7 @@
> >
<!-- THUMBNAIL--> <!-- THUMBNAIL-->
<img <img
src={getAssetThumbnailUrl(asset.id)} src={getAssetMediaUrl({ id: asset.id })}
alt={$getAltText(toTimelineAsset(asset))} alt={$getAltText(toTimelineAsset(asset))}
title={assetData} title={assetData}
class="h-60 object-cover w-full rounded-t-md" class="h-60 object-cover w-full rounded-t-md"

View file

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl, getPeopleThumbnailUrl } from '$lib/utils';
import type { AlbumResponseDto, PersonResponseDto } from '@immich/sdk'; import type { AlbumResponseDto, PersonResponseDto } from '@immich/sdk';
import { Card, CardBody, IconButton, Text } from '@immich/ui'; import { Card, CardBody, IconButton, Text } from '@immich/ui';
import { mdiClose } from '@mdi/js'; import { mdiClose } from '@mdi/js';
@ -20,7 +20,7 @@
{#if isAlbum && 'albumThumbnailAssetId' in item} {#if isAlbum && 'albumThumbnailAssetId' in item}
{#if item.albumThumbnailAssetId} {#if item.albumThumbnailAssetId}
<img <img
src={getAssetThumbnailUrl(item.albumThumbnailAssetId)} src={getAssetMediaUrl({ id: item.albumThumbnailAssetId })}
alt={item.albumName} alt={item.albumName}
class="h-12 w-12 rounded-lg object-cover" class="h-12 w-12 rounded-lg object-cover"
/> />

View file

@ -0,0 +1,43 @@
import { getAssetMediaUrl } from '$lib/utils';
import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
type AllAssetMediaSize = AssetMediaSize | 'all';
class ImageManager {
preload(asset: AssetResponseDto | undefined, size: AssetMediaSize = AssetMediaSize.Preview) {
if (!asset) {
return;
}
const url = getAssetMediaUrl({ id: asset.id, size, cacheKey: asset.thumbhash });
if (!url) {
return;
}
const img = new Image();
img.src = url;
}
cancel(asset: AssetResponseDto | undefined, size: AllAssetMediaSize = AssetMediaSize.Preview) {
if (!asset) {
return;
}
const sizes = size === 'all' ? Object.values(AssetMediaSize) : [size];
for (const size of sizes) {
const url = getAssetMediaUrl({ id: asset.id, size, cacheKey: asset.thumbhash });
if (url) {
cancelImageUrl(url);
}
}
}
cancelPreloadUrl(url: string | undefined) {
if (url) {
cancelImageUrl(url);
}
}
}
export const imageManager = new ImageManager();

View file

@ -1,38 +0,0 @@
import { getAssetUrl } from '$lib/utils';
import { cancelImageUrl, preloadImageUrl } from '$lib/utils/sw-messaging';
import { AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
class PreloadManager {
preload(asset: AssetResponseDto | undefined) {
if (globalThis.isSecureContext) {
preloadImageUrl(getAssetUrl({ asset }));
return;
}
if (!asset || asset.type !== AssetTypeEnum.Image) {
return;
}
const img = new Image();
const url = getAssetUrl({ asset });
if (!url) {
return;
}
img.src = url;
}
cancel(asset: AssetResponseDto | undefined) {
if (!globalThis.isSecureContext || !asset) {
return;
}
const url = getAssetUrl({ asset });
cancelImageUrl(url);
}
cancelPreloadUrl(url: string | undefined) {
if (!globalThis.isSecureContext) {
return;
}
cancelImageUrl(url);
}
}
export const preloadManager = new PreloadManager();

View file

@ -1,5 +1,5 @@
import { editManager, type EditActions, type EditToolManager } from '$lib/managers/edit/edit-manager.svelte'; import { editManager, type EditActions, type EditToolManager } from '$lib/managers/edit/edit-manager.svelte';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { getDimensions } from '$lib/utils/asset-utils'; import { getDimensions } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { import {
@ -185,7 +185,7 @@ class TransformManager implements EditToolManager {
this.imgElement = new Image(); this.imgElement = new Image();
const imageURL = getAssetThumbnailUrl({ const imageURL = getAssetMediaUrl({
id: asset.id, id: asset.id,
cacheKey: asset.thumbhash, cacheKey: asset.thumbhash,
edited: false, edited: false,

View file

@ -193,7 +193,7 @@ const createUrl = (path: string, parameters?: Record<string, unknown>) => {
return getBaseUrl() + url.pathname + url.search + url.hash; return getBaseUrl() + url.pathname + url.search + url.hash;
}; };
type AssetUrlOptions = { id: string; cacheKey?: string | null; edited?: boolean }; type AssetUrlOptions = { id: string; cacheKey?: string | null; edited?: boolean; size?: AssetMediaSize };
export const getAssetUrl = ({ export const getAssetUrl = ({
asset, asset,
@ -210,12 +210,10 @@ export const getAssetUrl = ({
const id = asset.id; const id = asset.id;
const cacheKey = asset.thumbhash; const cacheKey = asset.thumbhash;
if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) { if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) {
return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, cacheKey }); return getAssetMediaUrl({ id, size: AssetMediaSize.Preview, cacheKey });
} }
const targetSize = targetImageSize(asset, forceOriginal); const size = targetImageSize(asset, forceOriginal);
return targetSize === 'original' return getAssetMediaUrl({ id, size, cacheKey });
? getAssetOriginalUrl({ id, cacheKey })
: getAssetThumbnailUrl({ id, size: targetSize, cacheKey });
}; };
const forceUseOriginal = (asset: AssetResponseDto) => { const forceUseOriginal = (asset: AssetResponseDto) => {
@ -224,33 +222,21 @@ const forceUseOriginal = (asset: AssetResponseDto) => {
export const targetImageSize = (asset: AssetResponseDto, forceOriginal: boolean) => { export const targetImageSize = (asset: AssetResponseDto, forceOriginal: boolean) => {
if (forceOriginal || get(alwaysLoadOriginalFile) || forceUseOriginal(asset)) { if (forceOriginal || get(alwaysLoadOriginalFile) || forceUseOriginal(asset)) {
return isWebCompatibleImage(asset) ? 'original' : AssetMediaSize.Fullsize; return isWebCompatibleImage(asset) ? AssetMediaSize.Original : AssetMediaSize.Fullsize;
} }
return AssetMediaSize.Preview; return AssetMediaSize.Preview;
}; };
export const getAssetOriginalUrl = (options: string | AssetUrlOptions) => { export const getAssetMediaUrl = (options: AssetUrlOptions) => {
if (typeof options === 'string') { const { id, size, cacheKey: c, edited = true } = options;
options = { id: options }; const isOriginal = size === AssetMediaSize.Original;
} const path = isOriginal ? getAssetOriginalPath(id) : getAssetThumbnailPath(id);
const { id, cacheKey, edited = true } = options; return createUrl(path, { ...authManager.params, size: isOriginal ? undefined : size, c, edited });
return createUrl(getAssetOriginalPath(id), { ...authManager.params, c: cacheKey, edited });
}; };
export const getAssetThumbnailUrl = (options: string | (AssetUrlOptions & { size?: AssetMediaSize })) => { export const getAssetPlaybackUrl = (options: AssetUrlOptions) => {
if (typeof options === 'string') { const { id, cacheKey: c } = options;
options = { id: options }; return createUrl(getAssetPlaybackPath(id), { ...authManager.params, c });
}
const { id, size, cacheKey, edited = true } = options;
return createUrl(getAssetThumbnailPath(id), { ...authManager.params, size, c: cacheKey, edited });
};
export const getAssetPlaybackUrl = (options: string | AssetUrlOptions) => {
if (typeof options === 'string') {
options = { id: options };
}
const { id, cacheKey } = options;
return createUrl(getAssetPlaybackPath(id), { ...authManager.params, c: cacheKey });
}; };
export const getProfileImageUrl = (user: UserResponseDto) => export const getProfileImageUrl = (user: UserResponseDto) =>

View file

@ -1,5 +1,5 @@
import type { Faces } from '$lib/stores/people.store'; import type { Faces } from '$lib/stores/people.store';
import { getAssetThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl } from '$lib/utils';
import { AssetTypeEnum, type AssetFaceResponseDto } from '@immich/sdk'; import { AssetTypeEnum, type AssetFaceResponseDto } from '@immich/sdk';
import type { ZoomImageWheelState } from '@zoom-image/core'; import type { ZoomImageWheelState } from '@zoom-image/core';
@ -82,7 +82,7 @@ export const zoomImageToBase64 = async (
if (assetType === AssetTypeEnum.Image) { if (assetType === AssetTypeEnum.Image) {
image = photoViewer; image = photoViewer;
} else if (assetType === AssetTypeEnum.Video) { } else if (assetType === AssetTypeEnum.Video) {
const data = getAssetThumbnailUrl(assetId); const data = getAssetMediaUrl({ id: assetId });
const img: HTMLImageElement = new Image(); const img: HTMLImageElement = new Image();
img.src = data; img.src = data;

View file

@ -1,4 +1,4 @@
import { getAssetThumbnailUrl, setSharedLink } from '$lib/utils'; import { getAssetMediaUrl, setSharedLink } from '$lib/utils';
import { authenticate } from '$lib/utils/auth'; import { authenticate } from '$lib/utils/auth';
import { getFormatter } from '$lib/utils/i18n'; import { getFormatter } from '$lib/utils/i18n';
import { getAssetInfoFromParam } from '$lib/utils/navigation'; import { getAssetInfoFromParam } from '$lib/utils/navigation';
@ -36,7 +36,7 @@ export const loadSharedLink = async ({
setSharedLink(sharedLink); setSharedLink(sharedLink);
const assetCount = sharedLink.assets.length; const assetCount = sharedLink.assets.length;
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id; const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
const assetPath = assetId ? getAssetThumbnailUrl(assetId) : '/feature-panel.png'; const assetPath = assetId ? getAssetMediaUrl({ id: assetId }) : '/feature-panel.png';
return { return {
...common, ...common,

View file

@ -5,7 +5,7 @@
import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte'; import EmptyPlaceholder from '$lib/components/shared-components/empty-placeholder.svelte';
import SingleGridRow from '$lib/components/shared-components/single-grid-row.svelte'; import SingleGridRow from '$lib/components/shared-components/single-grid-row.svelte';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils'; import { getAssetMediaUrl, getPeopleThumbnailUrl } from '$lib/utils';
import { AssetMediaSize, type SearchExploreResponseDto } from '@immich/sdk'; import { AssetMediaSize, type SearchExploreResponseDto } from '@immich/sdk';
import { Icon } from '@immich/ui'; import { Icon } from '@immich/ui';
import { mdiHeart } from '@mdi/js'; import { mdiHeart } from '@mdi/js';
@ -90,7 +90,7 @@
<a class="relative" href={Route.search({ city: item.value })} draggable="false"> <a class="relative" href={Route.search({ city: item.value })} draggable="false">
<div class="flex justify-center overflow-hidden rounded-xl brightness-75 filter"> <div class="flex justify-center overflow-hidden rounded-xl brightness-75 filter">
<img <img
src={getAssetThumbnailUrl({ id: item.data.id, size: AssetMediaSize.Thumbnail })} src={getAssetMediaUrl({ id: item.data.id, size: AssetMediaSize.Thumbnail })}
alt={item.value} alt={item.value}
class="object-cover aspect-square w-full" class="object-cover aspect-square w-full"
/> />

View file

@ -28,7 +28,7 @@
import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { memoryStore } from '$lib/stores/memory.store.svelte'; import { memoryStore } from '$lib/stores/memory.store.svelte';
import { preferences, user } from '$lib/stores/user.store'; import { preferences, user } from '$lib/stores/user.store';
import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils'; import { getAssetMediaUrl, memoryLaneTitle } from '$lib/utils';
import { import {
updateStackedAssetInTimeline, updateStackedAssetInTimeline,
updateUnstackedAssetInTimeline, updateUnstackedAssetInTimeline,
@ -98,7 +98,7 @@
title: $memoryLaneTitle(memory), title: $memoryLaneTitle(memory),
href: Route.memories({ id: memory.assets[0].id }), href: Route.memories({ id: memory.assets[0].id }),
alt: $t('memory_lane_title', { values: { title: $getAltText(toTimelineAsset(memory.assets[0])) } }), alt: $t('memory_lane_title', { values: { title: $getAltText(toTimelineAsset(memory.assets[0])) } }),
src: getAssetThumbnailUrl(memory.assets[0].id), src: getAssetMediaUrl({ id: memory.assets[0].id }),
})), })),
); );
</script> </script>