From bd708249614028ccbfdaa8c6646ead1c55d44bb5 Mon Sep 17 00:00:00 2001 From: Dag Stuan Date: Tue, 17 Jun 2025 16:09:34 +0200 Subject: [PATCH] fix(web): more refactoring and tweaking of the memory viewer. (#19214) * Fix fade in for video-native-viewer. The previous implementation never actually faded in the video element. Fix this by ensuring the video element is only added to the DOM after mounting, so Svelte can handle the fade-in transition correctly. * Refactor asset viewing in memory page. Split photo and video viewing into separate components to ensure they work similarly to the assets viewer. The previous implementation faded out the assets, while the assets-viewer only fades assets in. For images, add a spinner while waiting for the image to load, before adding the image to the DOM. For videos, add the video to the DOM after mounting the component. In both cases, the assets fade in smoothly, like the regular assets viewer. * fix: styling --------- Co-authored-by: Alex --- .../asset-viewer/video-native-viewer.svelte | 112 +++++++++--------- .../memory-page/memory-photo-viewer.svelte | 69 +++++++++++ .../memory-page/memory-video-viewer.svelte | 40 +++++++ .../memory-page/memory-viewer.svelte | 92 +++++++------- .../shared-components/control-app-bar.svelte | 3 - 5 files changed, 209 insertions(+), 107 deletions(-) create mode 100644 web/src/lib/components/memory-page/memory-photo-viewer.svelte create mode 100644 web/src/lib/components/memory-page/memory-video-viewer.svelte diff --git a/web/src/lib/components/asset-viewer/video-native-viewer.svelte b/web/src/lib/components/asset-viewer/video-native-viewer.svelte index 8205c8c353..4b8bb40f77 100644 --- a/web/src/lib/components/asset-viewer/video-native-viewer.svelte +++ b/web/src/lib/components/asset-viewer/video-native-viewer.svelte @@ -2,6 +2,7 @@ import FaceEditor from '$lib/components/asset-viewer/face-editor/face-editor.svelte'; import VideoRemoteViewer from '$lib/components/asset-viewer/video-remote-viewer.svelte'; import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte'; + import { assetViewerFadeDuration } from '$lib/constants'; import { castManager } from '$lib/managers/cast-manager.svelte'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; import { loopVideo as loopVideoPreference, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store'; @@ -41,8 +42,11 @@ let assetFileUrl = $state(''); let forceMuted = $state(false); let isScrubbing = $state(false); + let showVideo = $state(false); onMount(() => { + // Show video after mount to ensure fading in. + showVideo = true; assetFileUrl = getAssetPlaybackUrl({ id: assetId, cacheKey }); if (videoPlayer) { forceMuted = false; @@ -102,59 +106,61 @@ }); -
- {#if castManager.isCasting} -
- -
- {:else} - - - {#if isLoading} -
- +{#if showVideo} +
+ {#if castManager.isCasting} +
+
- {/if} + {:else} + - {#if isFaceEditMode.value} - + {#if isLoading} +
+ +
+ {/if} + + {#if isFaceEditMode.value} + + {/if} {/if} - {/if} -
+
+{/if} diff --git a/web/src/lib/components/memory-page/memory-photo-viewer.svelte b/web/src/lib/components/memory-page/memory-photo-viewer.svelte new file mode 100644 index 0000000000..b0b1dc98a6 --- /dev/null +++ b/web/src/lib/components/memory-page/memory-photo-viewer.svelte @@ -0,0 +1,69 @@ + + +{#if !imageLoaded} + + +{/if} + +{#if !imageLoaded} +
+ +
+{:else if imageLoaded} +
+ {$getAltText(asset)} +
+{/if} + + diff --git a/web/src/lib/components/memory-page/memory-video-viewer.svelte b/web/src/lib/components/memory-page/memory-video-viewer.svelte new file mode 100644 index 0000000000..7758b067f3 --- /dev/null +++ b/web/src/lib/components/memory-page/memory-video-viewer.svelte @@ -0,0 +1,40 @@ + + +{#if showVideo} +
+ +
+{/if} diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index 77ccebe42b..f1a15f4429 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -4,6 +4,8 @@ import { intersectionObserver } from '$lib/actions/intersection-observer'; import { resizeObserver } from '$lib/actions/resize-observer'; import { shortcuts } from '$lib/actions/shortcut'; + import MemoryPhotoViewer from '$lib/components/memory-page/memory-photo-viewer.svelte'; + import MemoryVideoViewer from '$lib/components/memory-page/memory-video-viewer.svelte'; import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte'; import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte'; import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte'; @@ -23,7 +25,7 @@ notificationController, NotificationType, } from '$lib/components/shared-components/notification/notification'; - import { AppRoute, assetViewerFadeDuration, QueryParameter } from '$lib/constants'; + import { AppRoute, QueryParameter } from '$lib/constants'; import { authManager } from '$lib/managers/auth-manager.svelte'; import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; @@ -31,9 +33,8 @@ import { type MemoryAsset, memoryStore } from '$lib/stores/memory.store.svelte'; import { locale, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store'; import { preferences } from '$lib/stores/user.store'; - import { getAssetPlaybackUrl, getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils'; + import { getAssetThumbnailUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils'; import { cancelMultiselect } from '$lib/utils/asset-utils'; - import { getAltText } from '$lib/utils/thumbnail-util'; import { fromISODateTimeUTC, toTimelineAsset } from '$lib/utils/timeline-util'; import { AssetMediaSize, getAssetInfo } from '@immich/sdk'; import { IconButton } from '@immich/ui'; @@ -59,7 +60,6 @@ import { DateTime } from 'luxon'; import { t } from 'svelte-i18n'; import { Tween } from 'svelte/motion'; - import { fade } from 'svelte/transition'; let memoryGallery: HTMLElement | undefined = $state(); let memoryWrapper: HTMLElement | undefined = $state(); @@ -363,15 +363,16 @@ {/snippet}
- handlePromiseError(handleAction('PlayPauseButtonClick', paused ? 'play' : 'pause'))} - class="hover:text-black" - /> +
+ handlePromiseError(handleAction('PlayPauseButtonClick', paused ? 'play' : 'pause'))} + /> +
{#each current.memory.assets as asset, index (asset.id)} @@ -385,20 +386,23 @@ {(current.assetIndex + 1).toLocaleString($locale)}/{current.memory.assets.length.toLocaleString($locale)}

- ($videoViewerMuted = !$videoViewerMuted)} - /> + +
+ ($videoViewerMuted = !$videoViewerMuted)} + /> +
{#if galleryInView}
@@ -409,7 +413,6 @@ >
{#key current.asset.id} -
- {#if current.asset.isVideo} - - {:else} - {$getAltText(current.asset)} - {/if} -
+ {#if current.asset.isVideo} + + {:else} + + {/if} {/key}
{#if current.previous} -
+
{/if} {#if current.next} -
+
@@ -626,13 +617,12 @@
@@ -90,7 +88,6 @@ variant="ghost" icon={backIcon} size="large" - class={buttonClass} /> {/if} {@render leading?.()}