mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	fix(web): prevent thumbhashes from covering search bar (#20720)
The thumbhash had a z-index setting which meant it would cover the search bar, and would always cause weird animations when scrolling up in search results. This is fixable by removing the z-index and moving it in front the other elements to get a naturally higher higher z-index preference.
This commit is contained in:
		
							parent
							
								
									f1c494ef97
								
							
						
					
					
						commit
						f36efd128b
					
				@ -1,22 +0,0 @@
 | 
				
			|||||||
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
 | 
					 | 
				
			||||||
import { render } from '@testing-library/svelte';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
describe('ImageThumbnail component', () => {
 | 
					 | 
				
			||||||
  beforeAll(() => {
 | 
					 | 
				
			||||||
    Element.prototype.animate = vi.fn().mockImplementation(() => ({
 | 
					 | 
				
			||||||
      cancel: () => {},
 | 
					 | 
				
			||||||
    }));
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  it('shows thumbhash while image is loading', () => {
 | 
					 | 
				
			||||||
    const sut = render(ImageThumbnail, {
 | 
					 | 
				
			||||||
      url: 'http://localhost/img.png',
 | 
					 | 
				
			||||||
      altText: 'test',
 | 
					 | 
				
			||||||
      base64ThumbHash: '1QcSHQRnh493V4dIh4eXh1h4kJUI',
 | 
					 | 
				
			||||||
      widthStyle: '250px',
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const thumbhash = sut.getByTestId('thumbhash');
 | 
					 | 
				
			||||||
    expect(thumbhash).not.toBeFalsy();
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@ -45,4 +45,15 @@ 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();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +1,10 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { thumbhash } from '$lib/actions/thumbhash';
 | 
					 | 
				
			||||||
  import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
 | 
					  import BrokenAsset from '$lib/components/assets/broken-asset.svelte';
 | 
				
			||||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
					  import Icon from '$lib/components/elements/icon.svelte';
 | 
				
			||||||
  import { cancelImageUrl } from '$lib/utils/sw-messaging';
 | 
					  import { cancelImageUrl } from '$lib/utils/sw-messaging';
 | 
				
			||||||
  import { TUNABLES } from '$lib/utils/tunables';
 | 
					 | 
				
			||||||
  import { mdiEyeOffOutline } from '@mdi/js';
 | 
					  import { mdiEyeOffOutline } from '@mdi/js';
 | 
				
			||||||
  import type { ActionReturn } from 'svelte/action';
 | 
					  import type { ActionReturn } from 'svelte/action';
 | 
				
			||||||
  import type { ClassValue } from 'svelte/elements';
 | 
					  import type { ClassValue } from 'svelte/elements';
 | 
				
			||||||
  import { fade } from 'svelte/transition';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  interface Props {
 | 
					  interface Props {
 | 
				
			||||||
    url: string;
 | 
					    url: string;
 | 
				
			||||||
@ -15,7 +12,6 @@
 | 
				
			|||||||
    title?: string | null;
 | 
					    title?: string | null;
 | 
				
			||||||
    heightStyle?: string | undefined;
 | 
					    heightStyle?: string | undefined;
 | 
				
			||||||
    widthStyle: string;
 | 
					    widthStyle: string;
 | 
				
			||||||
    base64ThumbHash?: string | null;
 | 
					 | 
				
			||||||
    curve?: boolean;
 | 
					    curve?: boolean;
 | 
				
			||||||
    shadow?: boolean;
 | 
					    shadow?: boolean;
 | 
				
			||||||
    circle?: boolean;
 | 
					    circle?: boolean;
 | 
				
			||||||
@ -33,7 +29,6 @@
 | 
				
			|||||||
    title = null,
 | 
					    title = null,
 | 
				
			||||||
    heightStyle = undefined,
 | 
					    heightStyle = undefined,
 | 
				
			||||||
    widthStyle,
 | 
					    widthStyle,
 | 
				
			||||||
    base64ThumbHash = null,
 | 
					 | 
				
			||||||
    curve = false,
 | 
					    curve = false,
 | 
				
			||||||
    shadow = false,
 | 
					    shadow = false,
 | 
				
			||||||
    circle = false,
 | 
					    circle = false,
 | 
				
			||||||
@ -45,10 +40,6 @@
 | 
				
			|||||||
    brokenAssetClass = '',
 | 
					    brokenAssetClass = '',
 | 
				
			||||||
  }: Props = $props();
 | 
					  }: Props = $props();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let {
 | 
					 | 
				
			||||||
    IMAGE_THUMBNAIL: { THUMBHASH_FADE_DURATION },
 | 
					 | 
				
			||||||
  } = TUNABLES;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let loaded = $state(false);
 | 
					  let loaded = $state(false);
 | 
				
			||||||
  let errored = $state(false);
 | 
					  let errored = $state(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -100,7 +91,6 @@
 | 
				
			|||||||
    alt={loaded || errored ? altText : ''}
 | 
					    alt={loaded || errored ? altText : ''}
 | 
				
			||||||
    {title}
 | 
					    {title}
 | 
				
			||||||
    class={['object-cover', optionalClasses, imageClass]}
 | 
					    class={['object-cover', optionalClasses, imageClass]}
 | 
				
			||||||
    class:opacity-0={!thumbhash && !loaded}
 | 
					 | 
				
			||||||
    draggable="false"
 | 
					    draggable="false"
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
@ -110,19 +100,3 @@
 | 
				
			|||||||
    <Icon {title} path={mdiEyeOffOutline} size="2em" class={hiddenIconClass} />
 | 
					    <Icon {title} path={mdiEyeOffOutline} size="2em" class={hiddenIconClass} />
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
					 | 
				
			||||||
{#if base64ThumbHash && (!loaded || errored)}
 | 
					 | 
				
			||||||
  <canvas
 | 
					 | 
				
			||||||
    use:thumbhash={{ base64ThumbHash }}
 | 
					 | 
				
			||||||
    data-testid="thumbhash"
 | 
					 | 
				
			||||||
    style:width={widthStyle}
 | 
					 | 
				
			||||||
    style:height={heightStyle}
 | 
					 | 
				
			||||||
    {title}
 | 
					 | 
				
			||||||
    class="absolute top-0 object-cover"
 | 
					 | 
				
			||||||
    class:rounded-xl={curve}
 | 
					 | 
				
			||||||
    class:shadow-lg={shadow}
 | 
					 | 
				
			||||||
    class:rounded-full={circle}
 | 
					 | 
				
			||||||
    draggable="false"
 | 
					 | 
				
			||||||
    out:fade={{ duration: THUMBHASH_FADE_DURATION }}
 | 
					 | 
				
			||||||
  ></canvas>
 | 
					 | 
				
			||||||
{/if}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -230,15 +230,6 @@
 | 
				
			|||||||
    ]}
 | 
					    ]}
 | 
				
			||||||
    data-outline
 | 
					    data-outline
 | 
				
			||||||
  ></div>
 | 
					  ></div>
 | 
				
			||||||
  {#if (!loaded || thumbError) && asset.thumbhash}
 | 
					 | 
				
			||||||
    <canvas
 | 
					 | 
				
			||||||
      use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
 | 
					 | 
				
			||||||
      class="absolute object-cover z-1"
 | 
					 | 
				
			||||||
      style:width="{width}px"
 | 
					 | 
				
			||||||
      style:height="{height}px"
 | 
					 | 
				
			||||||
      out:fade={{ duration: THUMBHASH_FADE_DURATION }}
 | 
					 | 
				
			||||||
    ></canvas>
 | 
					 | 
				
			||||||
  {/if}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class={['group absolute -top-[0px] -bottom-[0px]', { 'cursor-not-allowed': disabled, 'cursor-pointer': !disabled }]}
 | 
					    class={['group absolute -top-[0px] -bottom-[0px]', { 'cursor-not-allowed': disabled, 'cursor-pointer': !disabled }]}
 | 
				
			||||||
@ -352,7 +343,21 @@
 | 
				
			|||||||
          />
 | 
					          />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      {/if}
 | 
					      {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {#if (!loaded || thumbError) && asset.thumbhash}
 | 
				
			||||||
 | 
					        <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}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    {#if selectionCandidate}
 | 
					    {#if selectionCandidate}
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        class="absolute top-0 h-full w-full bg-immich-primary opacity-40"
 | 
					        class="absolute top-0 h-full w-full bg-immich-primary opacity-40"
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user