diff --git a/e2e/src/web/specs/photo-viewer.e2e-spec.ts b/e2e/src/web/specs/photo-viewer.e2e-spec.ts index c8a9b42b2a..3f9bb4237a 100644 --- a/e2e/src/web/specs/photo-viewer.e2e-spec.ts +++ b/e2e/src/web/specs/photo-viewer.e2e-spec.ts @@ -3,7 +3,7 @@ import { Page, expect, test } from '@playwright/test'; import { utils } from 'src/utils'; function imageLocator(page: Page) { - return page.getByAltText('Image taken on').locator('visible=true'); + return page.getByAltText('Image taken').locator('visible=true'); } test.describe('Photo Viewer', () => { let admin: LoginResponseDto; diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 833334cf13..c1e0cbf2b1 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -10,6 +10,7 @@ import { AppRoute, AssetAction, ProjectionType } from '$lib/constants'; import { activityManager } from '$lib/managers/activity-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; + import { eventManager } from '$lib/managers/event-manager.svelte'; import { preloadManager } from '$lib/managers/PreloadManager.svelte'; import { closeEditorCofirm } from '$lib/stores/asset-editor.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; @@ -51,8 +52,6 @@ import SlideshowBar from './slideshow-bar.svelte'; import VideoViewer from './video-wrapper-viewer.svelte'; - type HasAsset = boolean; - export type AssetCursor = { current: AssetResponseDto; nextAsset: AssetResponseDto | undefined | null; @@ -69,10 +68,9 @@ preAction?: PreAction | undefined; onAction?: OnAction | undefined; showCloseButton?: boolean; - onClose: (asset: AssetResponseDto) => void; - onNext: () => Promise; - onPrevious: () => Promise; - onRandom: () => Promise<{ id: string } | undefined>; + onClose?: (asset: AssetResponseDto) => void; + onNavigateToAsset?: (asset: AssetResponseDto | undefined | null) => Promise; + onRandom?: () => Promise<{ id: string } | undefined>; copyImage?: () => Promise; } @@ -87,8 +85,7 @@ onAction = undefined, showCloseButton, onClose, - onNext, - onPrevious, + onNavigateToAsset, onRandom, copyImage = $bindable(), }: Props = $props(); @@ -105,6 +102,8 @@ const stackSelectedThumbnailSize = 65; let asset = $derived(cursor.current); + let nextAsset = $derived(cursor.nextAsset); + let previousAsset = $derived(cursor.previousAsset); let appearsInAlbums: AlbumResponseDto[] = $state([]); let shouldPlayMotionPhoto = $state(false); let sharedLink = getSharedLink(); @@ -220,7 +219,7 @@ }; const closeViewer = () => { - onClose(asset); + onClose?.(asset); }; const closeEditor = () => { @@ -252,14 +251,19 @@ if ($slideshowState === SlideshowState.PlaySlideshow && $slideshowNavigation === SlideshowNavigation.Shuffle) { hasNext = order === 'previous' ? slideshowHistory.previous() : slideshowHistory.next(); if (!hasNext) { - const asset = await onRandom(); + const asset = await onRandom?.(); if (asset) { slideshowHistory.queue(asset); hasNext = true; } } + } else if (onNavigateToAsset) { + hasNext = + order === 'previous' + ? await onNavigateToAsset(cursor.previousAsset) + : await onNavigateToAsset(cursor.nextAsset); } else { - hasNext = order === 'previous' ? await onPrevious() : await onNext(); + hasNext = false; } if ($slideshowState === SlideshowState.PlaySlideshow) { @@ -396,7 +400,6 @@ await ocrManager.getAssetOcr(asset.id); } }; - $effect(() => { // eslint-disable-next-line @typescript-eslint/no-unused-expressions asset; @@ -404,6 +407,34 @@ preloadManager.preload(cursor.nextAsset); preloadManager.preload(cursor.previousAsset); }); + + $effect(() => { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + asset.id; + if (viewerKind !== 'PhotoViewer' && viewerKind !== 'ImagePanaramaViewer') { + eventManager.emit('AssetViewerFree'); + } + }); + + const viewerKind = $derived.by(() => { + if (previewStackedAsset) { + return asset.type === AssetTypeEnum.Image ? 'StackPhotoViewer' : 'StackVideoViewer'; + } + if (asset.type === AssetTypeEnum.Image) { + if (shouldPlayMotionPhoto && asset.livePhotoVideoId) { + return 'LiveVideoViewer'; + } else if ( + asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || + (asset.originalPath && asset.originalPath.toLowerCase().endsWith('.insp')) + ) { + return 'ImagePanaramaViewer'; + } else if (isShowEditor && selectedEditType === 'crop') { + return 'CropArea'; + } + return 'PhotoViewer'; + } + return 'VideoViewer'; + }); @@ -449,7 +480,7 @@ {/if} {#if $slideshowState != SlideshowState.None} -
+
assetViewerHtmlElement?.requestFullscreen?.()} @@ -460,110 +491,99 @@
{/if} - {#if $slideshowState === SlideshowState.None && showNavigation && !isShowEditor} -
+ {#if $slideshowState === SlideshowState.None && showNavigation && !isShowEditor && previousAsset} +
navigateAsset('previous')} />
{/if}
- {#if previewStackedAsset} - {#key previewStackedAsset.id} - {#if previewStackedAsset.type === AssetTypeEnum.Image} - navigateAsset('previous')} - onNextAsset={() => navigateAsset('next')} - haveFadeTransition={false} - {sharedLink} - /> - {:else} - navigateAsset('previous')} - onNextAsset={() => navigateAsset('next')} - onClose={closeViewer} - onVideoEnded={() => navigateAsset()} - onVideoStarted={handleVideoStarted} - {playOriginalVideo} - /> - {/if} - {/key} - {:else} - {#key asset.id} - {#if asset.type === AssetTypeEnum.Image} - {#if shouldPlayMotionPhoto && asset.livePhotoVideoId} - navigateAsset('previous')} - onNextAsset={() => navigateAsset('next')} - onVideoEnded={() => (shouldPlayMotionPhoto = false)} - {playOriginalVideo} - /> - {:else if asset.exifInfo?.projectionType === ProjectionType.EQUIRECTANGULAR || (asset.originalPath && asset.originalPath - .toLowerCase() - .endsWith('.insp'))} - - {:else if isShowEditor && selectedEditType === 'crop'} - - {:else} - navigateAsset('previous')} - onNextAsset={() => navigateAsset('next')} - {sharedLink} - haveFadeTransition={$slideshowState !== SlideshowState.None && $slideshowTransition} - /> - {/if} - {:else} - navigateAsset('previous')} - onNextAsset={() => navigateAsset('next')} - onClose={closeViewer} - onVideoEnded={() => navigateAsset()} - onVideoStarted={handleVideoStarted} - {playOriginalVideo} - /> - {/if} + {#if viewerKind === 'StackPhotoViewer'} + navigateAsset('previous')} + onNextAsset={() => navigateAsset('next')} + haveFadeTransition={false} + {sharedLink} + /> + {:else if viewerKind === 'StackVideoViewer'} + navigateAsset('previous')} + onNextAsset={() => navigateAsset('next')} + onClose={closeViewer} + onVideoEnded={() => navigateAsset()} + onVideoStarted={handleVideoStarted} + {playOriginalVideo} + /> + {:else if viewerKind === 'LiveVideoViewer'} + navigateAsset('previous')} + onNextAsset={() => navigateAsset('next')} + onVideoEnded={() => (shouldPlayMotionPhoto = false)} + {playOriginalVideo} + /> + {:else if viewerKind === 'ImagePanaramaViewer'} + + {:else if viewerKind === 'CropArea'} + + {:else if viewerKind === 'PhotoViewer'} + navigateAsset('previous')} + onNextAsset={() => navigateAsset('next')} + {sharedLink} + haveFadeTransition={$slideshowState !== SlideshowState.None && $slideshowTransition} + onFree={() => eventManager.emit('AssetViewerFree')} + /> + {:else if viewerKind === 'VideoViewer'} + navigateAsset('previous')} + onNextAsset={() => navigateAsset('next')} + onClose={closeViewer} + onVideoEnded={() => navigateAsset()} + onVideoStarted={handleVideoStarted} + {playOriginalVideo} + /> + {/if} - {#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || activityManager.commentCount > 0) && !activityManager.isLoading} -
- -
- {/if} + {#if $slideshowState === SlideshowState.None && isShared && ((album && album.isActivityEnabled) || activityManager.commentCount > 0) && !activityManager.isLoading} +
+ +
+ {/if} - {#if $slideshowState === SlideshowState.None && asset.type === AssetTypeEnum.Image && !isShowEditor && ocrManager.hasOcrData} -
- -
- {/if} - {/key} + {#if $slideshowState === SlideshowState.None && asset.type === AssetTypeEnum.Image && !isShowEditor && ocrManager.hasOcrData} +
+ +
{/if}
- {#if $slideshowState === SlideshowState.None && showNavigation && !isShowEditor} + {#if $slideshowState === SlideshowState.None && showNavigation && !isShowEditor && nextAsset}
navigateAsset('next')} />
diff --git a/web/src/lib/components/asset-viewer/photo-viewer.svelte b/web/src/lib/components/asset-viewer/photo-viewer.svelte index 370d70e663..e9f8f66bab 100644 --- a/web/src/lib/components/asset-viewer/photo-viewer.svelte +++ b/web/src/lib/components/asset-viewer/photo-viewer.svelte @@ -22,7 +22,7 @@ import { toTimelineAsset } from '$lib/utils/timeline-util'; import { AssetMediaSize, type SharedLinkResponseDto } from '@immich/sdk'; import { LoadingSpinner, toastManager } from '@immich/ui'; - import { onDestroy, onMount } from 'svelte'; + import { onDestroy, onMount, untrack } from 'svelte'; import { useSwipe, type SwipeCustomEvent } from 'svelte-gestures'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; @@ -34,6 +34,10 @@ haveFadeTransition?: boolean; sharedLink?: SharedLinkResponseDto | undefined; onPreviousAsset?: (() => void) | null; + onFree?: (() => void) | null; + onBusy?: (() => void) | null; + onError?: (() => void) | null; + onLoad?: (() => void) | null; onNextAsset?: (() => void) | null; copyImage?: () => Promise; zoomToggle?: (() => void) | null; @@ -46,6 +50,10 @@ sharedLink = undefined, onPreviousAsset = null, onNextAsset = null, + onFree = null, + onBusy = null, + onError = null, + onLoad = null, copyImage = $bindable(), zoomToggle = $bindable(), }: Props = $props(); @@ -156,16 +164,23 @@ }; const onload = () => { + onLoad?.(); + onFree?.(); imageLoaded = true; originalImageLoaded = targetImageSize === AssetMediaSize.Fullsize || targetImageSize === 'original'; }; const onerror = () => { + onError?.(); + onFree?.(); imageError = imageLoaded = true; }; onMount(() => { return () => { + if (!imageLoaded && !imageError) { + onFree?.(); + } preloadManager.cancelPreloadUrl(imageLoaderUrl); }; }); @@ -180,10 +195,16 @@ let lastUrl: string | undefined | null; $effect(() => { + if (!lastUrl) { + untrack(() => onBusy?.()); + } if (lastUrl && lastUrl !== imageLoaderUrl) { - imageLoaded = false; - originalImageLoaded = false; - imageError = false; + untrack(() => { + imageLoaded = false; + originalImageLoaded = false; + imageError = false; + onBusy?.(); + }); } lastUrl = imageLoaderUrl; }); diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index 34c6ee18db..9fba872e0a 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -26,7 +26,7 @@ import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { type MemoryAsset, memoryStore } from '$lib/stores/memory.store.svelte'; + import { memoryStore, type MemoryAsset } from '$lib/stores/memory.store.svelte'; import { locale, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store'; import { preferences } from '$lib/stores/user.store'; import { getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils'; @@ -651,8 +651,6 @@ bind:this={memoryGallery} > Promise.resolve(false)} - onNext={() => Promise.resolve(false)} - onRandom={() => Promise.resolve(undefined)} - onClose={() => {}} /> {/await} {/await} diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index 8a26288538..18a2e961bc 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -38,9 +38,7 @@ onIntersected?: (() => void) | undefined; showAssetName?: boolean; isShowDeleteConfirmation?: boolean; - onPrevious?: (() => Promise<{ id: string } | undefined>) | undefined; - onNext?: (() => Promise<{ id: string } | undefined>) | undefined; - onRandom?: (() => Promise<{ id: string } | undefined>) | undefined; + onNavigateToAsset?: (asset: AssetResponseDto | undefined) => Promise; onReload?: (() => void) | undefined; pageHeaderOffset?: number; slidingWindowOffset?: number; @@ -57,9 +55,7 @@ onIntersected = undefined, showAssetName = false, isShowDeleteConfirmation = $bindable(false), - onPrevious = undefined, - onNext = undefined, - onRandom = undefined, + onNavigateToAsset, onReload = undefined, slidingWindowOffset = 0, pageHeaderOffset = 0, @@ -89,7 +85,7 @@ return top + pageHeaderOffset < window.bottom && top + geo.getHeight(i) > window.top; }; - let currentIndex = 0; + let currentIndex = $state(0); if (initialAssetId && assets.length > 0) { const index = assets.findIndex(({ id }) => id === initialAssetId); if (index !== -1) { @@ -298,48 +294,15 @@ })(), ); - const handleNext = async (): Promise => { - try { - let asset: { id: string } | undefined; - if (onNext) { - asset = await onNext(); - } else { - if (currentIndex >= assets.length - 1) { - return false; - } - - currentIndex = currentIndex + 1; - asset = currentIndex < assets.length ? assets[currentIndex] : undefined; - } - - if (!asset) { - return false; - } - - await navigateToAsset(asset); - return true; - } catch (error) { - handleError(error, $t('errors.cannot_navigate_next_asset')); - return false; - } - }; - const handleRandom = async (): Promise<{ id: string } | undefined> => { try { - let asset: { id: string } | undefined; - if (onRandom) { - asset = await onRandom(); - } else { - if (assets.length > 0) { - const randomIndex = Math.floor(Math.random() * assets.length); - asset = assets[randomIndex]; - } - } - - if (!asset) { + if (assets.length === 0) { return; } + const randomIndex = Math.floor(Math.random() * assets.length); + const asset = assets[randomIndex]; + await navigateToAsset(asset); return asset; } catch (error) { @@ -348,30 +311,13 @@ } }; - const handlePrevious = async (): Promise => { - try { - let asset: { id: string } | undefined; - if (onPrevious) { - asset = await onPrevious(); - } else { - if (currentIndex <= 0) { - return false; - } - - currentIndex = currentIndex - 1; - asset = currentIndex >= 0 ? assets[currentIndex] : undefined; - } - - if (!asset) { - return false; - } - - await navigateToAsset(asset); + const handleNavigateToAsset = async (target: AssetResponseDto | undefined | null) => { + if (target) { + currentIndex = assets.indexOf(target); + await (onNavigateToAsset ? onNavigateToAsset(target) : navigateToAsset(target)); return true; - } catch (error) { - handleError(error, $t('errors.cannot_navigate_previous_asset')); - return false; } + return false; }; const navigateToAsset = async (asset?: { id: string }) => { @@ -393,9 +339,9 @@ if (assets.length === 0) { await goto(AppRoute.PHOTOS); } else if (currentIndex === assets.length) { - await handlePrevious(); + await handleNavigateToAsset(assetCursor.previousAsset); } else { - await setAssetId(assets[currentIndex].id); + await handleNavigateToAsset(assetCursor.nextAsset); } break; } @@ -546,8 +492,7 @@ { assetViewingStore.showAssetViewer(false); diff --git a/web/src/lib/components/timeline/TimelineAssetViewer.svelte b/web/src/lib/components/timeline/TimelineAssetViewer.svelte index 082fe57db5..de20d84722 100644 --- a/web/src/lib/components/timeline/TimelineAssetViewer.svelte +++ b/web/src/lib/components/timeline/TimelineAssetViewer.svelte @@ -5,6 +5,7 @@ import { assetCacheManager } from '$lib/managers/AssetCacheManager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; + import { eventManager } from '$lib/managers/event-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { websocketEvents } from '$lib/stores/websocket'; @@ -96,8 +97,11 @@ if (!targetAsset) { return false; } - + let waitForAssetViewerFree = new Promise((resolve) => { + eventManager.once('AssetViewerFree', () => resolve()); + }); await navigate({ targetRoute: 'current', assetId: targetAsset.id }); + await waitForAssetViewerFree; return true; }; @@ -228,8 +232,7 @@ handleAction(action); assetCacheManager.invalidate(); }} - onPrevious={() => handleNavigateToAsset(assetCursor.previousAsset)} - onNext={() => handleNavigateToAsset(assetCursor.nextAsset)} + onNavigateToAsset={handleNavigateToAsset} onRandom={handleRandom} onClose={handleClose} /> diff --git a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte index f43aecbd2c..e007c60794 100644 --- a/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte +++ b/web/src/lib/components/utilities-page/duplicates/duplicates-compare-control.svelte @@ -44,21 +44,11 @@ assetViewingStore.showAssetViewer(false); }); - const onNext = async () => { - const index = getAssetIndex($viewingAsset.id) + 1; - if (index >= assets.length) { + const handleNavigateToAsset = async (asset: AssetResponseDto | undefined | null) => { + if (!asset) { return false; } - await onViewAsset(assets[index]); - return true; - }; - - const onPrevious = async () => { - const index = getAssetIndex($viewingAsset.id) - 1; - if (index < 0) { - return false; - } - await onViewAsset(assets[index]); + await onViewAsset(asset); return true; }; @@ -222,8 +212,7 @@ 1} - {onNext} - {onPrevious} + onNavigateToAsset={handleNavigateToAsset} {onRandom} onClose={() => { assetViewingStore.showAssetViewer(false); diff --git a/web/src/lib/managers/event-manager.svelte.ts b/web/src/lib/managers/event-manager.svelte.ts index 6038c3c3f0..40e3f00ff9 100644 --- a/web/src/lib/managers/event-manager.svelte.ts +++ b/web/src/lib/managers/event-manager.svelte.ts @@ -43,6 +43,8 @@ export type Events = { // confirmed permanently deleted from server UserAdminDeleted: [{ id: string }]; + AssetViewerFree: []; + SystemConfigUpdate: [SystemConfigDto]; LibraryCreate: [LibraryResponseDto]; diff --git a/web/src/lib/stores/asset-viewing.store.ts b/web/src/lib/stores/asset-viewing.store.ts index 00e0224a0e..3cd2cd9579 100644 --- a/web/src/lib/stores/asset-viewing.store.ts +++ b/web/src/lib/stores/asset-viewing.store.ts @@ -5,7 +5,6 @@ import { readonly, writable } from 'svelte/store'; function createAssetViewingStore() { const viewingAssetStoreState = writable(); - const viewState = writable(false); const gridScrollTarget = writable(); diff --git a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte index 27dc10be57..87f969b1bf 100644 --- a/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/map/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -24,7 +24,6 @@ let { isViewing: showAssetViewer, asset: viewingAsset, setAssetId } = assetViewingStore; let viewingAssets: string[] = $state([]); - let viewingAssetCursor = 0; onDestroy(() => { assetViewingStore.showAssetViewer(false); @@ -36,27 +35,16 @@ async function onViewAssets(assetIds: string[]) { viewingAssets = assetIds; - viewingAssetCursor = 0; await setAssetId(assetIds[0]); } - async function navigateNext() { - if (viewingAssetCursor < viewingAssets.length - 1) { - await setAssetId(viewingAssets[++viewingAssetCursor]); - await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); - return true; + const handleNavigateToAsset = async (currentAsset: AssetResponseDto | undefined | null) => { + if (!currentAsset) { + return false; } - return false; - } - - async function navigatePrevious() { - if (viewingAssetCursor > 0) { - await setAssetId(viewingAssets[--viewingAssetCursor]); - await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); - return true; - } - return false; - } + await navigate({ targetRoute: 'current', assetId: currentAsset.id }); + return true; + }; async function navigateRandom() { if (viewingAssets.length <= 0) { @@ -138,13 +126,12 @@
- {#if $showAssetViewer} + {#if $showAssetViewer && assetCursor.current} {#await import('$lib/components/asset-viewer/asset-viewer.svelte') then { default: AssetViewer }} 1} - onNext={navigateNext} - onPrevious={navigatePrevious} + onNavigateToAsset={handleNavigateToAsset} onRandom={navigateRandom} onClose={() => { assetViewingStore.showAssetViewer(false); diff --git a/web/src/routes/(user)/utilities/large-files/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/utilities/large-files/[[photos=photos]]/[[assetId=id]]/+page.svelte index 2157c79055..90ed7d9ef1 100644 --- a/web/src/routes/(user)/utilities/large-files/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/utilities/large-files/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -8,7 +8,8 @@ import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { handlePromiseError } from '$lib/utils'; import { navigate } from '$lib/utils/navigation'; - import { getAssetInfo, type AssetResponseDto } from '@immich/sdk'; + import type { AssetResponseDto } from '@immich/sdk'; + import { getAssetInfo } from '@immich/sdk'; import { untrack } from 'svelte'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; @@ -22,29 +23,17 @@ let assets = $derived(data.assets); let asset = $derived(data.asset); const { isViewing: showAssetViewer, asset: viewingAsset, setAsset } = assetViewingStore; - const getAssetIndex = (id: string) => assets.findIndex((asset) => asset.id === id); - $effect(() => { if (asset) { setAsset(asset); } }); - const onNext = async () => { - const index = getAssetIndex($viewingAsset.id) + 1; - if (index >= assets.length) { + const handleNavigateToAsset = async (asset: AssetResponseDto | undefined | null) => { + if (!asset) { return false; } - await onViewAsset(assets[index]); - return true; - }; - - const onPrevious = async () => { - const index = getAssetIndex($viewingAsset.id) - 1; - if (index < 0) { - return false; - } - await onViewAsset(assets[index]); + await onViewAsset(asset); return true; }; @@ -142,9 +131,8 @@ 1} - {onNext} - {onPrevious} {onRandom} {onAction} onClose={() => {