feat: show thumbhash behind load error, if possible (#17554)

* feat: show thumbhash behind load error, if possible

* forgot this
This commit is contained in:
Min Idzelis 2025-04-11 18:01:51 -04:00 committed by GitHub
parent 40e3322b25
commit c62fc155c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 20 additions and 10 deletions

View File

@ -595,7 +595,7 @@
id="stack-slideshow" id="stack-slideshow"
class="z-[1002] flex place-item-center place-content-center absolute bottom-0 w-full col-span-4 col-start-1 overflow-x-auto horizontal-scrollbar" class="z-[1002] flex place-item-center place-content-center absolute bottom-0 w-full col-span-4 col-start-1 overflow-x-auto horizontal-scrollbar"
> >
<div class="relative w-full whitespace-nowrap"> <div class="relative w-full">
{#each stackedAssets as stackedAsset (stackedAsset.id)} {#each stackedAssets as stackedAsset (stackedAsset.id)}
<div <div
class={['inline-block px-1 relative transition-all pb-2']} class={['inline-block px-1 relative transition-all pb-2']}
@ -603,6 +603,7 @@
> >
<Thumbnail <Thumbnail
imageClass={{ 'border-2 border-white': stackedAsset.id === asset.id }} imageClass={{ 'border-2 border-white': stackedAsset.id === asset.id }}
brokenAssetClass="text-xs"
dimmed={stackedAsset.id !== asset.id} dimmed={stackedAsset.id !== asset.id}
asset={stackedAsset} asset={stackedAsset}
onClick={(stackedAsset) => { onClick={(stackedAsset) => {

View File

@ -184,7 +184,9 @@
]} ]}
/> />
{#if imageError} {#if imageError}
<BrokenAsset class="text-xl" /> <div class="h-full w-full">
<BrokenAsset class="text-xl h-full w-full" />
</div>
{/if} {/if}
<!-- svelte-ignore a11y_missing_attribute --> <!-- svelte-ignore a11y_missing_attribute -->
<img bind:this={loader} style="display:none" src={imageLoaderUrl} aria-hidden="true" /> <img bind:this={loader} style="display:none" src={imageLoaderUrl} aria-hidden="true" />

View File

@ -14,7 +14,7 @@
</script> </script>
<div <div
class="flex flex-col overflow-hidden max-h-full max-w-full justify-center items-center bg-gray-100 dark:bg-gray-700 dark:text-gray-100 p-4 {className}" class="flex flex-col overflow-hidden max-h-full max-w-full justify-center items-center bg-gray-100/40 dark:bg-gray-700/40 dark:text-gray-100 p-4 {className}"
style:width style:width
style:height style:height
> >

View File

@ -21,7 +21,8 @@
border?: boolean; border?: boolean;
hiddenIconClass?: string; hiddenIconClass?: string;
class?: ClassValue; class?: ClassValue;
onComplete?: (() => void) | undefined; brokenAssetClass?: ClassValue;
onComplete?: ((errored: boolean) => void) | undefined;
} }
let { let {
@ -39,6 +40,7 @@
hiddenIconClass = 'text-white', hiddenIconClass = 'text-white',
onComplete = undefined, onComplete = undefined,
class: imageClass = '', class: imageClass = '',
brokenAssetClass = '',
}: Props = $props(); }: Props = $props();
let { let {
@ -50,17 +52,17 @@
const setLoaded = () => { const setLoaded = () => {
loaded = true; loaded = true;
onComplete?.(); onComplete?.(false);
}; };
const setErrored = () => { const setErrored = () => {
errored = true; errored = true;
onComplete?.(); onComplete?.(true);
}; };
function mount(elem: HTMLImageElement) { function mount(elem: HTMLImageElement) {
if (elem.complete) { if (elem.complete) {
loaded = true; loaded = true;
onComplete?.(); onComplete?.(false);
} }
} }
@ -71,6 +73,7 @@
shadow && 'shadow-lg', shadow && 'shadow-lg',
(circle || !heightStyle) && 'aspect-square', (circle || !heightStyle) && 'aspect-square',
border && 'border-[3px] border-immich-dark-primary/80 hover:border-immich-primary', border && 'border-[3px] border-immich-dark-primary/80 hover:border-immich-primary',
brokenAssetClass,
] ]
.filter(Boolean) .filter(Boolean)
.join(' '), .join(' '),

View File

@ -40,6 +40,7 @@
showArchiveIcon?: boolean; showArchiveIcon?: boolean;
showStackedIcon?: boolean; showStackedIcon?: boolean;
imageClass?: ClassValue; imageClass?: ClassValue;
brokenAssetClass?: ClassValue;
dimmed?: boolean; dimmed?: boolean;
onClick?: ((asset: AssetResponseDto) => void) | undefined; onClick?: ((asset: AssetResponseDto) => void) | undefined;
onSelect?: ((asset: AssetResponseDto) => void) | undefined; onSelect?: ((asset: AssetResponseDto) => void) | undefined;
@ -66,6 +67,7 @@
onMouseEvent = undefined, onMouseEvent = undefined,
handleFocus = undefined, handleFocus = undefined,
imageClass = '', imageClass = '',
brokenAssetClass = '',
dimmed = false, dimmed = false,
}: Props = $props(); }: Props = $props();
@ -77,6 +79,7 @@
let focussableElement: HTMLElement | undefined = $state(); let focussableElement: HTMLElement | undefined = $state();
let mouseOver = $state(false); let mouseOver = $state(false);
let loaded = $state(false); let loaded = $state(false);
let thumbError = $state(false);
$effect(() => { $effect(() => {
if (focussed && document.activeElement !== focussableElement) { if (focussed && document.activeElement !== focussableElement) {
@ -153,10 +156,10 @@
style:width="{width}px" style:width="{width}px"
style:height="{height}px" style:height="{height}px"
> >
{#if !loaded && asset.thumbhash} {#if (!loaded || thumbError) && asset.thumbhash}
<canvas <canvas
use:thumbhash={{ base64ThumbHash: asset.thumbhash }} use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
class="absolute object-cover z-10" class="absolute object-cover"
style:width="{width}px" style:width="{width}px"
style:height="{height}px" style:height="{height}px"
out:fade={{ duration: THUMBHASH_FADE_DURATION }} out:fade={{ duration: THUMBHASH_FADE_DURATION }}
@ -296,12 +299,13 @@
</div> </div>
<ImageThumbnail <ImageThumbnail
class={imageClass} class={imageClass}
{brokenAssetClass}
url={getAssetThumbnailUrl({ id: asset.id, size: AssetMediaSize.Thumbnail, cacheKey: asset.thumbhash })} 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"
curve={selected} curve={selected}
onComplete={() => (loaded = true)} onComplete={(errored) => ((loaded = true), (thumbError = errored))}
/> />
{#if asset.type === AssetTypeEnum.Video} {#if asset.type === AssetTypeEnum.Video}
<div class="absolute top-0 h-full w-full"> <div class="absolute top-0 h-full w-full">