1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +00:00

feat(web): navigate assets with gestures (next/prev) (#11888)

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
kaziu687 2024-08-29 17:40:17 +02:00 committed by GitHub
parent f3e176e192
commit c008feca63
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 69 additions and 2 deletions

7
web/package-lock.json generated
View file

@ -24,6 +24,7 @@
"lodash-es": "^4.17.21",
"luxon": "^3.4.4",
"socket.io-client": "^4.7.4",
"svelte-gestures": "^5.0.4",
"svelte-i18n": "^4.0.0",
"svelte-local-storage-store": "^0.6.4",
"svelte-maplibre": "^0.9.0",
@ -7791,6 +7792,12 @@
}
}
},
"node_modules/svelte-gestures": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/svelte-gestures/-/svelte-gestures-5.0.4.tgz",
"integrity": "sha512-a6cnR46AfFZ8zZyvA38A1wBLBFI7rYuAWQnmv3yYgSdbaJK/U7JG34rSkjMCePRvf4BETJSDfMNngLs5zEAfbw==",
"license": "MIT"
},
"node_modules/svelte-hmr": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",

View file

@ -80,6 +80,7 @@
"lodash-es": "^4.17.21",
"luxon": "^3.4.4",
"socket.io-client": "^4.7.4",
"svelte-gestures": "^5.0.4",
"svelte-i18n": "^4.0.0",
"svelte-local-storage-store": "^0.6.4",
"svelte-maplibre": "^0.9.0",

View file

@ -462,6 +462,8 @@
bind:copyImage
asset={previewStackedAsset}
{preloadAssets}
onPreviousAsset={() => navigateAsset('previous')}
onNextAsset={() => navigateAsset('next')}
on:close={closeViewer}
haveFadeTransition={false}
{sharedLink}
@ -472,6 +474,8 @@
checksum={previewStackedAsset.checksum}
projectionType={previewStackedAsset.exifInfo?.projectionType}
loopVideo={true}
onPreviousAsset={() => navigateAsset('previous')}
onNextAsset={() => navigateAsset('next')}
on:close={closeViewer}
on:onVideoEnded={() => navigateAsset()}
on:onVideoStarted={handleVideoStarted}
@ -487,6 +491,8 @@
checksum={asset.checksum}
projectionType={asset.exifInfo?.projectionType}
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
onPreviousAsset={() => navigateAsset('previous')}
onNextAsset={() => navigateAsset('next')}
on:close={closeViewer}
on:onVideoEnded={() => (shouldPlayMotionPhoto = false)}
/>
@ -497,7 +503,16 @@
{:else if isShowEditor && selectedEditType === 'crop'}
<CropArea {asset} />
{:else}
<PhotoViewer bind:zoomToggle bind:copyImage {asset} {preloadAssets} on:close={closeViewer} {sharedLink} />
<PhotoViewer
bind:zoomToggle
bind:copyImage
{asset}
{preloadAssets}
onPreviousAsset={() => navigateAsset('previous')}
onNextAsset={() => navigateAsset('next')}
on:close={closeViewer}
{sharedLink}
/>
{/if}
{:else}
<VideoViewer
@ -505,6 +520,8 @@
checksum={asset.checksum}
projectionType={asset.exifInfo?.projectionType}
loopVideo={$slideshowState !== SlideshowState.PlaySlideshow}
onPreviousAsset={() => navigateAsset('previous')}
onNextAsset={() => navigateAsset('next')}
on:close={closeViewer}
on:onVideoEnded={() => navigateAsset()}
on:onVideoStarted={handleVideoStarted}

View file

@ -15,6 +15,7 @@
import { canCopyImagesToClipboard, copyImageToClipboard } from 'copy-image-clipboard';
import { onDestroy, onMount } from 'svelte';
import { t } from 'svelte-i18n';
import { type SwipeCustomEvent, swipe } from 'svelte-gestures';
import { fade } from 'svelte/transition';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
@ -24,6 +25,8 @@
export let element: HTMLDivElement | undefined = undefined;
export let haveFadeTransition = true;
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
export let onPreviousAsset: (() => void) | null = null;
export let onNextAsset: (() => void) | null = null;
export let copyImage: (() => Promise<void>) | null = null;
export let zoomToggle: (() => void) | null = null;
@ -110,6 +113,18 @@
handlePromiseError(copyImage());
};
const onSwipe = (event: SwipeCustomEvent) => {
if ($photoZoomState.currentZoom > 1) {
return;
}
if (onNextAsset && event.detail.direction === 'left') {
onNextAsset();
}
if (onPreviousAsset && event.detail.direction === 'right') {
onPreviousAsset();
}
};
onMount(() => {
const onload = () => {
imageLoaded = true;
@ -166,6 +181,8 @@
<img
bind:this={$photoViewer}
src={assetFileUrl}
use:swipe
on:swipe={onSwipe}
alt={$getAltText(asset)}
class="h-full w-full {$slideshowState === SlideshowState.None
? 'object-contain'

View file

@ -5,12 +5,16 @@
import { handleError } from '$lib/utils/handle-error';
import { AssetMediaSize } from '@immich/sdk';
import { createEventDispatcher, tick } from 'svelte';
import { swipe } from 'svelte-gestures';
import type { SwipeCustomEvent } from 'svelte-gestures';
import { fade } from 'svelte/transition';
import { t } from 'svelte-i18n';
export let assetId: string;
export let loopVideo: boolean;
export let checksum: string;
export let onPreviousAsset: () => void;
export let onNextAsset: () => void;
let element: HTMLVideoElement | undefined = undefined;
let isVideoLoading = true;
@ -49,6 +53,15 @@
handleError(error, $t('errors.unable_to_play_video'));
}
};
const onSwipe = (event: SwipeCustomEvent) => {
if (event.detail.direction === 'left') {
onNextAsset();
}
if (event.detail.direction === 'right') {
onPreviousAsset();
}
};
</script>
<div transition:fade={{ duration: 150 }} class="flex h-full select-none place-content-center place-items-center">
@ -59,6 +72,8 @@
playsinline
controls
class="h-full object-contain"
use:swipe
on:swipe={onSwipe}
on:canplay={(e) => handleCanPlay(e.currentTarget)}
on:ended={() => dispatch('onVideoEnded')}
on:volumechange={(e) => {

View file

@ -8,10 +8,20 @@
export let projectionType: string | null | undefined;
export let checksum: string;
export let loopVideo: boolean;
export let onPreviousAsset: () => void;
export let onNextAsset: () => void;
</script>
{#if projectionType === ProjectionType.EQUIRECTANGULAR}
<PanoramaViewer asset={{ id: assetId, type: AssetTypeEnum.Video }} />
{:else}
<VideoNativeViewer {loopVideo} {checksum} {assetId} on:onVideoEnded on:onVideoStarted />
<VideoNativeViewer
{loopVideo}
{checksum}
{assetId}
{onPreviousAsset}
{onNextAsset}
on:onVideoEnded
on:onVideoStarted
/>
{/if}