From 508f32c08a5a0de1a45e00a8872cb3bd984bc347 Mon Sep 17 00:00:00 2001
From: martin <74269598+martabal@users.noreply.github.com>
Date: Thu, 21 Mar 2024 21:01:08 +0100
Subject: [PATCH] feat(web): improvements to slideshow (#8032)

* feat: improvements to slideshow

* feat: pause video with slideshow bar

* pr feedback

* fix: remove dispatch

* fix: simplify

* pr feedback

* pr feedback

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
---
 .../asset-viewer/asset-viewer.svelte          |  7 ++
 .../asset-viewer/slideshow-bar.svelte         | 67 +++++++++++++++----
 .../asset-viewer/video-viewer.svelte          |  3 +
 3 files changed, 64 insertions(+), 13 deletions(-)

diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
index f9d2f787bb..51da304114 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -97,6 +97,9 @@
   let isShowActivity = false;
   let isLiked: ActivityResponseDto | null = null;
   let numberOfComments: number;
+  let fullscreenElement: Element;
+
+  $: isFullScreen = fullscreenElement !== null;
 
   $: {
     if (asset.stackCount && asset.stack) {
@@ -512,6 +515,8 @@
   ]}
 />
 
+<svelte:document bind:fullscreenElement />
+
 <section
   id="immich-asset-viewer"
   class="fixed left-0 top-0 z-[1001] grid h-screen w-screen grid-cols-4 grid-rows-[64px_1fr] overflow-hidden bg-black"
@@ -562,6 +567,8 @@
     {#if $slideshowState != SlideshowState.None}
       <div class="z-[1000] absolute w-full flex">
         <SlideshowBar
+          {isFullScreen}
+          onSetToFullScreen={() => assetViewerHtmlElement.requestFullscreen()}
           onPrevious={() => navigateAsset('previous')}
           onNext={() => navigateAsset('next')}
           onClose={() => ($slideshowState = SlideshowState.StopSlideshow)}
diff --git a/web/src/lib/components/asset-viewer/slideshow-bar.svelte b/web/src/lib/components/asset-viewer/slideshow-bar.svelte
index 37cd7f7012..03e8c9df41 100644
--- a/web/src/lib/components/asset-viewer/slideshow-bar.svelte
+++ b/web/src/lib/components/asset-viewer/slideshow-bar.svelte
@@ -3,23 +3,46 @@
   import ProgressBar, { ProgressBarStatus } from '$lib/components/shared-components/progress-bar/progress-bar.svelte';
   import SlideshowSettings from '$lib/components/slideshow-settings.svelte';
   import { SlideshowNavigation, slideshowStore } from '$lib/stores/slideshow.store';
-  import { mdiChevronLeft, mdiChevronRight, mdiClose, mdiCog, mdiPause, mdiPlay } from '@mdi/js';
+  import { mdiChevronLeft, mdiChevronRight, mdiClose, mdiCog, mdiFullscreen, mdiPause, mdiPlay } from '@mdi/js';
   import { onDestroy, onMount } from 'svelte';
+  import { fly } from 'svelte/transition';
 
+  export let isFullScreen: boolean;
   export let onNext = () => {};
   export let onPrevious = () => {};
   export let onClose = () => {};
+  export let onSetToFullScreen = () => {};
 
   const { restartProgress, stopProgress, slideshowDelay, showProgressBar, slideshowNavigation } = slideshowStore;
 
   let progressBarStatus: ProgressBarStatus;
   let progressBar: ProgressBar;
   let showSettings = false;
+  let showControls = true;
+  let timer: NodeJS.Timeout;
+  let isOverControls = false;
 
   let unsubscribeRestart: () => void;
   let unsubscribeStop: () => void;
 
+  const resetTimer = () => {
+    clearTimeout(timer);
+    document.body.style.cursor = '';
+    showControls = true;
+    startTimer();
+  };
+
+  const startTimer = () => {
+    timer = setTimeout(() => {
+      if (!isOverControls) {
+        showControls = false;
+        document.body.style.cursor = 'none';
+      }
+    }, 10_000);
+  };
+
   onMount(() => {
+    startTimer();
     unsubscribeRestart = restartProgress.subscribe((value) => {
       if (value) {
         progressBar.restart(value);
@@ -52,19 +75,37 @@
   };
 </script>
 
-<div class="m-4 flex gap-2">
-  <CircleIconButton buttonSize="50" icon={mdiClose} on:click={onClose} title="Exit Slideshow" />
-  <CircleIconButton
-    buttonSize="50"
-    icon={progressBarStatus === ProgressBarStatus.Paused ? mdiPlay : mdiPause}
-    on:click={() => (progressBarStatus === ProgressBarStatus.Paused ? progressBar.play() : progressBar.pause())}
-    title={progressBarStatus === ProgressBarStatus.Paused ? 'Play' : 'Pause'}
-  />
-  <CircleIconButton buttonSize="50" icon={mdiChevronLeft} on:click={onPrevious} title="Previous" />
-  <CircleIconButton buttonSize="50" icon={mdiChevronRight} on:click={onNext} title="Next" />
-  <CircleIconButton buttonSize="50" icon={mdiCog} on:click={() => (showSettings = !showSettings)} title="Next" />
-</div>
+<svelte:window on:mousemove={resetTimer} />
 
+{#if showControls}
+  <!-- svelte-ignore a11y-no-static-element-interactions -->
+  <div
+    class="m-4 flex gap-2"
+    on:mouseenter={() => (isOverControls = true)}
+    on:mouseleave={() => (isOverControls = false)}
+    transition:fly={{ duration: 150 }}
+  >
+    <CircleIconButton buttonSize="50" icon={mdiClose} on:click={onClose} title="Exit Slideshow" />
+
+    <CircleIconButton
+      buttonSize="50"
+      icon={progressBarStatus === ProgressBarStatus.Paused ? mdiPlay : mdiPause}
+      on:click={() => (progressBarStatus === ProgressBarStatus.Paused ? progressBar.play() : progressBar.pause())}
+      title={progressBarStatus === ProgressBarStatus.Paused ? 'Play' : 'Pause'}
+    />
+    <CircleIconButton buttonSize="50" icon={mdiChevronLeft} on:click={onPrevious} title="Previous" />
+    <CircleIconButton buttonSize="50" icon={mdiChevronRight} on:click={onNext} title="Next" />
+    <CircleIconButton buttonSize="50" icon={mdiCog} on:click={() => (showSettings = !showSettings)} title="Next" />
+    {#if !isFullScreen}
+      <CircleIconButton
+        buttonSize="50"
+        icon={mdiFullscreen}
+        on:click={onSetToFullScreen}
+        title="Set Slideshow to fullscreen"
+      />
+    {/if}
+  </div>
+{/if}
 {#if showSettings}
   <SlideshowSettings onClose={() => (showSettings = false)} />
 {/if}
diff --git a/web/src/lib/components/asset-viewer/video-viewer.svelte b/web/src/lib/components/asset-viewer/video-viewer.svelte
index f5ff5f0fc2..c53fcf6008 100644
--- a/web/src/lib/components/asset-viewer/video-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/video-viewer.svelte
@@ -9,7 +9,9 @@
 
   export let assetId: string;
 
+  let element: HTMLVideoElement | undefined = undefined;
   let isVideoLoading = true;
+
   const dispatch = createEventDispatcher<{ onVideoEnded: void; onVideoStarted: void }>();
 
   const handleCanPlay = async (event: Event) => {
@@ -29,6 +31,7 @@
 
 <div transition:fade={{ duration: 150 }} class="flex h-full select-none place-content-center place-items-center">
   <video
+    bind:this={element}
     autoplay
     playsinline
     controls