From 8d44afe915b9f94683a0c92814516470af502c9b Mon Sep 17 00:00:00 2001 From: martin <74269598+martabal@users.noreply.github.com> Date: Sat, 2 Mar 2024 16:50:02 +0100 Subject: [PATCH] feat(web): ascending order for slideshow (#7502) * feat: ascending order for slideshow * feat: use dropdown * rename * fix: size * pr feedback * fix: hide text on small screen * Wording --------- Co-authored-by: Alex Tran --- .../asset-viewer/asset-viewer.svelte | 55 ++++++++----------- .../asset-viewer/slideshow-bar.svelte | 32 ++++++----- .../lib/components/elements/dropdown.svelte | 20 +++---- .../settings/setting-dropdown.svelte | 48 ++++++++++++++++ .../lib/components/slideshow-settings.svelte | 33 +++++++++-- web/src/lib/stores/slideshow.store.ts | 13 ++++- .../(user)/albums/[albumId]/+page.svelte | 7 ++- 7 files changed, 144 insertions(+), 64 deletions(-) create mode 100644 web/src/lib/components/shared-components/settings/setting-dropdown.svelte diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 3869c60970..b8d25d8b66 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -7,7 +7,7 @@ import type { AssetStore } from '$lib/stores/assets.store'; import { isShowDetail, showDeleteModal } from '$lib/stores/preferences.store'; import { featureFlags } from '$lib/stores/server-config.store'; - import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; + import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { stackAssetsStore } from '$lib/stores/stacked-asset.store'; import { user } from '$lib/stores/user.store'; import { getAssetJobMessage, isSharedLink, handlePromiseError } from '$lib/utils'; @@ -67,7 +67,7 @@ const { restartProgress: restartSlideshowProgress, stopProgress: stopSlideshowProgress, - slideshowShuffle, + slideshowNavigation, slideshowState, } = slideshowStore; @@ -190,8 +190,8 @@ } }); - shuffleSlideshowUnsubscribe = slideshowShuffle.subscribe((value) => { - if (value) { + shuffleSlideshowUnsubscribe = slideshowNavigation.subscribe((value) => { + if (value === SlideshowNavigation.Shuffle) { slideshowHistory.reset(); slideshowHistory.queue(asset.id); } @@ -269,11 +269,11 @@ return; } case 'ArrowLeft': { - navigateAssetBackward(); + await navigateAsset('previous'); return; } case 'ArrowRight': { - await navigateAssetForward(); + await navigateAsset('next'); return; } case 'd': @@ -330,13 +330,16 @@ $restartSlideshowProgress = true; }; - const navigateAssetForward = async (e?: Event) => { - if ($slideshowState === SlideshowState.PlaySlideshow && $slideshowShuffle) { - return slideshowHistory.next() || navigateAssetRandom(); + const navigateAsset = async (order: 'previous' | 'next', e?: Event) => { + if ($slideshowState === SlideshowState.PlaySlideshow && $slideshowNavigation === SlideshowNavigation.Shuffle) { + return (order === 'previous' ? slideshowHistory.previous() : slideshowHistory.next()) || navigateAssetRandom(); } if ($slideshowState === SlideshowState.PlaySlideshow && assetStore) { - const hasNext = await assetStore.getNextAssetId(asset.id); + const hasNext = + order === 'previous' + ? await assetStore.getPreviousAssetId(asset.id) + : await assetStore.getNextAssetId(asset.id); if (hasNext) { $restartSlideshowProgress = true; } else { @@ -345,21 +348,7 @@ } e?.stopPropagation(); - dispatch('next'); - }; - - const navigateAssetBackward = (e?: Event) => { - if ($slideshowState === SlideshowState.PlaySlideshow && $slideshowShuffle) { - slideshowHistory.previous(); - return; - } - - if ($slideshowState === SlideshowState.PlaySlideshow) { - $restartSlideshowProgress = true; - } - - e?.stopPropagation(); - dispatch('previous'); + dispatch(order); }; const showDetailInfoHandler = () => { @@ -504,7 +493,7 @@ const handleVideoEnded = async () => { if ($slideshowState === SlideshowState.PlaySlideshow) { - await navigateAssetForward(); + await navigateAsset('next'); } }; @@ -594,7 +583,9 @@ {#if $slideshowState === SlideshowState.None && showNavigation}
- + navigateAsset('previous', e)} + >
{/if} @@ -603,9 +594,9 @@ {#if $slideshowState != SlideshowState.None}
($slideshowState = SlideshowState.StopSlideshow)} + onPrevious={() => navigateAsset('previous')} + onNext={() => navigateAsset('next')} + onClose={() => ($slideshowState = SlideshowState.StopSlideshow)} />
{/if} @@ -708,7 +699,9 @@ {#if $slideshowState === SlideshowState.None && showNavigation}
- + navigateAsset('next', e)} + >
{/if} diff --git a/web/src/lib/components/asset-viewer/slideshow-bar.svelte b/web/src/lib/components/asset-viewer/slideshow-bar.svelte index 164b0a7913..37cd7f7012 100644 --- a/web/src/lib/components/asset-viewer/slideshow-bar.svelte +++ b/web/src/lib/components/asset-viewer/slideshow-bar.svelte @@ -2,11 +2,15 @@ import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import ProgressBar, { ProgressBarStatus } from '$lib/components/shared-components/progress-bar/progress-bar.svelte'; import SlideshowSettings from '$lib/components/slideshow-settings.svelte'; - import { slideshowStore } from '$lib/stores/slideshow.store'; + import { SlideshowNavigation, slideshowStore } from '$lib/stores/slideshow.store'; import { mdiChevronLeft, mdiChevronRight, mdiClose, mdiCog, mdiPause, mdiPlay } from '@mdi/js'; - import { createEventDispatcher, onDestroy, onMount } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; - const { restartProgress, stopProgress, slideshowDelay, showProgressBar } = slideshowStore; + export let onNext = () => {}; + export let onPrevious = () => {}; + export let onClose = () => {}; + + const { restartProgress, stopProgress, slideshowDelay, showProgressBar, slideshowNavigation } = slideshowStore; let progressBarStatus: ProgressBarStatus; let progressBar: ProgressBar; @@ -15,12 +19,6 @@ let unsubscribeRestart: () => void; let unsubscribeStop: () => void; - const dispatch = createEventDispatcher<{ - next: void; - prev: void; - close: void; - }>(); - onMount(() => { unsubscribeRestart = restartProgress.subscribe((value) => { if (value) { @@ -44,18 +42,26 @@ unsubscribeStop(); } }); + + const handleDone = () => { + if ($slideshowNavigation === SlideshowNavigation.AscendingOrder) { + onPrevious(); + return; + } + onNext(); + };
- dispatch('close')} title="Exit Slideshow" /> + (progressBarStatus === ProgressBarStatus.Paused ? progressBar.play() : progressBar.pause())} title={progressBarStatus === ProgressBarStatus.Paused ? 'Play' : 'Pause'} /> - dispatch('prev')} title="Previous" /> - dispatch('next')} title="Next" /> + + (showSettings = !showSettings)} title="Next" />
@@ -69,5 +75,5 @@ duration={$slideshowDelay} bind:this={progressBar} bind:status={progressBarStatus} - on:done={() => dispatch('next')} + on:done={handleDone} /> diff --git a/web/src/lib/components/elements/dropdown.svelte b/web/src/lib/components/elements/dropdown.svelte index 24755a3a0a..21d664c3ce 100644 --- a/web/src/lib/components/elements/dropdown.svelte +++ b/web/src/lib/components/elements/dropdown.svelte @@ -2,6 +2,11 @@ // Necessary for eslint /* eslint-disable @typescript-eslint/no-explicit-any */ type T = any; + + export type RenderedOption = { + title: string; + icon?: string; + }; + +
+
+
+ + {#if isEdited} +
+ Unsaved change +
+ {/if} +
+ +

{subtitle}

+ +
+
+ { + return { + title: option.title, + icon: option.icon, + }; + }} + on:select={({ detail }) => onToggle(detail)} + /> +
+
diff --git a/web/src/lib/components/slideshow-settings.svelte b/web/src/lib/components/slideshow-settings.svelte index 0bc970a644..70c893c4ed 100644 --- a/web/src/lib/components/slideshow-settings.svelte +++ b/web/src/lib/components/slideshow-settings.svelte @@ -4,28 +4,51 @@ SettingInputFieldType, } from '$lib/components/shared-components/settings/setting-input-field.svelte'; import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte'; - import { slideshowStore } from '../stores/slideshow.store'; + import { mdiArrowDownThin, mdiArrowUpThin, mdiShuffle } from '@mdi/js'; + import { SlideshowNavigation, slideshowStore } from '../stores/slideshow.store'; import Button from './elements/buttons/button.svelte'; + import type { RenderedOption } from './elements/dropdown.svelte'; + import SettingDropdown from './shared-components/settings/setting-dropdown.svelte'; - const { slideshowShuffle, slideshowDelay, showProgressBar } = slideshowStore; + const { slideshowDelay, showProgressBar, slideshowNavigation } = slideshowStore; export let onClose = () => {}; + + const options: Record = { + [SlideshowNavigation.Shuffle]: { icon: mdiShuffle, title: 'Shuffle' }, + [SlideshowNavigation.AscendingOrder]: { icon: mdiArrowUpThin, title: 'Backward' }, + [SlideshowNavigation.DescendingOrder]: { icon: mdiArrowDownThin, title: 'Forward' }, + }; + + export const handleToggle = (selectedOption: RenderedOption) => { + for (const [key, option] of Object.entries(options)) { + if (option === selectedOption) { + $slideshowNavigation = key as SlideshowNavigation; + break; + } + } + };

Slideshow Settings

- + handleToggle(option)} + /> (false); const stopState = writable(false); - const slideshowShuffle = persisted('slideshow-shuffle', true); + const slideshowNavigation = persisted( + 'slideshow-navigation', + SlideshowNavigation.DescendingOrder, + ); const slideshowState = writable(SlideshowState.None); const showProgressBar = persisted('slideshow-show-progressbar', true); @@ -40,7 +49,7 @@ function createSlideshowStore() { } }, }, - slideshowShuffle, + slideshowNavigation, slideshowState, slideshowDelay, showProgressBar, diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte index 4b87b4cd51..f0daba676e 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte @@ -37,7 +37,7 @@ import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { AssetStore } from '$lib/stores/assets.store'; import { locale } from '$lib/stores/preferences.store'; - import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; + import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { user } from '$lib/stores/user.store'; import { downloadArchive } from '$lib/utils/asset-utils'; import { clickOutside } from '$lib/utils/click-outside'; @@ -78,7 +78,7 @@ export let data: PageData; let { isViewing: showAssetViewer, setAssetId } = assetViewingStore; - let { slideshowState, slideshowShuffle } = slideshowStore; + let { slideshowState, slideshowNavigation } = slideshowStore; $: album = data.album; $: albumId = album.id; @@ -236,7 +236,8 @@ }; const handleStartSlideshow = async () => { - const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0]; + const asset = + $slideshowNavigation === SlideshowNavigation.Shuffle ? await assetStore.getRandomAsset() : assetStore.assets[0]; if (asset) { await setAssetId(asset.id); $slideshowState = SlideshowState.PlaySlideshow;