This commit is contained in:
Thomas 2025-10-16 17:59:12 -04:00 committed by GitHub
commit f6e4543796
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 52 additions and 24 deletions

View file

@ -45,15 +45,4 @@ describe('Thumbnail component', () => {
const tabbables = getTabbable(container!); const tabbables = getTabbable(container!);
expect(tabbables.length).toBe(0); expect(tabbables.length).toBe(0);
}); });
it('shows thumbhash while image is loading', () => {
const asset = assetFactory.build({ originalPath: 'image.jpg', originalMimeType: 'image/jpeg' });
const sut = render(Thumbnail, {
asset,
selected: true,
});
const thumbhash = sut.getByTestId('thumbhash');
expect(thumbhash).not.toBeFalsy();
});
}); });

View file

@ -19,6 +19,7 @@
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { mobileDevice } from '$lib/stores/mobile-device.svelte'; import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { isCached } from '$lib/utils/cache';
import { moveFocus } from '$lib/utils/focus-util'; import { moveFocus } from '$lib/utils/focus-util';
import { currentUrlReplaceAssetId } from '$lib/utils/navigation'; import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
import { TUNABLES } from '$lib/utils/tunables'; import { TUNABLES } from '$lib/utils/tunables';
@ -75,6 +76,12 @@
IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION }, IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION },
} = TUNABLES; } = TUNABLES;
const thumbnailURL = getAssetThumbnailUrl({
id: asset.id,
size: AssetMediaSize.Thumbnail,
cacheKey: asset.thumbhash,
});
let usingMobileDevice = $derived(mobileDevice.pointerCoarse); let usingMobileDevice = $derived(mobileDevice.pointerCoarse);
let element: HTMLElement | undefined = $state(); let element: HTMLElement | undefined = $state();
let mouseOver = $state(false); let mouseOver = $state(false);
@ -313,7 +320,7 @@
<ImageThumbnail <ImageThumbnail
class={imageClass} class={imageClass}
{brokenAssetClass} {brokenAssetClass}
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })} url={thumbnailURL}
altText={$getAltText(asset)} altText={$getAltText(asset)}
widthStyle="{width}px" widthStyle="{width}px"
heightStyle="{height}px" heightStyle="{height}px"
@ -344,17 +351,31 @@
</div> </div>
{/if} {/if}
{#if (!loaded || thumbError) && asset.thumbhash} {#if asset.thumbhash}
<canvas {#await isCached(new Request(thumbnailURL))}
use:thumbhash={{ base64ThumbHash: asset.thumbhash }} <canvas
data-testid="thumbhash" use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
class="absolute top-0 object-cover" data-testid="thumbhash"
style:width="{width}px" class="absolute top-0 object-cover"
style:height="{height}px" style:width="{width}px"
class:rounded-xl={selected} style:height="{height}px"
draggable="false" class:rounded-xl={selected}
out:fade={{ duration: THUMBHASH_FADE_DURATION }} draggable="false"
></canvas> ></canvas>
{:then cached}
{#if !cached && !loaded && !thumbError}
<canvas
use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
data-testid="thumbhash"
class="absolute top-0 object-cover"
style:width="{width}px"
style:height="{height}px"
class:rounded-xl={selected}
draggable="false"
out:fade={{ duration: THUMBHASH_FADE_DURATION }}
></canvas>
{/if}
{/await}
{/if} {/if}
</div> </div>

View file

@ -0,0 +1,18 @@
let cache: Cache | undefined;
const getCache = async () => {
cache ||= await openCache();
return cache;
};
const openCache = async () => {
const [key] = await caches.keys();
if (key) {
return caches.open(key);
}
};
export const isCached = async (req: Request) => {
const cache = await getCache();
return !!(await cache?.match(req));
};

View file

@ -29,6 +29,6 @@ export const TUNABLES = {
NAVIGATE_ON_ASSET_IN_VIEW: getBoolean(storage.getItem('ASSET_GRID.NAVIGATE_ON_ASSET_IN_VIEW'), false), NAVIGATE_ON_ASSET_IN_VIEW: getBoolean(storage.getItem('ASSET_GRID.NAVIGATE_ON_ASSET_IN_VIEW'), false),
}, },
IMAGE_THUMBNAIL: { IMAGE_THUMBNAIL: {
THUMBHASH_FADE_DURATION: getNumber(storage.getItem('THUMBHASH_FADE_DURATION'), 100), THUMBHASH_FADE_DURATION: getNumber(storage.getItem('THUMBHASH_FADE_DURATION'), 1000),
}, },
}; };