From f386b4d3771c57560d5d8f973cfb695433703999 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Sat, 15 Feb 2025 22:34:13 -0500 Subject: [PATCH] feat(web): use thumbhash as a cache key (#16106) Co-authored-by: Alex --- .../asset-viewer/asset-viewer.svelte | 15 +++++++------- .../editor/crop-tool/crop-area.svelte | 2 +- .../asset-viewer/photo-viewer.spec.ts | 10 +++++----- .../asset-viewer/photo-viewer.svelte | 12 +++++------ .../asset-viewer/video-native-viewer.svelte | 8 ++++---- .../asset-viewer/video-wrapper-viewer.svelte | 6 +++--- .../assets/thumbnail/thumbnail.svelte | 6 +++--- web/src/lib/utils.ts | 20 ++++++++++--------- 8 files changed, 41 insertions(+), 38 deletions(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index ea5d6e9275..5d5158b9d1 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -43,10 +43,10 @@ import DetailPanel from './detail-panel.svelte'; import CropArea from './editor/crop-tool/crop-area.svelte'; import EditorPanel from './editor/editor-panel.svelte'; + import ImagePanoramaViewer from './image-panorama-viewer.svelte'; import PhotoViewer from './photo-viewer.svelte'; import SlideshowBar from './slideshow-bar.svelte'; import VideoViewer from './video-wrapper-viewer.svelte'; - import ImagePanoramaViewer from './image-panorama-viewer.svelte'; 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) { asset = assetUpdate; } @@ -198,8 +198,8 @@ onMount(async () => { unsubscribes.push( - websocketEvents.on('on_upload_success', onAssetUpdate), - websocketEvents.on('on_asset_update', onAssetUpdate), + websocketEvents.on('on_upload_success', (asset) => onAssetUpdate({ event: 'upload', asset })), + websocketEvents.on('on_asset_update', (asset) => onAssetUpdate({ event: 'update', asset })), ); slideshowStateUnsubscribe = slideshowState.subscribe((value) => { @@ -377,6 +377,7 @@ case AssetAction.KEEP_THIS_DELETE_OTHERS: case AssetAction.UNSTACK: { closeViewer(); + break; } } @@ -483,7 +484,7 @@ {:else} navigateAsset('previous')} @@ -500,7 +501,7 @@ {#if shouldPlayMotionPhoto && asset.livePhotoVideoId} navigateAsset('previous')} @@ -529,7 +530,7 @@ {:else} navigateAsset('previous')} diff --git a/web/src/lib/components/asset-viewer/editor/crop-tool/crop-area.svelte b/web/src/lib/components/asset-viewer/editor/crop-tool/crop-area.svelte index 028074bc02..91fbd61e85 100644 --- a/web/src/lib/components/asset-viewer/editor/crop-tool/crop-area.svelte +++ b/web/src/lib/components/asset-viewer/editor/crop-tool/crop-area.svelte @@ -50,7 +50,7 @@ img = new Image(); 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('error', (error) => { diff --git a/web/src/lib/components/asset-viewer/photo-viewer.spec.ts b/web/src/lib/components/asset-viewer/photo-viewer.spec.ts index e1372e37da..31b690ad0c 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.spec.ts +++ b/web/src/lib/components/asset-viewer/photo-viewer.spec.ts @@ -40,7 +40,7 @@ describe('PhotoViewer component', () => { expect(getAssetThumbnailUrlSpy).toBeCalledWith({ id: asset.id, size: AssetMediaSize.Preview, - checksum: asset.checksum, + cacheKey: asset.thumbhash, }); expect(getAssetOriginalUrlSpy).not.toBeCalled(); }); @@ -50,7 +50,7 @@ describe('PhotoViewer component', () => { render(PhotoViewer, { asset }); 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', () => { @@ -59,7 +59,7 @@ describe('PhotoViewer component', () => { render(PhotoViewer, { asset, sharedLink }); 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', () => { @@ -70,7 +70,7 @@ describe('PhotoViewer component', () => { expect(getAssetThumbnailUrlSpy).toBeCalledWith({ id: asset.id, size: AssetMediaSize.Preview, - checksum: asset.checksum, + cacheKey: asset.thumbhash, }); expect(getAssetOriginalUrlSpy).not.toBeCalled(); @@ -84,7 +84,7 @@ describe('PhotoViewer component', () => { expect(getAssetThumbnailUrlSpy).toBeCalledWith({ id: asset.id, size: AssetMediaSize.Preview, - checksum: asset.checksum, + cacheKey: asset.thumbhash, }); expect(getAssetOriginalUrlSpy).not.toBeCalled(); diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index bad8d3c404..2e0355611b 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -70,19 +70,19 @@ for (const preloadAsset of preloadAssets || []) { if (preloadAsset.type === AssetTypeEnum.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)) { - return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum }); + return getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, cacheKey }); } return useOriginal - ? getAssetOriginalUrl({ id, checksum }) - : getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, checksum }); + ? getAssetOriginalUrl({ id, cacheKey }) + : getAssetThumbnailUrl({ id, size: AssetMediaSize.Preview, cacheKey }); }; copyImage = async () => { @@ -158,7 +158,7 @@ preload(useOriginalImage, preloadAssets); }); - let imageLoaderUrl = $derived(getAssetUrl(asset.id, useOriginalImage, asset.checksum)); + let imageLoaderUrl = $derived(getAssetUrl(asset.id, useOriginalImage, asset.thumbhash)); void; onNextAsset?: () => void; onVideoEnded?: () => void; @@ -24,7 +24,7 @@ let { assetId, loopVideo, - checksum, + cacheKey, onPreviousAsset = () => {}, onNextAsset = () => {}, onVideoEnded = () => {}, @@ -39,7 +39,7 @@ onMount(() => { if (videoPlayer) { - assetFileUrl = getAssetPlaybackUrl({ id: assetId, checksum }); + assetFileUrl = getAssetPlaybackUrl({ id: assetId, cacheKey }); forceMuted = false; videoPlayer.load(); } @@ -106,7 +106,7 @@ onclose={() => onClose()} muted={forceMuted || $videoViewerMuted} bind:volume={$videoViewerVolume} - poster={getAssetThumbnailUrl({ id: assetId, size: AssetMediaSize.Preview, checksum })} + poster={getAssetThumbnailUrl({ id: assetId, size: AssetMediaSize.Preview, cacheKey })} src={assetFileUrl} > diff --git a/web/src/lib/components/asset-viewer/video-wrapper-viewer.svelte b/web/src/lib/components/asset-viewer/video-wrapper-viewer.svelte index 7ee21e59a2..a5a94d85d4 100644 --- a/web/src/lib/components/asset-viewer/video-wrapper-viewer.svelte +++ b/web/src/lib/components/asset-viewer/video-wrapper-viewer.svelte @@ -6,7 +6,7 @@ interface Props { assetId: string; projectionType: string | null | undefined; - checksum: string; + cacheKey: string | null; loopVideo: boolean; onClose?: () => void; onPreviousAsset?: () => void; @@ -18,7 +18,7 @@ let { assetId, projectionType, - checksum, + cacheKey, loopVideo, onPreviousAsset, onClose, @@ -33,7 +33,7 @@ {:else} ) => { 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') { options = { id: options }; } - const { id, checksum } = options; - return createUrl(getAssetOriginalPath(id), { key: getKey(), c: checksum }); + const { id, cacheKey } = options; + 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') { options = { id: options }; } - const { id, size, checksum } = options; - return createUrl(getAssetThumbnailPath(id), { size, key: getKey(), c: checksum }); + const { id, size, cacheKey } = options; + 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') { options = { id: options }; } - const { id, checksum } = options; - return createUrl(getAssetPlaybackPath(id), { key: getKey(), c: checksum }); + const { id, cacheKey } = options; + return createUrl(getAssetPlaybackPath(id), { key: getKey(), c: cacheKey }); }; export const getProfileImageUrl = (user: UserResponseDto) =>