Compare commits

...

1 Commits

Author SHA1 Message Date
midzelis
3ee13c1e3f rename: preloadManager to imageManager 2026-01-22 15:42:43 +00:00
36 changed files with 153 additions and 152 deletions

View File

@@ -23,12 +23,14 @@ class AssetMediaSize {
String toJson() => value;
static const original = AssetMediaSize._(r'original');
static const fullsize = AssetMediaSize._(r'fullsize');
static const preview = AssetMediaSize._(r'preview');
static const thumbnail = AssetMediaSize._(r'thumbnail');
/// List of all possible values in this [enum][AssetMediaSize].
static const values = <AssetMediaSize>[
original,
fullsize,
preview,
thumbnail,
@@ -70,6 +72,7 @@ class AssetMediaSizeTypeTransformer {
AssetMediaSize? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'original': return AssetMediaSize.original;
case r'fullsize': return AssetMediaSize.fullsize;
case r'preview': return AssetMediaSize.preview;
case r'thumbnail': return AssetMediaSize.thumbnail;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
<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 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';
interface Props {
@@ -15,7 +15,7 @@
let alt = $derived(album.albumName || $t('unnamed_album'));
let thumbnailUrl = $derived(
album.albumThumbnailAssetId ? getAssetThumbnailUrl({ id: album.albumThumbnailAssetId }) : null,
album.albumThumbnailAssetId ? getAssetMediaUrl({ id: album.albumThumbnailAssetId }) : null,
);
</script>

View File

@@ -7,7 +7,7 @@
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
import { Route } from '$lib/route';
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 { handleError } from '$lib/utils/handle-error';
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 })}>
<img
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"
/>
</a>
@@ -195,7 +195,7 @@
>
<img
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"
/>
</a>

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<script lang="ts">
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 { toTimelineAsset } from '$lib/utils/timeline-util';
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
@@ -14,7 +14,7 @@
let canvasContainer = $state<HTMLElement | null>(null);
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(() => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
<script lang="ts">
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 { mdiEyeOffOutline } from '@mdi/js';
import type { ActionReturn } from 'svelte/action';
@@ -60,7 +60,7 @@
onComplete?.(false);
}
return {
destroy: () => preloadManager.cancelPreloadUrl(url),
destroy: () => imageManager.cancelPreloadUrl(url),
};
}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { ProjectionType } from '$lib/constants';
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 { getAltText } from '$lib/utils/thumbnail-util';
import { AssetMediaSize, AssetVisibility, type UserResponseDto } from '@immich/sdk';
@@ -335,7 +335,7 @@
<ImageThumbnail
class={imageClass}
{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)}
widthStyle="{width}px"
heightStyle="{height}px"
@@ -371,7 +371,7 @@
<ImageThumbnail
class={imageClass}
{brokenAssetClass}
url={getAssetOriginalUrl({ id: asset.id, cacheKey: asset.thumbhash })}
url={getAssetMediaUrl({ id: asset.id, size: AssetMediaSize.Original, cacheKey: asset.thumbhash })}
altText={$getAltText(asset)}
widthStyle="{width}px"
heightStyle="{height}px"

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { assetViewerFadeDuration } from '$lib/constants';
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 { AssetMediaSize } from '@immich/sdk';
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>
{#if !imageLoaded}

View File

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

View File

@@ -30,7 +30,7 @@
import { memoryStore, type MemoryAsset } from '$lib/stores/memory.store.svelte';
import { locale, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.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 { fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util';
import { AssetMediaSize, AssetTypeEnum, getAssetInfo } from '@immich/sdk';
@@ -449,7 +449,7 @@
{#if current.previousMemory && current.previousMemory.assets.length > 0}
<img
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')}
draggable="false"
/>
@@ -598,7 +598,7 @@
{#if current.nextMemory && current.nextMemory.assets.length > 0}
<img
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')}
draggable="false"
/>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { Route } from '$lib/route';
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 { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
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"
>
<img
src={getAssetThumbnailUrl({ id: item.id, size: AssetMediaSize.Thumbnail })}
src={getAssetMediaUrl({ id: item.id, size: AssetMediaSize.Thumbnail })}
alt={city}
class="object-cover w-39 h-39"
loading="lazy"

View File

@@ -16,7 +16,7 @@
import { themeManager } from '$lib/managers/theme-manager.svelte';
import MapSettingsModal from '$lib/modals/MapSettingsModal.svelte';
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 { Icon, modalManager } from '@immich/ui';
import { mdiCog, mdiMap, mdiMapMarker } from '@mdi/js';
@@ -388,7 +388,7 @@
<Icon icon={mdiMapMarker} size="50px" class="text-primary -translate-y-[50%]" />
{:else}
<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"
alt={feature.properties?.city && feature.properties.country
? $t('map_marker_for_images', {

View File

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

View File

@@ -1,5 +1,5 @@
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 { assetFactory } from '@test-data/factories/asset-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', () => {
vi.mocked(getAssetThumbnailUrl).mockReturnValue('/asdf');
vi.mocked(getAssetMediaUrl).mockReturnValue('/asdf');
const component = render(ShareCover, {
sharedLink: sharedLinkFactory.build({ assets: [assetFactory.build({ id: 'someId' })] }),
preload: false,
@@ -32,7 +32,7 @@ describe('ShareCover component', () => {
expect(img.getAttribute('loading')).toBe('lazy');
expect(img.className).toBe('size-full rounded-xl object-cover aspect-square text');
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', () => {

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
import { getAssetMediaUrl } from '$lib/utils';
import { cancelImageUrl } from '$lib/utils/sw-messaging';
import { AssetMediaSize, type AssetResponseDto } from '@immich/sdk';
const AllAssetMediaSize = {
...AssetMediaSize,
All: 'all',
} as const;
type AllAssetMediaSize = (typeof AllAssetMediaSize)[keyof typeof AllAssetMediaSize];
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 { getAssetThumbnailUrl } from '$lib/utils';
import { getAssetMediaUrl } from '$lib/utils';
import { getDimensions } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
import {
@@ -185,7 +185,7 @@ class TransformManager implements EditToolManager {
this.imgElement = new Image();
const imageURL = getAssetThumbnailUrl({
const imageURL = getAssetMediaUrl({
id: asset.id,
cacheKey: asset.thumbhash,
edited: false,

View File

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

View File

@@ -1,5 +1,5 @@
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 type { ZoomImageWheelState } from '@zoom-image/core';
@@ -82,7 +82,7 @@ export const zoomImageToBase64 = async (
if (assetType === AssetTypeEnum.Image) {
image = photoViewer;
} else if (assetType === AssetTypeEnum.Video) {
const data = getAssetThumbnailUrl(assetId);
const data = getAssetMediaUrl({ id: assetId });
const img: HTMLImageElement = new Image();
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 { getFormatter } from '$lib/utils/i18n';
import { getAssetInfoFromParam } from '$lib/utils/navigation';
@@ -36,7 +36,7 @@ export const loadSharedLink = async ({
setSharedLink(sharedLink);
const assetCount = sharedLink.assets.length;
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 {
...common,

View File

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

View File

@@ -28,7 +28,7 @@
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { memoryStore } from '$lib/stores/memory.store.svelte';
import { preferences, user } from '$lib/stores/user.store';
import { getAssetThumbnailUrl, memoryLaneTitle } from '$lib/utils';
import { getAssetMediaUrl, memoryLaneTitle } from '$lib/utils';
import {
updateStackedAssetInTimeline,
updateUnstackedAssetInTimeline,
@@ -98,7 +98,7 @@
title: $memoryLaneTitle(memory),
href: Route.memories({ id: memory.assets[0].id }),
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>