mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	fix: gallery viewer sliding window offload assets (#17016)
* fix: gallery viewer sliding window offload assets * fix: update bottom sliding window * do not use negative * Calculate offset before gallery --------- Co-authored-by: Min Idzelis <min123@gmail.com>
This commit is contained in:
		
							parent
							
								
									21954939cf
								
							
						
					
					
						commit
						dbc279f843
					
				@ -67,9 +67,12 @@
 | 
				
			|||||||
  let paused = $state(false);
 | 
					  let paused = $state(false);
 | 
				
			||||||
  let current = $state<MemoryAsset | undefined>(undefined);
 | 
					  let current = $state<MemoryAsset | undefined>(undefined);
 | 
				
			||||||
  let isSaved = $derived(current?.memory.isSaved);
 | 
					  let isSaved = $derived(current?.memory.isSaved);
 | 
				
			||||||
 | 
					  let viewerHeight = $state(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { isViewing } = assetViewingStore;
 | 
					  const { isViewing } = assetViewingStore;
 | 
				
			||||||
  const viewport: Viewport = $state({ width: 0, height: 0 });
 | 
					  const viewport: Viewport = $state({ width: 0, height: 0 });
 | 
				
			||||||
 | 
					  // need to include padding in the viewport for gallery
 | 
				
			||||||
 | 
					  const galleryViewport: Viewport = $derived({ height: viewport.height, width: viewport.width - 32 });
 | 
				
			||||||
  const assetInteraction = new AssetInteraction();
 | 
					  const assetInteraction = new AssetInteraction();
 | 
				
			||||||
  let progressBarController: Tween<number> | undefined = $state(undefined);
 | 
					  let progressBarController: Tween<number> | undefined = $state(undefined);
 | 
				
			||||||
  let videoPlayer: HTMLVideoElement | undefined = $state();
 | 
					  let videoPlayer: HTMLVideoElement | undefined = $state();
 | 
				
			||||||
@ -331,7 +334,12 @@
 | 
				
			|||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<section id="memory-viewer" class="w-full bg-immich-dark-gray" bind:this={memoryWrapper}>
 | 
					<section
 | 
				
			||||||
 | 
					  id="memory-viewer"
 | 
				
			||||||
 | 
					  class="w-full bg-immich-dark-gray"
 | 
				
			||||||
 | 
					  bind:this={memoryWrapper}
 | 
				
			||||||
 | 
					  use:resizeObserver={({ height, width }) => ((viewport.height = height), (viewport.width = width))}
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
  {#if current}
 | 
					  {#if current}
 | 
				
			||||||
    <ControlAppBar onClose={() => goto(AppRoute.PHOTOS)} forceDark multiRow>
 | 
					    <ControlAppBar onClose={() => goto(AppRoute.PHOTOS)} forceDark multiRow>
 | 
				
			||||||
      {#snippet leading()}
 | 
					      {#snippet leading()}
 | 
				
			||||||
@ -386,7 +394,7 @@
 | 
				
			|||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
    <!-- Viewer -->
 | 
					    <!-- Viewer -->
 | 
				
			||||||
    <section class="overflow-hidden pt-32 md:pt-20">
 | 
					    <section class="overflow-hidden pt-32 md:pt-20" bind:clientHeight={viewerHeight}>
 | 
				
			||||||
      <div
 | 
					      <div
 | 
				
			||||||
        class="ml-[-100%] box-border flex h-[calc(100vh_-_224px)] md:h-[calc(100vh_-_180px)] w-[300%] items-center justify-center gap-10 overflow-hidden"
 | 
					        class="ml-[-100%] box-border flex h-[calc(100vh_-_224px)] md:h-[calc(100vh_-_180px)] w-[300%] items-center justify-center gap-10 overflow-hidden"
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
@ -580,43 +588,44 @@
 | 
				
			|||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </section>
 | 
					    </section>
 | 
				
			||||||
 | 
					 | 
				
			||||||
    <!-- GALLERY VIEWER -->
 | 
					 | 
				
			||||||
    <section class="bg-immich-dark-gray p-4">
 | 
					 | 
				
			||||||
      <div
 | 
					 | 
				
			||||||
        class="sticky mb-10 flex place-content-center place-items-center transition-all"
 | 
					 | 
				
			||||||
        class:opacity-0={galleryInView}
 | 
					 | 
				
			||||||
        class:opacity-100={!galleryInView}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <CircleIconButton
 | 
					 | 
				
			||||||
          title={$t('show_gallery')}
 | 
					 | 
				
			||||||
          icon={mdiChevronDown}
 | 
					 | 
				
			||||||
          color="light"
 | 
					 | 
				
			||||||
          onclick={() => memoryGallery?.scrollIntoView({ behavior: 'smooth' })}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <div
 | 
					 | 
				
			||||||
        id="gallery-memory"
 | 
					 | 
				
			||||||
        use:intersectionObserver={{
 | 
					 | 
				
			||||||
          onIntersect: handleGalleryScrollsIntoView,
 | 
					 | 
				
			||||||
          onSeparate: handleGalleryScrollsOutOfView,
 | 
					 | 
				
			||||||
          bottom: '-200px',
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
        use:resizeObserver={({ height, width }) => ((viewport.height = height), (viewport.width = width))}
 | 
					 | 
				
			||||||
        bind:this={memoryGallery}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <GalleryViewer
 | 
					 | 
				
			||||||
          onNext={handleNextAsset}
 | 
					 | 
				
			||||||
          onPrevious={handlePreviousAsset}
 | 
					 | 
				
			||||||
          assets={current.memory.assets}
 | 
					 | 
				
			||||||
          {viewport}
 | 
					 | 
				
			||||||
          {assetInteraction}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </section>
 | 
					 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
</section>
 | 
					</section>
 | 
				
			||||||
 | 
					{#if current}
 | 
				
			||||||
 | 
					  <!-- GALLERY VIEWER -->
 | 
				
			||||||
 | 
					  <section class="bg-immich-dark-gray p-4">
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      class="sticky mb-10 flex place-content-center place-items-center transition-all"
 | 
				
			||||||
 | 
					      class:opacity-0={galleryInView}
 | 
				
			||||||
 | 
					      class:opacity-100={!galleryInView}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <CircleIconButton
 | 
				
			||||||
 | 
					        title={$t('show_gallery')}
 | 
				
			||||||
 | 
					        icon={mdiChevronDown}
 | 
				
			||||||
 | 
					        color="light"
 | 
				
			||||||
 | 
					        onclick={() => memoryGallery?.scrollIntoView({ behavior: 'smooth' })}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      id="gallery-memory"
 | 
				
			||||||
 | 
					      use:intersectionObserver={{
 | 
				
			||||||
 | 
					        onIntersect: handleGalleryScrollsIntoView,
 | 
				
			||||||
 | 
					        onSeparate: handleGalleryScrollsOutOfView,
 | 
				
			||||||
 | 
					        bottom: '-200px',
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					      bind:this={memoryGallery}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <GalleryViewer
 | 
				
			||||||
 | 
					        onNext={handleNextAsset}
 | 
				
			||||||
 | 
					        onPrevious={handlePreviousAsset}
 | 
				
			||||||
 | 
					        assets={current.memory.assets}
 | 
				
			||||||
 | 
					        viewport={galleryViewport}
 | 
				
			||||||
 | 
					        {assetInteraction}
 | 
				
			||||||
 | 
					        slidingWindowOffset={viewerHeight}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </section>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<style>
 | 
					<style>
 | 
				
			||||||
  .main-view {
 | 
					  .main-view {
 | 
				
			||||||
 | 
				
			|||||||
@ -35,6 +35,8 @@
 | 
				
			|||||||
    onPrevious?: (() => Promise<AssetResponseDto | undefined>) | undefined;
 | 
					    onPrevious?: (() => Promise<AssetResponseDto | undefined>) | undefined;
 | 
				
			||||||
    onNext?: (() => Promise<AssetResponseDto | undefined>) | undefined;
 | 
					    onNext?: (() => Promise<AssetResponseDto | undefined>) | undefined;
 | 
				
			||||||
    onRandom?: (() => Promise<AssetResponseDto | undefined>) | undefined;
 | 
					    onRandom?: (() => Promise<AssetResponseDto | undefined>) | undefined;
 | 
				
			||||||
 | 
					    pageHeaderOffset?: number;
 | 
				
			||||||
 | 
					    slidingWindowOffset?: number;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let {
 | 
					  let {
 | 
				
			||||||
@ -49,6 +51,8 @@
 | 
				
			|||||||
    onPrevious = undefined,
 | 
					    onPrevious = undefined,
 | 
				
			||||||
    onNext = undefined,
 | 
					    onNext = undefined,
 | 
				
			||||||
    onRandom = undefined,
 | 
					    onRandom = undefined,
 | 
				
			||||||
 | 
					    slidingWindowOffset = 0,
 | 
				
			||||||
 | 
					    pageHeaderOffset = 0,
 | 
				
			||||||
  }: Props = $props();
 | 
					  }: Props = $props();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let { isViewing: isViewerOpen, asset: viewingAsset, setAsset } = assetViewingStore;
 | 
					  let { isViewing: isViewerOpen, asset: viewingAsset, setAsset } = assetViewingStore;
 | 
				
			||||||
@ -86,7 +90,7 @@
 | 
				
			|||||||
          height: geometry.getHeight(i),
 | 
					          height: geometry.getHeight(i),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        // 54 is the content height of the asset-selection-app-bar
 | 
					        // 54 is the content height of the asset-selection-app-bar
 | 
				
			||||||
        const layoutTopWithOffset = layout.top + 54;
 | 
					        const layoutTopWithOffset = layout.top + pageHeaderOffset;
 | 
				
			||||||
        const layoutBottom = layoutTopWithOffset + layout.height;
 | 
					        const layoutBottom = layoutTopWithOffset + layout.height;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const display = layoutTopWithOffset < slidingWindow.bottom && layoutBottom > slidingWindow.top;
 | 
					        const display = layoutTopWithOffset < slidingWindow.bottom && layoutBottom > slidingWindow.top;
 | 
				
			||||||
@ -109,7 +113,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const updateSlidingWindow = () => {
 | 
					  const updateSlidingWindow = () => {
 | 
				
			||||||
    const v = $state.snapshot(viewport);
 | 
					    const v = $state.snapshot(viewport);
 | 
				
			||||||
    const top = document.scrollingElement?.scrollTop || 0;
 | 
					    const top = (document.scrollingElement?.scrollTop || 0) - slidingWindowOffset;
 | 
				
			||||||
    const bottom = top + v.height;
 | 
					    const bottom = top + v.height;
 | 
				
			||||||
    const w = {
 | 
					    const w = {
 | 
				
			||||||
      top,
 | 
					      top,
 | 
				
			||||||
 | 
				
			|||||||
@ -154,7 +154,13 @@
 | 
				
			|||||||
    <!-- Assets -->
 | 
					    <!-- Assets -->
 | 
				
			||||||
    {#if data.pathAssets && data.pathAssets.length > 0}
 | 
					    {#if data.pathAssets && data.pathAssets.length > 0}
 | 
				
			||||||
      <div bind:clientHeight={viewport.height} bind:clientWidth={viewport.width} class="mt-2">
 | 
					      <div bind:clientHeight={viewport.height} bind:clientWidth={viewport.width} class="mt-2">
 | 
				
			||||||
        <GalleryViewer assets={data.pathAssets} {assetInteraction} {viewport} showAssetName={true} />
 | 
					        <GalleryViewer
 | 
				
			||||||
 | 
					          assets={data.pathAssets}
 | 
				
			||||||
 | 
					          {assetInteraction}
 | 
				
			||||||
 | 
					          {viewport}
 | 
				
			||||||
 | 
					          showAssetName={true}
 | 
				
			||||||
 | 
					          pageHeaderOffset={54}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
  </section>
 | 
					  </section>
 | 
				
			||||||
 | 
				
			|||||||
@ -368,6 +368,7 @@
 | 
				
			|||||||
        onIntersected={loadNextPage}
 | 
					        onIntersected={loadNextPage}
 | 
				
			||||||
        showArchiveIcon={true}
 | 
					        showArchiveIcon={true}
 | 
				
			||||||
        {viewport}
 | 
					        {viewport}
 | 
				
			||||||
 | 
					        pageHeaderOffset={54}
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
    {:else if !isLoading}
 | 
					    {:else if !isLoading}
 | 
				
			||||||
      <div class="flex min-h-[calc(66vh_-_11rem)] w-full place-content-center items-center dark:text-white">
 | 
					      <div class="flex min-h-[calc(66vh_-_11rem)] w-full place-content-center items-center dark:text-white">
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user