mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
feat(web): use thumbhash as a cache key (#16106)
Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
parent
c524fcf084
commit
f386b4d377
@ -43,10 +43,10 @@
|
|||||||
import DetailPanel from './detail-panel.svelte';
|
import DetailPanel from './detail-panel.svelte';
|
||||||
import CropArea from './editor/crop-tool/crop-area.svelte';
|
import CropArea from './editor/crop-tool/crop-area.svelte';
|
||||||
import EditorPanel from './editor/editor-panel.svelte';
|
import EditorPanel from './editor/editor-panel.svelte';
|
||||||
|
import ImagePanoramaViewer from './image-panorama-viewer.svelte';
|
||||||
import PhotoViewer from './photo-viewer.svelte';
|
import PhotoViewer from './photo-viewer.svelte';
|
||||||
import SlideshowBar from './slideshow-bar.svelte';
|
import SlideshowBar from './slideshow-bar.svelte';
|
||||||
import VideoViewer from './video-wrapper-viewer.svelte';
|
import VideoViewer from './video-wrapper-viewer.svelte';
|
||||||
import ImagePanoramaViewer from './image-panorama-viewer.svelte';
|
|
||||||
|
|
||||||
type HasAsset = boolean;
|
type HasAsset = boolean;
|
||||||
|
|
||||||
@ -190,7 +190,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAssetUpdate = (assetUpdate: AssetResponseDto) => {
|
const onAssetUpdate = ({ asset: assetUpdate }: { event: 'upload' | 'update'; asset: AssetResponseDto }) => {
|
||||||
if (assetUpdate.id === asset.id) {
|
if (assetUpdate.id === asset.id) {
|
||||||
asset = assetUpdate;
|
asset = assetUpdate;
|
||||||
}
|
}
|
||||||
@ -198,8 +198,8 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
unsubscribes.push(
|
unsubscribes.push(
|
||||||
websocketEvents.on('on_upload_success', onAssetUpdate),
|
websocketEvents.on('on_upload_success', (asset) => onAssetUpdate({ event: 'upload', asset })),
|
||||||
websocketEvents.on('on_asset_update', onAssetUpdate),
|
websocketEvents.on('on_asset_update', (asset) => onAssetUpdate({ event: 'update', asset })),
|
||||||
);
|
);
|
||||||
|
|
||||||
slideshowStateUnsubscribe = slideshowState.subscribe((value) => {
|
slideshowStateUnsubscribe = slideshowState.subscribe((value) => {
|
||||||
@ -377,6 +377,7 @@
|
|||||||
case AssetAction.KEEP_THIS_DELETE_OTHERS:
|
case AssetAction.KEEP_THIS_DELETE_OTHERS:
|
||||||
case AssetAction.UNSTACK: {
|
case AssetAction.UNSTACK: {
|
||||||
closeViewer();
|
closeViewer();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -483,7 +484,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<VideoViewer
|
<VideoViewer
|
||||||
assetId={previewStackedAsset.id}
|
assetId={previewStackedAsset.id}
|
||||||
checksum={previewStackedAsset.checksum}
|
cacheKey={previewStackedAsset.thumbhash}
|
||||||
projectionType={previewStackedAsset.exifInfo?.projectionType}
|
projectionType={previewStackedAsset.exifInfo?.projectionType}
|
||||||
loopVideo={true}
|
loopVideo={true}
|
||||||
onPreviousAsset={() => navigateAsset('previous')}
|
onPreviousAsset={() => navigateAsset('previous')}
|
||||||
@ -500,7 +501,7 @@
|
|||||||
{#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
|
{#if shouldPlayMotionPhoto && asset.livePhotoVideoId}
|
||||||
<VideoViewer
|
<VideoViewer
|
||||||
assetId={asset.livePhotoVideoId}
|
assetId={asset.livePhotoVideoId}
|
||||||
checksum={asset.checksum}
|
cacheKey={asset.thumbhash}
|
||||||
projectionType={asset.exifInfo?.projectionType}
|
projectionType={asset.exifInfo?.projectionType}
|
||||||
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
||||||
onPreviousAsset={() => navigateAsset('previous')}
|
onPreviousAsset={() => navigateAsset('previous')}
|
||||||
@ -529,7 +530,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<VideoViewer
|
<VideoViewer
|
||||||
assetId={asset.id}
|
assetId={asset.id}
|
||||||
checksum={asset.checksum}
|
cacheKey={asset.thumbhash}
|
||||||
projectionType={asset.exifInfo?.projectionType}
|
projectionType={asset.exifInfo?.projectionType}
|
||||||
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
|
||||||
onPreviousAsset={() => navigateAsset('previous')}
|
onPreviousAsset={() => navigateAsset('previous')}
|
||||||
|
@ -50,7 +50,7 @@
|
|||||||
img = new Image();
|
img = new Image();
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
img.src = getAssetOriginalUrl({ id: asset.id, checksum: asset.checksum });
|
img.src = getAssetOriginalUrl({ id: asset.id, cacheKey: asset.thumbhash });
|
||||||
|
|
||||||
img.addEventListener('load', () => onImageLoad(true));
|
img.addEventListener('load', () => onImageLoad(true));
|
||||||
img.addEventListener('error', (error) => {
|
img.addEventListener('error', (error) => {
|
||||||
|
@ -40,7 +40,7 @@ describe('PhotoViewer component', () => {
|
|||||||
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
size: AssetMediaSize.Preview,
|
size: AssetMediaSize.Preview,
|
||||||
checksum: asset.checksum,
|
cacheKey: asset.thumbhash,
|
||||||
});
|
});
|
||||||
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
@ -50,7 +50,7 @@ describe('PhotoViewer component', () => {
|
|||||||
render(PhotoViewer, { asset });
|
render(PhotoViewer, { asset });
|
||||||
|
|
||||||
expect(getAssetThumbnailUrlSpy).not.toBeCalled();
|
expect(getAssetThumbnailUrlSpy).not.toBeCalled();
|
||||||
expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, checksum: asset.checksum });
|
expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, cacheKey: asset.thumbhash });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads original for shared link when download permission is true and showMetadata permission is true', () => {
|
it('loads original for shared link when download permission is true and showMetadata permission is true', () => {
|
||||||
@ -59,7 +59,7 @@ describe('PhotoViewer component', () => {
|
|||||||
render(PhotoViewer, { asset, sharedLink });
|
render(PhotoViewer, { asset, sharedLink });
|
||||||
|
|
||||||
expect(getAssetThumbnailUrlSpy).not.toBeCalled();
|
expect(getAssetThumbnailUrlSpy).not.toBeCalled();
|
||||||
expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, checksum: asset.checksum });
|
expect(getAssetOriginalUrlSpy).toBeCalledWith({ id: asset.id, cacheKey: asset.thumbhash });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('not loads original image when shared link download permission is false', () => {
|
it('not loads original image when shared link download permission is false', () => {
|
||||||
@ -70,7 +70,7 @@ describe('PhotoViewer component', () => {
|
|||||||
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
size: AssetMediaSize.Preview,
|
size: AssetMediaSize.Preview,
|
||||||
checksum: asset.checksum,
|
cacheKey: asset.thumbhash,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
||||||
@ -84,7 +84,7 @@ describe('PhotoViewer component', () => {
|
|||||||
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
expect(getAssetThumbnailUrlSpy).toBeCalledWith({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
size: AssetMediaSize.Preview,
|
size: AssetMediaSize.Preview,
|
||||||
checksum: asset.checksum,
|
cacheKey: asset.thumbhash,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
expect(getAssetOriginalUrlSpy).not.toBeCalled();
|
||||||
|
@ -70,19 +70,19 @@
|
|||||||
for (const preloadAsset of preloadAssets || []) {
|
for (const preloadAsset of preloadAssets || []) {
|
||||||
if (preloadAsset.type === AssetTypeEnum.Image) {
|
if (preloadAsset.type === AssetTypeEnum.Image) {
|
||||||
let img = new Image();
|
let img = new Image();
|
||||||
img.src = getAssetUrl(preloadAsset.id, useOriginal, preloadAsset.checksum);
|
img.src = getAssetUrl(preloadAsset.id, useOriginal, preloadAsset.thumbhash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getAssetUrl = (id: string, useOriginal: boolean, checksum: string) => {
|
const getAssetUrl = (id: string, useOriginal: boolean, cacheKey: string | null) => {
|
||||||
if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) {
|
if (sharedLink && (!sharedLink.allowDownload || !sharedLink.showMetadata)) {
|
||||||
return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum });
|
return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, cacheKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
return useOriginal
|
return useOriginal
|
||||||
? getAssetOriginalUrl({ id, checksum })
|
? getAssetOriginalUrl({ id, cacheKey })
|
||||||
: getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum });
|
: getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, cacheKey });
|
||||||
};
|
};
|
||||||
|
|
||||||
copyImage = async () => {
|
copyImage = async () => {
|
||||||
@ -158,7 +158,7 @@
|
|||||||
preload(useOriginalImage, preloadAssets);
|
preload(useOriginalImage, preloadAssets);
|
||||||
});
|
});
|
||||||
|
|
||||||
let imageLoaderUrl = $derived(getAssetUrl(asset.id, useOriginalImage, asset.checksum));
|
let imageLoaderUrl = $derived(getAssetUrl(asset.id, useOriginalImage, asset.thumbhash));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window
|
<svelte:window
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
assetId: string;
|
assetId: string;
|
||||||
loopVideo: boolean;
|
loopVideo: boolean;
|
||||||
checksum: string;
|
cacheKey: string | null;
|
||||||
onPreviousAsset?: () => void;
|
onPreviousAsset?: () => void;
|
||||||
onNextAsset?: () => void;
|
onNextAsset?: () => void;
|
||||||
onVideoEnded?: () => void;
|
onVideoEnded?: () => void;
|
||||||
@ -24,7 +24,7 @@
|
|||||||
let {
|
let {
|
||||||
assetId,
|
assetId,
|
||||||
loopVideo,
|
loopVideo,
|
||||||
checksum,
|
cacheKey,
|
||||||
onPreviousAsset = () => {},
|
onPreviousAsset = () => {},
|
||||||
onNextAsset = () => {},
|
onNextAsset = () => {},
|
||||||
onVideoEnded = () => {},
|
onVideoEnded = () => {},
|
||||||
@ -39,7 +39,7 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (videoPlayer) {
|
if (videoPlayer) {
|
||||||
assetFileUrl = getAssetPlaybackUrl({ id: assetId, checksum });
|
assetFileUrl = getAssetPlaybackUrl({ id: assetId, cacheKey });
|
||||||
forceMuted = false;
|
forceMuted = false;
|
||||||
videoPlayer.load();
|
videoPlayer.load();
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@
|
|||||||
onclose={() => onClose()}
|
onclose={() => onClose()}
|
||||||
muted={forceMuted || $videoViewerMuted}
|
muted={forceMuted || $videoViewerMuted}
|
||||||
bind:volume={$videoViewerVolume}
|
bind:volume={$videoViewerVolume}
|
||||||
poster={getAssetThumbnailUrl({ id: assetId, size: AssetMediaSize.Preview, checksum })}
|
poster={getAssetThumbnailUrl({ id: assetId, size: AssetMediaSize.Preview, cacheKey })}
|
||||||
src={assetFileUrl}
|
src={assetFileUrl}
|
||||||
>
|
>
|
||||||
</video>
|
</video>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
assetId: string;
|
assetId: string;
|
||||||
projectionType: string | null | undefined;
|
projectionType: string | null | undefined;
|
||||||
checksum: string;
|
cacheKey: string | null;
|
||||||
loopVideo: boolean;
|
loopVideo: boolean;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
onPreviousAsset?: () => void;
|
onPreviousAsset?: () => void;
|
||||||
@ -18,7 +18,7 @@
|
|||||||
let {
|
let {
|
||||||
assetId,
|
assetId,
|
||||||
projectionType,
|
projectionType,
|
||||||
checksum,
|
cacheKey,
|
||||||
loopVideo,
|
loopVideo,
|
||||||
onPreviousAsset,
|
onPreviousAsset,
|
||||||
onClose,
|
onClose,
|
||||||
@ -33,7 +33,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<VideoNativeViewer
|
<VideoNativeViewer
|
||||||
{loopVideo}
|
{loopVideo}
|
||||||
{checksum}
|
{cacheKey}
|
||||||
{assetId}
|
{assetId}
|
||||||
{onPreviousAsset}
|
{onPreviousAsset}
|
||||||
{onNextAsset}
|
{onNextAsset}
|
||||||
|
@ -327,7 +327,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, checksum: asset.checksum })}
|
url={getAssetThumbnailUrl({ 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"
|
||||||
@ -339,7 +339,7 @@
|
|||||||
<div class="absolute top-0 h-full w-full">
|
<div class="absolute top-0 h-full w-full">
|
||||||
<VideoThumbnail
|
<VideoThumbnail
|
||||||
{assetStore}
|
{assetStore}
|
||||||
url={getAssetPlaybackUrl({ id: asset.id, checksum: asset.checksum })}
|
url={getAssetPlaybackUrl({ id: asset.id, cacheKey: asset.thumbhash })}
|
||||||
enablePlayback={mouseOver && $playVideoThumbnailOnHover}
|
enablePlayback={mouseOver && $playVideoThumbnailOnHover}
|
||||||
curve={selected}
|
curve={selected}
|
||||||
durationInSeconds={timeToSeconds(asset.duration)}
|
durationInSeconds={timeToSeconds(asset.duration)}
|
||||||
@ -352,7 +352,7 @@
|
|||||||
<div class="absolute top-0 h-full w-full">
|
<div class="absolute top-0 h-full w-full">
|
||||||
<VideoThumbnail
|
<VideoThumbnail
|
||||||
{assetStore}
|
{assetStore}
|
||||||
url={getAssetPlaybackUrl({ id: asset.livePhotoVideoId, checksum: asset.checksum })}
|
url={getAssetPlaybackUrl({ id: asset.livePhotoVideoId, cacheKey: asset.thumbhash })}
|
||||||
pauseIcon={mdiMotionPauseOutline}
|
pauseIcon={mdiMotionPauseOutline}
|
||||||
playIcon={mdiMotionPlayOutline}
|
playIcon={mdiMotionPlayOutline}
|
||||||
showTime={false}
|
showTime={false}
|
||||||
|
@ -180,28 +180,30 @@ const createUrl = (path: string, parameters?: Record<string, unknown>) => {
|
|||||||
return getBaseUrl() + url.pathname + url.search + url.hash;
|
return getBaseUrl() + url.pathname + url.search + url.hash;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAssetOriginalUrl = (options: string | { id: string; checksum?: string }) => {
|
type AssetUrlOptions = { id: string; cacheKey?: string | null };
|
||||||
|
|
||||||
|
export const getAssetOriginalUrl = (options: string | AssetUrlOptions) => {
|
||||||
if (typeof options === 'string') {
|
if (typeof options === 'string') {
|
||||||
options = { id: options };
|
options = { id: options };
|
||||||
}
|
}
|
||||||
const { id, checksum } = options;
|
const { id, cacheKey } = options;
|
||||||
return createUrl(getAssetOriginalPath(id), { key: getKey(), c: checksum });
|
return createUrl(getAssetOriginalPath(id), { key: getKey(), c: cacheKey });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAssetThumbnailUrl = (options: string | { id: string; size?: AssetMediaSize; checksum?: string }) => {
|
export const getAssetThumbnailUrl = (options: string | (AssetUrlOptions & { size?: AssetMediaSize })) => {
|
||||||
if (typeof options === 'string') {
|
if (typeof options === 'string') {
|
||||||
options = { id: options };
|
options = { id: options };
|
||||||
}
|
}
|
||||||
const { id, size, checksum } = options;
|
const { id, size, cacheKey } = options;
|
||||||
return createUrl(getAssetThumbnailPath(id), { size, key: getKey(), c: checksum });
|
return createUrl(getAssetThumbnailPath(id), { size, key: getKey(), c: cacheKey });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAssetPlaybackUrl = (options: string | { id: string; checksum?: string }) => {
|
export const getAssetPlaybackUrl = (options: string | AssetUrlOptions) => {
|
||||||
if (typeof options === 'string') {
|
if (typeof options === 'string') {
|
||||||
options = { id: options };
|
options = { id: options };
|
||||||
}
|
}
|
||||||
const { id, checksum } = options;
|
const { id, cacheKey } = options;
|
||||||
return createUrl(getAssetPlaybackPath(id), { key: getKey(), c: checksum });
|
return createUrl(getAssetPlaybackPath(id), { key: getKey(), c: cacheKey });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getProfileImageUrl = (user: UserResponseDto) =>
|
export const getProfileImageUrl = (user: UserResponseDto) =>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user