mirror of
https://github.com/samsonjs/immich.git
synced 2026-04-26 14:57:42 +00:00
Exit slideshow when exiting fullscreen. Browsers do not send a keyboard event when exiting fullscreen, so if the user exits fullscreen with the escape key, the slideshow remains open, requiring another escape key press to close it. Fix this by listening for the fullscreenchange event and closing the slideshow when exiting fullscreen.
228 lines
6 KiB
Svelte
228 lines
6 KiB
Svelte
<script lang="ts">
|
|
import { shortcuts } from '$lib/actions/shortcut';
|
|
import ProgressBar from '$lib/components/shared-components/progress-bar/progress-bar.svelte';
|
|
import { ProgressBarStatus } from '$lib/constants';
|
|
import { modalManager } from '$lib/managers/modal-manager.svelte';
|
|
import SlideshowSettingsModal from '$lib/modals/SlideshowSettingsModal.svelte';
|
|
import { SlideshowNavigation, slideshowStore } from '$lib/stores/slideshow.store';
|
|
import { IconButton } from '@immich/ui';
|
|
import { mdiChevronLeft, mdiChevronRight, mdiClose, mdiCog, mdiFullscreen, mdiPause, mdiPlay } from '@mdi/js';
|
|
import { onDestroy, onMount } from 'svelte';
|
|
import { swipe } from 'svelte-gestures';
|
|
import { t } from 'svelte-i18n';
|
|
import { fly } from 'svelte/transition';
|
|
|
|
interface Props {
|
|
isFullScreen: boolean;
|
|
onNext?: () => void;
|
|
onPrevious?: () => void;
|
|
onClose?: () => void;
|
|
onSetToFullScreen?: () => void;
|
|
}
|
|
|
|
let {
|
|
isFullScreen,
|
|
onNext = () => {},
|
|
onPrevious = () => {},
|
|
onClose = () => {},
|
|
onSetToFullScreen = () => {},
|
|
}: Props = $props();
|
|
|
|
const { restartProgress, stopProgress, slideshowDelay, showProgressBar, slideshowNavigation, slideshowAutoplay } =
|
|
slideshowStore;
|
|
|
|
let progressBarStatus: ProgressBarStatus | undefined = $state();
|
|
let progressBar = $state<ReturnType<typeof ProgressBar>>();
|
|
let showControls = $state(true);
|
|
let timer: NodeJS.Timeout;
|
|
let isOverControls = $state(false);
|
|
|
|
let unsubscribeRestart: () => void;
|
|
let unsubscribeStop: () => void;
|
|
|
|
const setCursorStyle = (style: string) => {
|
|
document.body.style.cursor = style;
|
|
};
|
|
|
|
const stopControlsHideTimer = () => {
|
|
clearTimeout(timer);
|
|
setCursorStyle('');
|
|
};
|
|
|
|
const showControlBar = () => {
|
|
showControls = true;
|
|
stopControlsHideTimer();
|
|
hideControlsAfterDelay();
|
|
};
|
|
|
|
const hideControlsAfterDelay = () => {
|
|
timer = setTimeout(() => {
|
|
if (!isOverControls) {
|
|
showControls = false;
|
|
setCursorStyle('none');
|
|
}
|
|
}, 2500);
|
|
};
|
|
|
|
onMount(() => {
|
|
hideControlsAfterDelay();
|
|
unsubscribeRestart = restartProgress.subscribe((value) => {
|
|
if (value) {
|
|
progressBar?.restart();
|
|
}
|
|
});
|
|
|
|
unsubscribeStop = stopProgress.subscribe((value) => {
|
|
if (value) {
|
|
progressBar?.restart();
|
|
stopControlsHideTimer();
|
|
}
|
|
});
|
|
});
|
|
|
|
onDestroy(() => {
|
|
if (unsubscribeRestart) {
|
|
unsubscribeRestart();
|
|
}
|
|
|
|
if (unsubscribeStop) {
|
|
unsubscribeStop();
|
|
}
|
|
});
|
|
|
|
const handleDone = async () => {
|
|
await progressBar?.resetProgress();
|
|
|
|
if ($slideshowNavigation === SlideshowNavigation.AscendingOrder) {
|
|
onPrevious();
|
|
return;
|
|
}
|
|
onNext();
|
|
};
|
|
|
|
const onShowSettings = async () => {
|
|
// eslint-disable-next-line tscompat/tscompat
|
|
if (document.fullscreenElement) {
|
|
// eslint-disable-next-line tscompat/tscompat
|
|
await document.exitFullscreen();
|
|
}
|
|
await modalManager.show(SlideshowSettingsModal);
|
|
};
|
|
|
|
onMount(() => {
|
|
function exitFullscreenHandler() {
|
|
const doc = document as Document & {
|
|
webkitIsFullScreen?: boolean;
|
|
};
|
|
|
|
if (
|
|
// eslint-disable-next-line tscompat/tscompat
|
|
!document.fullscreenElement &&
|
|
!doc.webkitIsFullScreen
|
|
) {
|
|
onClose();
|
|
}
|
|
}
|
|
|
|
document.addEventListener('fullscreenchange', exitFullscreenHandler);
|
|
document.addEventListener('webkitfullscreenchange', exitFullscreenHandler);
|
|
|
|
return () => {
|
|
document.removeEventListener('fullscreenchange', exitFullscreenHandler);
|
|
document.removeEventListener('webkitfullscreenchange', exitFullscreenHandler);
|
|
};
|
|
});
|
|
</script>
|
|
|
|
<svelte:document
|
|
onmousemove={showControlBar}
|
|
use:shortcuts={[
|
|
{ shortcut: { key: 'Escape' }, onShortcut: onClose },
|
|
{ shortcut: { key: 'ArrowLeft' }, onShortcut: onPrevious },
|
|
{ shortcut: { key: 'ArrowRight' }, onShortcut: onNext },
|
|
{
|
|
shortcut: { key: ' ' },
|
|
onShortcut: () => {
|
|
if (progressBarStatus === ProgressBarStatus.Paused) {
|
|
progressBar?.play();
|
|
} else {
|
|
progressBar?.pause();
|
|
}
|
|
},
|
|
preventDefault: true,
|
|
},
|
|
]}
|
|
/>
|
|
|
|
<svelte:body use:swipe={() => ({ touchAction: 'pan-x' })} onswipedown={showControlBar} />
|
|
|
|
{#if showControls}
|
|
<div
|
|
class="m-4 flex gap-2 dark"
|
|
onmouseenter={() => (isOverControls = true)}
|
|
onmouseleave={() => (isOverControls = false)}
|
|
transition:fly={{ duration: 150 }}
|
|
role="navigation"
|
|
>
|
|
<IconButton
|
|
variant="ghost"
|
|
shape="round"
|
|
color="secondary"
|
|
icon={mdiClose}
|
|
onclick={onClose}
|
|
aria-label={$t('exit_slideshow')}
|
|
/>
|
|
|
|
<IconButton
|
|
variant="ghost"
|
|
shape="round"
|
|
color="secondary"
|
|
icon={progressBarStatus === ProgressBarStatus.Paused ? mdiPlay : mdiPause}
|
|
onclick={() => (progressBarStatus === ProgressBarStatus.Paused ? progressBar?.play() : progressBar?.pause())}
|
|
aria-label={progressBarStatus === ProgressBarStatus.Paused ? $t('play') : $t('pause')}
|
|
/>
|
|
<IconButton
|
|
variant="ghost"
|
|
shape="round"
|
|
color="secondary"
|
|
icon={mdiChevronLeft}
|
|
onclick={onPrevious}
|
|
aria-label={$t('previous')}
|
|
/>
|
|
<IconButton
|
|
variant="ghost"
|
|
shape="round"
|
|
color="secondary"
|
|
icon={mdiChevronRight}
|
|
onclick={onNext}
|
|
aria-label={$t('next')}
|
|
/>
|
|
<IconButton
|
|
variant="ghost"
|
|
shape="round"
|
|
color="secondary"
|
|
icon={mdiCog}
|
|
onclick={onShowSettings}
|
|
aria-label={$t('slideshow_settings')}
|
|
/>
|
|
{#if !isFullScreen}
|
|
<IconButton
|
|
variant="ghost"
|
|
shape="round"
|
|
color="secondary"
|
|
icon={mdiFullscreen}
|
|
onclick={onSetToFullScreen}
|
|
aria-label={$t('set_slideshow_to_fullscreen')}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<ProgressBar
|
|
autoplay={$slideshowAutoplay}
|
|
hidden={!$showProgressBar}
|
|
duration={$slideshowDelay}
|
|
bind:this={progressBar}
|
|
bind:status={progressBarStatus}
|
|
onDone={handleDone}
|
|
/>
|