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;