feat(web): improve slideshow quality of life (#18778)

* Add a new setting to toggle autoplay when showing the slideshow.
* Fix an issue where the slideshow would restart automatically when
navigating after it was paused.
* Add a keyboard shortcut 's' to start the slideshow from the asset
viewer.
* Add a keyboard shortcut ' ' to toggle the slideshow play/paused.
* Change the timeout for hiding the slideshow controls from 10 to 2.5
seconds.
* Add English translation for the 'autoplay_slideshow' setting.

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Dag Stuan 2025-06-02 16:45:39 +02:00 committed by GitHub
parent df927dd3ce
commit d544053c67
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 45 additions and 11 deletions

View File

@ -477,6 +477,7 @@
"authorized_devices": "Authorized Devices", "authorized_devices": "Authorized Devices",
"automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere", "automatic_endpoint_switching_subtitle": "Connect locally over designated Wi-Fi when available and use alternative connections elsewhere",
"automatic_endpoint_switching_title": "Automatic URL switching", "automatic_endpoint_switching_title": "Automatic URL switching",
"autoplay_slideshow": "Autoplay slideshow",
"back": "Back", "back": "Back",
"back_close_deselect": "Back, close, or deselect", "back_close_deselect": "Back, close, or deselect",
"background_location_permission": "Background location permission", "background_location_permission": "Background location permission",

View File

@ -352,7 +352,9 @@
const handleUpdateSelectedEditType = (type: string) => { const handleUpdateSelectedEditType = (type: string) => {
selectedEditType = type; selectedEditType = type;
}; };
let isFullScreen = $derived(fullscreenElement !== null); let isFullScreen = $derived(fullscreenElement !== null);
$effect(() => { $effect(() => {
if (asset) { if (asset) {
previewStackedAsset = undefined; previewStackedAsset = undefined;

View File

@ -113,6 +113,8 @@
}); });
}; };
const onPlaySlideshow = () => ($slideshowState = SlideshowState.PlaySlideshow);
$effect(() => { $effect(() => {
if (isFaceEditMode.value && $photoZoomState.currentZoom > 1) { if (isFaceEditMode.value && $photoZoomState.currentZoom > 1) {
zoomToggle(); zoomToggle();
@ -206,6 +208,8 @@
<svelte:document <svelte:document
use:shortcuts={[ use:shortcuts={[
{ shortcut: { key: 'z' }, onShortcut: zoomToggle, preventDefault: true },
{ shortcut: { key: 's' }, onShortcut: onPlaySlideshow, preventDefault: true },
{ shortcut: { key: 'c', ctrl: true }, onShortcut: onCopyShortcut, preventDefault: false }, { shortcut: { key: 'c', ctrl: true }, onShortcut: onCopyShortcut, preventDefault: false },
{ shortcut: { key: 'c', meta: true }, onShortcut: onCopyShortcut, preventDefault: false }, { shortcut: { key: 'c', meta: true }, onShortcut: onCopyShortcut, preventDefault: false },
{ shortcut: { key: 'z' }, onShortcut: zoomToggle, preventDefault: false }, { shortcut: { key: 'z' }, onShortcut: zoomToggle, preventDefault: false },

View File

@ -28,7 +28,8 @@
onSetToFullScreen = () => {}, onSetToFullScreen = () => {},
}: Props = $props(); }: Props = $props();
const { restartProgress, stopProgress, slideshowDelay, showProgressBar, slideshowNavigation } = slideshowStore; const { restartProgress, stopProgress, slideshowDelay, showProgressBar, slideshowNavigation, slideshowAutoplay } =
slideshowStore;
let progressBarStatus: ProgressBarStatus | undefined = $state(); let progressBarStatus: ProgressBarStatus | undefined = $state();
let progressBar = $state<ReturnType<typeof ProgressBar>>(); let progressBar = $state<ReturnType<typeof ProgressBar>>();
@ -60,20 +61,20 @@
showControls = false; showControls = false;
setCursorStyle('none'); setCursorStyle('none');
} }
}, 10_000); }, 2500);
}; };
onMount(() => { onMount(() => {
hideControlsAfterDelay(); hideControlsAfterDelay();
unsubscribeRestart = restartProgress.subscribe((value) => { unsubscribeRestart = restartProgress.subscribe((value) => {
if (value) { if (value) {
progressBar?.restart(value); progressBar?.restart();
} }
}); });
unsubscribeStop = stopProgress.subscribe((value) => { unsubscribeStop = stopProgress.subscribe((value) => {
if (value) { if (value) {
progressBar?.restart(false); progressBar?.restart();
stopControlsHideTimer(); stopControlsHideTimer();
} }
}); });
@ -90,7 +91,7 @@
}); });
const handleDone = async () => { const handleDone = async () => {
await progressBar?.reset(); await progressBar?.resetProgress();
if ($slideshowNavigation === SlideshowNavigation.AscendingOrder) { if ($slideshowNavigation === SlideshowNavigation.AscendingOrder) {
onPrevious(); onPrevious();
@ -113,6 +114,17 @@
{ shortcut: { key: 'Escape' }, onShortcut: onClose }, { shortcut: { key: 'Escape' }, onShortcut: onClose },
{ shortcut: { key: 'ArrowLeft' }, onShortcut: onPrevious }, { shortcut: { key: 'ArrowLeft' }, onShortcut: onPrevious },
{ shortcut: { key: 'ArrowRight' }, onShortcut: onNext }, { shortcut: { key: 'ArrowRight' }, onShortcut: onNext },
{
shortcut: { key: ' ' },
onShortcut: () => {
if (progressBarStatus === ProgressBarStatus.Paused) {
progressBar?.play();
} else {
progressBar?.pause();
}
},
preventDefault: true,
},
]} ]}
/> />
@ -187,7 +199,7 @@
{/if} {/if}
<ProgressBar <ProgressBar
autoplay autoplay={$slideshowAutoplay}
hidden={!$showProgressBar} hidden={!$showProgressBar}
duration={$slideshowDelay} duration={$slideshowDelay}
bind:this={progressBar} bind:this={progressBar}

View File

@ -51,7 +51,11 @@
onMount(async () => { onMount(async () => {
if (autoplay) { if (autoplay) {
status = ProgressBarStatus.Playing;
await play(); await play();
} else {
status = ProgressBarStatus.Paused;
await progress.set(0);
} }
}); });
@ -67,16 +71,15 @@
await progress.set($progress); await progress.set($progress);
}; };
export const restart = async (autoplay: boolean) => { export const restart = async () => {
await progress.set(0); await progress.set(0);
if (autoplay) { if (status !== ProgressBarStatus.Paused) {
await play(); await play();
} }
}; };
export const reset = async () => { export const resetProgress = async () => {
status = ProgressBarStatus.Paused;
await progress.set(0); await progress.set(0);
}; };

View File

@ -16,7 +16,14 @@
import type { RenderedOption } from './elements/dropdown.svelte'; import type { RenderedOption } from './elements/dropdown.svelte';
import SettingDropdown from './shared-components/settings/setting-dropdown.svelte'; import SettingDropdown from './shared-components/settings/setting-dropdown.svelte';
const { slideshowDelay, showProgressBar, slideshowNavigation, slideshowLook, slideshowTransition } = slideshowStore; const {
slideshowDelay,
showProgressBar,
slideshowNavigation,
slideshowLook,
slideshowTransition,
slideshowAutoplay,
} = slideshowStore;
interface Props { interface Props {
onClose?: () => void; onClose?: () => void;
@ -30,6 +37,7 @@
let tempSlideshowNavigation = $state($slideshowNavigation); let tempSlideshowNavigation = $state($slideshowNavigation);
let tempSlideshowLook = $state($slideshowLook); let tempSlideshowLook = $state($slideshowLook);
let tempSlideshowTransition = $state($slideshowTransition); let tempSlideshowTransition = $state($slideshowTransition);
let tempSlideshowAutoplay = $state($slideshowAutoplay);
const navigationOptions: Record<SlideshowNavigation, RenderedOption> = { const navigationOptions: Record<SlideshowNavigation, RenderedOption> = {
[SlideshowNavigation.Shuffle]: { icon: mdiShuffle, title: $t('shuffle') }, [SlideshowNavigation.Shuffle]: { icon: mdiShuffle, title: $t('shuffle') },
@ -60,6 +68,7 @@
$slideshowNavigation = tempSlideshowNavigation; $slideshowNavigation = tempSlideshowNavigation;
$slideshowLook = tempSlideshowLook; $slideshowLook = tempSlideshowLook;
$slideshowTransition = tempSlideshowTransition; $slideshowTransition = tempSlideshowTransition;
$slideshowAutoplay = tempSlideshowAutoplay;
onClose(); onClose();
}; };
</script> </script>
@ -83,6 +92,7 @@
tempSlideshowLook = handleToggle(option, lookOptions) || tempSlideshowLook; tempSlideshowLook = handleToggle(option, lookOptions) || tempSlideshowLook;
}} }}
/> />
<SettingSwitch title={$t('autoplay_slideshow')} bind:checked={tempSlideshowAutoplay} />
<SettingSwitch title={$t('show_progress_bar')} bind:checked={tempShowProgressBar} /> <SettingSwitch title={$t('show_progress_bar')} bind:checked={tempShowProgressBar} />
<SettingSwitch title={$t('show_slideshow_transition')} bind:checked={tempSlideshowTransition} /> <SettingSwitch title={$t('show_slideshow_transition')} bind:checked={tempSlideshowTransition} />
<SettingInputField <SettingInputField

View File

@ -39,6 +39,7 @@ function createSlideshowStore() {
const showProgressBar = persisted<boolean>('slideshow-show-progressbar', true); const showProgressBar = persisted<boolean>('slideshow-show-progressbar', true);
const slideshowDelay = persisted<number>('slideshow-delay', 5, {}); const slideshowDelay = persisted<number>('slideshow-delay', 5, {});
const slideshowTransition = persisted<boolean>('slideshow-transition', true); const slideshowTransition = persisted<boolean>('slideshow-transition', true);
const slideshowAutoplay = persisted<boolean>('slideshow-autoplay', true, {});
return { return {
restartProgress: { restartProgress: {
@ -69,6 +70,7 @@ function createSlideshowStore() {
slideshowDelay, slideshowDelay,
showProgressBar, showProgressBar,
slideshowTransition, slideshowTransition,
slideshowAutoplay,
}; };
} }