1
0
mirror of https://github.com/immich-app/immich.git synced 2025-06-24 04:46:50 +02:00

feat(web): slideshow mode (#3813)

* slideshow

slideshow for main screen

Added control buttons

update

close detail panel window sif opened

format

5 seconds

remove unused files

handle video player

format

* fix: restrict slideshow to timeline views

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Alex
2023-08-25 18:20:45 -05:00
committed by GitHub
parent 59bb727636
commit e18a9f84a4
4 changed files with 210 additions and 30 deletions

View File

@ -16,13 +16,17 @@
import { ProjectionType } from '$lib/constants';
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
import ProfileImageCropper from '../shared-components/profile-image-cropper.svelte';
import Pause from 'svelte-material-icons/Pause.svelte';
import Play from 'svelte-material-icons/Play.svelte';
import { isShowDetail } from '$lib/stores/preferences.store';
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
import NavigationArea from './navigation-area.svelte';
import { browser } from '$app/environment';
import { handleError } from '$lib/utils/handle-error';
import type { AssetStore } from '$lib/stores/assets.store';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import Close from 'svelte-material-icons/Close.svelte';
import ProgressBar, { ProgressBarStatus } from '../shared-components/progress-bar/progress-bar.svelte';
export let assetStore: AssetStore | null = null;
export let asset: AssetResponseDto;
@ -47,6 +51,7 @@
let isShowProfileImageCrop = false;
let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : true;
let canCopyImagesToClipboard: boolean;
const onKeyboardPress = (keyInfo: KeyboardEvent) => handleKeyboardPress(keyInfo.key, keyInfo.shiftKey);
onMount(async () => {
@ -125,12 +130,25 @@
const closeViewer = () => dispatch('close');
const navigateAssetForward = (e?: Event) => {
const navigateAssetForward = async (e?: Event) => {
if (isSlideshowMode && assetStore && progressBar) {
const hasNext = await assetStore.getNextAssetId(asset.id);
if (hasNext) {
progressBar.restart(true);
} else {
handleStopSlideshow();
}
}
e?.stopPropagation();
dispatch('next');
};
const navigateAssetBackward = (e?: Event) => {
if (isSlideshowMode && progressBar) {
progressBar.restart(true);
}
e?.stopPropagation();
dispatch('previous');
};
@ -263,36 +281,104 @@
handleError(error, `Unable to submit job`);
}
};
/**
* Slide show mode
*/
let isSlideshowMode = false;
let assetViewerHtmlElement: HTMLElement;
let progressBar: ProgressBar;
let progressBarStatus: ProgressBarStatus;
const handleVideoStarted = () => {
if (isSlideshowMode) {
progressBar.restart(false);
}
};
const handleVideoEnded = async () => {
if (isSlideshowMode) {
await navigateAssetForward();
}
};
const handlePlaySlideshow = async () => {
try {
await assetViewerHtmlElement.requestFullscreen();
} catch (error) {
console.error('Error entering fullscreen', error);
} finally {
isSlideshowMode = true;
}
};
const handleStopSlideshow = async () => {
try {
await document.exitFullscreen();
} catch (error) {
console.error('Error exiting fullscreen', error);
} finally {
isSlideshowMode = false;
progressBar.restart(false);
}
};
</script>
<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-y-hidden bg-black"
bind:this={assetViewerHtmlElement}
>
<div class="z-[1000] col-span-4 col-start-1 row-span-1 row-start-1 transition-transform">
<AssetViewerNavBar
{asset}
isMotionPhotoPlaying={shouldPlayMotionPhoto}
showCopyButton={canCopyImagesToClipboard && asset.type === AssetTypeEnum.Image}
showZoomButton={asset.type === AssetTypeEnum.Image}
showMotionPlayButton={!!asset.livePhotoVideoId}
showDownloadButton={shouldShowDownloadButton}
on:goBack={closeViewer}
on:showDetail={showDetailInfoHandler}
on:download={() => downloadFile(asset)}
on:delete={() => (isShowDeleteConfirmation = true)}
on:favorite={toggleFavorite}
on:addToAlbum={() => openAlbumPicker(false)}
on:addToSharedAlbum={() => openAlbumPicker(true)}
on:playMotionPhoto={() => (shouldPlayMotionPhoto = true)}
on:stopMotionPhoto={() => (shouldPlayMotionPhoto = false)}
on:toggleArchive={toggleArchive}
on:asProfileImage={() => (isShowProfileImageCrop = true)}
on:runJob={({ detail: job }) => handleRunJob(job)}
/>
{#if isSlideshowMode}
<!-- SlideShowController -->
<div class="flex">
<div class="m-4 flex gap-2">
<CircleIconButton logo={Close} on:click={handleStopSlideshow} title="Exit Slideshow" />
<CircleIconButton
logo={progressBarStatus === ProgressBarStatus.Paused ? Play : Pause}
on:click={() => (progressBarStatus === ProgressBarStatus.Paused ? progressBar.play() : progressBar.pause())}
title={progressBarStatus === ProgressBarStatus.Paused ? 'Play' : 'Pause'}
/>
<CircleIconButton logo={ChevronLeft} on:click={navigateAssetBackward} title="Previous" />
<CircleIconButton logo={ChevronRight} on:click={navigateAssetForward} title="Next" />
</div>
<ProgressBar
autoplay
bind:this={progressBar}
bind:status={progressBarStatus}
on:done={navigateAssetForward}
duration={5000}
/>
</div>
{:else}
<AssetViewerNavBar
{asset}
isMotionPhotoPlaying={shouldPlayMotionPhoto}
showCopyButton={canCopyImagesToClipboard && asset.type === AssetTypeEnum.Image}
showZoomButton={asset.type === AssetTypeEnum.Image}
showMotionPlayButton={!!asset.livePhotoVideoId}
showDownloadButton={shouldShowDownloadButton}
showSlideshow={!!assetStore}
on:goBack={closeViewer}
on:showDetail={showDetailInfoHandler}
on:download={() => downloadFile(asset)}
on:delete={() => (isShowDeleteConfirmation = true)}
on:favorite={toggleFavorite}
on:addToAlbum={() => openAlbumPicker(false)}
on:addToSharedAlbum={() => openAlbumPicker(true)}
on:playMotionPhoto={() => (shouldPlayMotionPhoto = true)}
on:stopMotionPhoto={() => (shouldPlayMotionPhoto = false)}
on:toggleArchive={toggleArchive}
on:asProfileImage={() => (isShowProfileImageCrop = true)}
on:runJob={({ detail: job }) => handleRunJob(job)}
on:playSlideShow={handlePlaySlideshow}
/>
{/if}
</div>
{#if showNavigation}
{#if !isSlideshowMode && showNavigation}
<div class="column-span-1 z-[999] col-start-1 row-span-1 row-start-2 mb-[60px] justify-self-start">
<NavigationArea on:click={navigateAssetBackward}><ChevronLeft size="36" /></NavigationArea>
</div>
@ -323,18 +409,23 @@
<PhotoViewer {asset} on:close={closeViewer} />
{/if}
{:else}
<VideoViewer assetId={asset.id} on:close={closeViewer} />
<VideoViewer
assetId={asset.id}
on:close={closeViewer}
on:onVideoEnded={handleVideoEnded}
on:onVideoStarted={handleVideoStarted}
/>
{/if}
{/key}
</div>
{#if showNavigation}
{#if !isSlideshowMode && showNavigation}
<div class="z-[999] col-span-1 col-start-4 row-span-1 row-start-2 mb-[60px] justify-self-end">
<NavigationArea on:click={navigateAssetForward}><ChevronRight size="36" /></NavigationArea>
</div>
{/if}
{#if $isShowDetail}
{#if !isSlideshowMode && $isShowDetail}
<div
transition:fly={{ duration: 150 }}
id="detail-panel"