diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index ac512b6766..cddf798eeb 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -66,7 +66,7 @@ onClose: (asset: AssetResponseDto) => void; onNext: () => Promise; onPrevious: () => Promise; - onRandom: () => Promise; + onRandom: () => Promise<{ id: string } | undefined>; copyImage?: () => Promise; } @@ -89,7 +89,7 @@ copyImage = $bindable(), }: Props = $props(); - const { setAsset } = assetViewingStore; + const { setAssetId } = assetViewingStore; const { restartProgress: restartSlideshowProgress, stopProgress: stopSlideshowProgress, @@ -210,7 +210,7 @@ slideshowStateUnsubscribe = slideshowState.subscribe((value) => { if (value === SlideshowState.PlaySlideshow) { slideshowHistory.reset(); - slideshowHistory.queue(asset); + slideshowHistory.queue(toTimelineAsset(asset)); handlePromiseError(handlePlaySlideshow()); } else if (value === SlideshowState.StopSlideshow) { handlePromiseError(handleStopSlideshow()); @@ -220,7 +220,7 @@ shuffleSlideshowUnsubscribe = slideshowNavigation.subscribe((value) => { if (value === SlideshowNavigation.Shuffle) { slideshowHistory.reset(); - slideshowHistory.queue(asset); + slideshowHistory.queue(toTimelineAsset(asset)); } }); @@ -335,8 +335,7 @@ let assetViewerHtmlElement = $state(); const slideshowHistory = new SlideshowHistory((asset) => { - setAsset(asset); - $restartSlideshowProgress = true; + handlePromiseError(setAssetId(asset.id).then(() => ($restartSlideshowProgress = true))); }); const handleVideoStarted = () => { diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index cdc10ccfd0..99ab4ee4b8 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -26,14 +26,15 @@ import { AppRoute, QueryParameter } from '$lib/constants'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { type Viewport } from '$lib/stores/assets-store.svelte'; + import { type TimelineAsset, type Viewport } from '$lib/stores/assets-store.svelte'; 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 { getAssetPlaybackUrl, getAssetThumbnailUrl, getKey, handlePromiseError, memoryLaneTitle } from '$lib/utils'; import { cancelMultiselect } from '$lib/utils/asset-utils'; - import { fromLocalDateTime } from '$lib/utils/timeline-util'; - import { AssetMediaSize, type AssetResponseDto, AssetTypeEnum } from '@immich/sdk'; + import { getAltText } from '$lib/utils/thumbnail-util'; + import { fromLocalDateTime, toTimelineAsset } from '$lib/utils/timeline-util'; + import { AssetMediaSize, getAssetInfo } from '@immich/sdk'; import { IconButton } from '@immich/ui'; import { mdiCardsOutline, @@ -66,6 +67,11 @@ let playerInitialized = $state(false); let paused = $state(false); let current = $state(undefined); + let currentMemoryAssetFull = $derived.by(async () => + current?.asset ? await getAssetInfo({ id: current?.asset.id, key: getKey() }) : undefined, + ); + let currentTimelineAssets = $derived(current?.memory.assets.map((a) => toTimelineAsset(a)) || []); + let isSaved = $derived(current?.memory.isSaved); let viewerHeight = $state(0); @@ -73,11 +79,11 @@ const viewport: Viewport = $state({ width: 0, height: 0 }); // need to include padding in the viewport for gallery const galleryViewport: Viewport = $derived({ height: viewport.height, width: viewport.width - 32 }); - const assetInteraction = new AssetInteraction(); + const assetInteraction = new AssetInteraction(); let progressBarController: Tween | undefined = $state(undefined); let videoPlayer: HTMLVideoElement | undefined = $state(); - const asHref = (asset: AssetResponseDto) => `?${QueryParameter.ID}=${asset.id}`; - const handleNavigate = async (asset?: AssetResponseDto) => { + const asHref = (asset: { id: string }) => `?${QueryParameter.ID}=${asset.id}`; + const handleNavigate = async (asset?: { id: string }) => { if ($isViewing) { return asset; } @@ -88,9 +94,9 @@ await goto(asHref(asset)); }; - const setProgressDuration = (asset: AssetResponseDto) => { - if (asset.type === AssetTypeEnum.Video) { - const timeParts = asset.duration.split(':').map(Number); + const setProgressDuration = (asset: TimelineAsset) => { + if (asset.isVideo) { + const timeParts = asset.duration!.split(':').map(Number); const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000; progressBarController = new Tween(0, { duration: (from: number, to: number) => (to ? durationInMilliseconds * (to - from) : 0), @@ -106,7 +112,8 @@ const handleNextMemory = () => handleNavigate(current?.nextMemory?.assets[0]); const handlePreviousMemory = () => handleNavigate(current?.previousMemory?.assets[0]); const handleEscape = async () => goto(AppRoute.PHOTOS); - const handleSelectAll = () => assetInteraction.selectAssets(current?.memory.assets || []); + const handleSelectAll = () => + assetInteraction.selectAssets(current?.memory.assets.map((a) => toTimelineAsset(a)) || []); const handleAction = async (callingContext: string, action: 'reset' | 'pause' | 'play') => { // leaving these log statements here as comments. Very useful to figure out what's going on during dev! // console.log(`handleAction[${callingContext}] called with: ${action}`); @@ -239,7 +246,7 @@ }; const initPlayer = () => { - const isVideoAssetButPlayerHasNotLoadedYet = current && current.asset.type === AssetTypeEnum.Video && !videoPlayer; + const isVideoAssetButPlayerHasNotLoadedYet = current && current.asset.isVideo && !videoPlayer; if (playerInitialized || isVideoAssetButPlayerHasNotLoadedYet) { return; } @@ -439,7 +446,7 @@
{#key current.asset.id}
- {#if current.asset.type === AssetTypeEnum.Video} + {#if current.asset.isVideo} {:else} - {current.asset.exifInfo?.description} + {#await currentMemoryAssetFull then asset} + {$getAltText(asset)} + {/await} {/if}
{/key} @@ -547,8 +556,10 @@ {fromLocalDateTime(current.memory.assets[0].localDateTime).toLocaleString(DateTime.DATE_FULL)}

- {current.asset.exifInfo?.city || ''} - {current.asset.exifInfo?.country || ''} + {#await currentMemoryAssetFull then asset} + {asset?.exifInfo?.city || ''} + {asset?.exifInfo?.country || ''} + {/await}

@@ -619,7 +630,7 @@ import { goto } from '$app/navigation'; import type { Action } from '$lib/components/asset-viewer/actions/action'; + import ImmichLogoSmallLink from '$lib/components/shared-components/immich-logo-small-link.svelte'; import { AppRoute, AssetAction } from '$lib/constants'; + import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import type { Viewport } from '$lib/stores/assets-store.svelte'; import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; import { getKey, handlePromiseError } from '$lib/utils'; - import { downloadArchive } from '$lib/utils/asset-utils'; + import { cancelMultiselect, downloadArchive } from '$lib/utils/asset-utils'; import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader'; import { handleError } from '$lib/utils/handle-error'; - import { addSharedLinkAssets, type AssetResponseDto, type SharedLinkResponseDto } from '@immich/sdk'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; + import { addSharedLinkAssets, getAssetInfo, type SharedLinkResponseDto } from '@immich/sdk'; import { mdiArrowLeft, mdiFileImagePlusOutline, mdiFolderDownloadOutline, mdiSelectAll } from '@mdi/js'; + import { t } from 'svelte-i18n'; + import AssetViewer from '../asset-viewer/asset-viewer.svelte'; import CircleIconButton from '../elements/buttons/circle-icon-button.svelte'; import DownloadAction from '../photos-page/actions/download-action.svelte'; import RemoveFromSharedLink from '../photos-page/actions/remove-from-shared-link.svelte'; import AssetSelectControlBar from '../photos-page/asset-select-control-bar.svelte'; import ControlAppBar from '../shared-components/control-app-bar.svelte'; import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte'; - import AssetViewer from '../asset-viewer/asset-viewer.svelte'; - import { cancelMultiselect } from '$lib/utils/asset-utils'; - import ImmichLogoSmallLink from '$lib/components/shared-components/immich-logo-small-link.svelte'; import { NotificationType, notificationController } from '../shared-components/notification/notification'; - import type { Viewport } from '$lib/stores/assets-store.svelte'; - import { t } from 'svelte-i18n'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; interface Props { sharedLink: SharedLinkResponseDto; @@ -31,9 +31,10 @@ let { sharedLink = $bindable(), isOwned }: Props = $props(); const viewport: Viewport = $state({ width: 0, height: 0 }); - const assetInteraction = new AssetInteraction(); + const assetInteraction = new AssetInteraction(); - let assets = $derived(sharedLink.assets); + let assets = $derived(sharedLink.assets.map((a) => toTimelineAsset(a))); + let fullAsset = $derived(assets[0] ? getAssetInfo({ id: assets[0]?.id, key: getKey() }) : null); dragAndDropFilesStore.subscribe((value) => { if (value.isDragging && value.files.length > 0) { @@ -127,14 +128,16 @@ {:else} - Promise.resolve(false)} - onNext={() => Promise.resolve(false)} - onRandom={() => Promise.resolve(undefined)} - onClose={() => {}} - /> + {#await fullAsset then asset} + Promise.resolve(false)} + onNext={() => Promise.resolve(false)} + onRandom={() => Promise.resolve(undefined)} + onClose={() => {}} + /> + {/await} {/if} 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 f670f5dd70..acfb8744ed 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 @@ -6,7 +6,7 @@ import { AppRoute, AssetAction } from '$lib/constants'; import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import type { Viewport } from '$lib/stores/assets-store.svelte'; + import type { TimelineAsset, Viewport } from '$lib/stores/assets-store.svelte'; import { showDeleteModal } from '$lib/stores/preferences.store'; import { featureFlags } from '$lib/stores/server-config.store'; import { handlePromiseError } from '$lib/utils'; @@ -25,17 +25,17 @@ import ShowShortcuts from '../show-shortcuts.svelte'; interface Props { - assets: AssetResponseDto[]; - assetInteraction: AssetInteraction; + assets: (TimelineAsset | AssetResponseDto)[]; + assetInteraction: AssetInteraction; disableAssetSelect?: boolean; showArchiveIcon?: boolean; viewport: Viewport; onIntersected?: (() => void) | undefined; showAssetName?: boolean; isShowDeleteConfirmation?: boolean; - onPrevious?: (() => Promise) | undefined; - onNext?: (() => Promise) | undefined; - onRandom?: (() => Promise) | undefined; + onPrevious?: (() => Promise<{ id: string } | undefined>) | undefined; + onNext?: (() => Promise<{ id: string } | undefined>) | undefined; + onRandom?: (() => Promise<{ id: string } | undefined>) | undefined; pageHeaderOffset?: number; slidingWindowOffset?: number; } @@ -56,7 +56,7 @@ pageHeaderOffset = 0, }: Props = $props(); - let { isViewing: isViewerOpen, asset: viewingAsset, setAsset } = assetViewingStore; + let { isViewing: isViewerOpen, asset: viewingAsset, setAssetId } = assetViewingStore; let geometry: CommonJustifiedLayout | undefined = $state(); @@ -83,19 +83,26 @@ containerHeight = geometry.containerHeight; containerWidth = geometry.containerWidth; for (const [i, asset] of assets.entries()) { - const layout = { - asset, - top: geometry.getTop(i), - left: geometry.getLeft(i), - width: geometry.getWidth(i), - height: geometry.getHeight(i), - }; - // 54 is the content height of the asset-selection-app-bar - const layoutTopWithOffset = layout.top + pageHeaderOffset; - const layoutBottom = layoutTopWithOffset + layout.height; + const top = geometry.getTop(i); + const left = geometry.getLeft(i); + const width = geometry.getWidth(i); + const height = geometry.getHeight(i); + + const layoutTopWithOffset = top + pageHeaderOffset; + const layoutBottom = layoutTopWithOffset + height; const display = layoutTopWithOffset < slidingWindow.bottom && layoutBottom > slidingWindow.top; - assetLayout.push({ ...layout, display }); + + const layout = { + asset, + top, + left, + width, + height, + display, + }; + + assetLayout.push(layout); } } @@ -109,7 +116,7 @@ let showShortcuts = $state(false); let currentViewAssetIndex = 0; let shiftKeyIsDown = $state(false); - let lastAssetMouseEvent: AssetResponseDto | null = $state(null); + let lastAssetMouseEvent: TimelineAsset | null = $state(null); let slidingWindow = $state({ top: 0, bottom: 0 }); const updateSlidingWindow = () => { @@ -139,14 +146,14 @@ } } }); - const viewAssetHandler = async (asset: AssetResponseDto) => { + const viewAssetHandler = async (asset: TimelineAsset) => { currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id); - setAsset(assets[currentViewAssetIndex]); + await setAssetId(assets[currentViewAssetIndex].id); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); }; const selectAllAssets = () => { - assetInteraction.selectAssets(assets); + assetInteraction.selectAssets(assets.map((a) => toTimelineAsset(a))); }; const deselectAllAssets = () => { @@ -168,7 +175,7 @@ } }; - const handleSelectAssets = (asset: AssetResponseDto) => { + const handleSelectAssets = (asset: TimelineAsset) => { if (!asset) { return; } @@ -191,14 +198,14 @@ assetInteraction.setAssetSelectionStart(deselect ? null : asset); }; - const handleSelectAssetCandidates = (asset: AssetResponseDto | null) => { + const handleSelectAssetCandidates = (asset: TimelineAsset | null) => { if (asset) { selectAssetCandidates(asset); } lastAssetMouseEvent = asset; }; - const selectAssetCandidates = (endAsset: AssetResponseDto) => { + const selectAssetCandidates = (endAsset: TimelineAsset) => { if (!shiftKeyIsDown) { return; } @@ -215,7 +222,7 @@ [start, end] = [end, start]; } - assetInteraction.setAssetSelectionCandidates(assets.slice(start, end + 1)); + assetInteraction.setAssetSelectionCandidates(assets.slice(start, end + 1).map((a) => toTimelineAsset(a))); }; const onSelectStart = (e: Event) => { @@ -310,7 +317,7 @@ const handleNext = async (): Promise => { try { - let asset: AssetResponseDto | undefined; + let asset: { id: string } | undefined; if (onNext) { asset = await onNext(); } else { @@ -334,9 +341,9 @@ } }; - const handleRandom = async (): Promise => { + const handleRandom = async (): Promise<{ id: string } | undefined> => { try { - let asset: AssetResponseDto | undefined; + let asset: { id: string } | undefined; if (onRandom) { asset = await onRandom(); } else { @@ -360,7 +367,7 @@ const handlePrevious = async (): Promise => { try { - let asset: AssetResponseDto | undefined; + let asset: { id: string } | undefined; if (onPrevious) { asset = await onPrevious(); } else { @@ -384,9 +391,9 @@ } }; - const navigateToAsset = async (asset?: AssetResponseDto) => { + const navigateToAsset = async (asset?: { id: string }) => { if (asset && asset.id !== $viewingAsset.id) { - setAsset(asset); + await setAssetId(asset.id); await navigate({ targetRoute: 'current', assetId: $viewingAsset.id }); } }; @@ -405,20 +412,20 @@ } else if (currentViewAssetIndex === assets.length) { await handlePrevious(); } else { - setAsset(assets[currentViewAssetIndex]); + await setAssetId(assets[currentViewAssetIndex].id); } break; } } }; - const assetMouseEventHandler = (asset: AssetResponseDto | null) => { + const assetMouseEventHandler = (asset: TimelineAsset | null) => { if (assetInteraction.selectionActive) { handleSelectAssetCandidates(asset); } }; - const assetOnFocusHandler = (asset: AssetResponseDto) => { + const assetOnFocusHandler = (asset: TimelineAsset) => { assetInteraction.focussedAssetId = asset.id; }; @@ -478,20 +485,19 @@ class="absolute" style:overflow="clip" style="width: {layout.width}px; height: {layout.height}px; top: {layout.top}px; left: {layout.left}px" - title={showAssetName ? asset.originalFileName : ''} > { if (assetInteraction.selectionActive) { - handleSelectAssets(asset); + handleSelectAssets(toTimelineAsset(asset)); return; } - void viewAssetHandler(asset); + void viewAssetHandler(toTimelineAsset(asset)); }} - onSelect={() => handleSelectAssets(asset)} - onMouseEvent={() => assetMouseEventHandler(asset)} - handleFocus={() => assetOnFocusHandler(asset)} + onSelect={() => handleSelectAssets(toTimelineAsset(asset))} + onMouseEvent={() => assetMouseEventHandler(toTimelineAsset(asset))} + handleFocus={() => assetOnFocusHandler(toTimelineAsset(asset))} {showArchiveIcon} asset={toTimelineAsset(asset)} selected={assetInteraction.hasSelectedAsset(asset.id)} @@ -500,11 +506,13 @@ thumbnailWidth={layout.width} thumbnailHeight={layout.height} /> + {#if showAssetName}
- {asset.originalFileName} + {@debug} + {(asset as AssetResponseDto).originalFileName}
{/if} diff --git a/web/src/lib/stores/asset-interaction.svelte.ts b/web/src/lib/stores/asset-interaction.svelte.ts index 3fe270b5cb..09889d99c4 100644 --- a/web/src/lib/stores/asset-interaction.svelte.ts +++ b/web/src/lib/stores/asset-interaction.svelte.ts @@ -1,27 +1,20 @@ +import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; import { user } from '$lib/stores/user.store'; -import type { AssetStackResponseDto, UserAdminResponseDto } from '@immich/sdk'; +import type { UserAdminResponseDto } from '@immich/sdk'; import { SvelteSet } from 'svelte/reactivity'; import { fromStore } from 'svelte/store'; -export type BaseInteractionAsset = { - id: string; - isTrashed: boolean; - isArchived: boolean; - isFavorite: boolean; - ownerId: string; - stack?: AssetStackResponseDto | null | undefined; -}; -export class AssetInteraction { - selectedAssets = $state([]); +export class AssetInteraction { + selectedAssets = $state([]); hasSelectedAsset(assetId: string) { return this.selectedAssets.some((asset) => asset.id === assetId); } selectedGroup = new SvelteSet(); - assetSelectionCandidates = $state([]); + assetSelectionCandidates = $state([]); hasSelectionCandidate(assetId: string) { return this.assetSelectionCandidates.some((asset) => asset.id === assetId); } - assetSelectionStart = $state(null); + assetSelectionStart = $state(null); focussedAssetId = $state(null); selectionActive = $derived(this.selectedAssets.length > 0); @@ -33,13 +26,13 @@ export class AssetInteraction { isAllFavorite = $derived(this.selectedAssets.every((asset) => asset.isFavorite)); isAllUserOwned = $derived(this.selectedAssets.every((asset) => asset.ownerId === this.userId)); - selectAsset(asset: T) { + selectAsset(asset: TimelineAsset) { if (!this.hasSelectedAsset(asset.id)) { this.selectedAssets.push(asset); } } - selectAssets(assets: T[]) { + selectAssets(assets: TimelineAsset[]) { for (const asset of assets) { this.selectAsset(asset); } @@ -60,11 +53,11 @@ export class AssetInteraction { this.selectedGroup.delete(group); } - setAssetSelectionStart(asset: T | null) { + setAssetSelectionStart(asset: TimelineAsset | null) { this.assetSelectionStart = asset; } - setAssetSelectionCandidates(assets: T[]) { + setAssetSelectionCandidates(assets: TimelineAsset[]) { this.assetSelectionCandidates = assets; } diff --git a/web/src/lib/stores/memory.store.svelte.ts b/web/src/lib/stores/memory.store.svelte.ts index 7173b43d06..4cf3fe7134 100644 --- a/web/src/lib/stores/memory.store.svelte.ts +++ b/web/src/lib/stores/memory.store.svelte.ts @@ -1,12 +1,7 @@ +import type { TimelineAsset } from '$lib/stores/assets-store.svelte'; import { asLocalTimeISO } from '$lib/utils/date-time'; -import { - type AssetResponseDto, - deleteMemory, - type MemoryResponseDto, - removeMemoryAssets, - searchMemories, - updateMemory, -} from '@immich/sdk'; +import { toTimelineAsset } from '$lib/utils/timeline-util'; +import { deleteMemory, type MemoryResponseDto, removeMemoryAssets, searchMemories, updateMemory } from '@immich/sdk'; import { DateTime } from 'luxon'; type MemoryIndex = { @@ -16,7 +11,7 @@ type MemoryIndex = { export type MemoryAsset = MemoryIndex & { memory: MemoryResponseDto; - asset: AssetResponseDto; + asset: TimelineAsset; previousMemory?: MemoryResponseDto; previous?: MemoryAsset; next?: MemoryAsset; @@ -36,7 +31,7 @@ class MemoryStoreSvelte { memoryIndex, previousMemory: this.memories[memoryIndex - 1], nextMemory: this.memories[memoryIndex + 1], - asset, + asset: toTimelineAsset(asset), assetIndex, previous, }; diff --git a/web/src/lib/utils/slideshow-history.ts b/web/src/lib/utils/slideshow-history.ts index b9d5fdea7e..2452a3a147 100644 --- a/web/src/lib/utils/slideshow-history.ts +++ b/web/src/lib/utils/slideshow-history.ts @@ -1,17 +1,15 @@ -import type { AssetResponseDto } from '@immich/sdk'; - export class SlideshowHistory { - private history: AssetResponseDto[] = []; + private history: { id: string }[] = []; private index = 0; - constructor(private onChange: (asset: AssetResponseDto) => void) {} + constructor(private onChange: (asset: { id: string }) => void) {} reset() { this.history = []; this.index = 0; } - queue(asset: AssetResponseDto) { + queue(asset: { id: string }) { this.history.push(asset); // If we were at the end of the slideshow history, move the index to the new end diff --git a/web/src/lib/utils/thumbnail-util.ts b/web/src/lib/utils/thumbnail-util.ts index a53691e716..c7404cd1b2 100644 --- a/web/src/lib/utils/thumbnail-util.ts +++ b/web/src/lib/utils/thumbnail-util.ts @@ -37,8 +37,16 @@ export function getThumbnailSize(assetCount: number, viewWidth: number): number return 300; } +export const getAltTextForTimelineAsset = () => { + // TODO: implement this in a performant way + return ''; +}; + export const getAltText = derived(t, ($t) => { - return (asset: AssetResponseDto) => { + return (asset: AssetResponseDto | null | undefined) => { + if (!asset) { + return ''; + } if (asset.exifInfo?.description) { return asset.exifInfo.description; } diff --git a/web/src/lib/utils/timeline-util.ts b/web/src/lib/utils/timeline-util.ts index 4837bf613d..2165e13773 100644 --- a/web/src/lib/utils/timeline-util.ts +++ b/web/src/lib/utils/timeline-util.ts @@ -1,4 +1,3 @@ -import type { BaseInteractionAsset } from '$lib/stores/asset-interaction.svelte'; import type { AssetBucket, TimelineAsset } from '$lib/stores/assets-store.svelte'; import { locale } from '$lib/stores/preferences.store'; import { getAssetRatio } from '$lib/utils/asset-utils'; @@ -108,7 +107,7 @@ export const getDateLocaleString = (date: DateTime, opts?: LocaleOptions): strin export const formatDateGroupTitle = memoize(formatGroupTitle); -export const toTimelineAsset = (unknownAsset: BaseInteractionAsset): TimelineAsset => { +export const toTimelineAsset = (unknownAsset: AssetResponseDto | TimelineAsset): TimelineAsset => { if (isTimelineAsset(unknownAsset)) { return unknownAsset; } @@ -132,5 +131,5 @@ export const toTimelineAsset = (unknownAsset: BaseInteractionAsset): TimelineAss livePhotoVideoId: assetResponse.livePhotoVideoId || null, }; }; -export const isTimelineAsset = (arg: BaseInteractionAsset): arg is TimelineAsset => +export const isTimelineAsset = (arg: AssetResponseDto | TimelineAsset): arg is TimelineAsset => (arg as TimelineAsset).ratio !== undefined; diff --git a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte index 869f3f7f07..ba5ac89e9f 100644 --- a/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/folders/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -27,8 +27,8 @@ import { foldersStore } from '$lib/stores/folders.svelte'; import { preferences } from '$lib/stores/user.store'; import { cancelMultiselect } from '$lib/utils/asset-utils'; + import { toTimelineAsset } from '$lib/utils/timeline-util'; import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils'; - import type { AssetResponseDto } from '@immich/sdk'; import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js'; import { onMount } from 'svelte'; import { t } from 'svelte-i18n'; @@ -47,7 +47,7 @@ let currentPath = $derived($page.url.searchParams.get(QueryParameter.PATH) || ''); let currentTreeItems = $derived(currentPath ? data.currentFolders : Object.keys(tree).sort()); - const assetInteraction = new AssetInteraction(); + const assetInteraction = new AssetInteraction(); onMount(async () => { await foldersStore.fetchUniquePaths(); @@ -83,7 +83,7 @@ return; } - assetInteraction.selectAssets(data.pathAssets); + assetInteraction.selectAssets(data.pathAssets.map((a) => toTimelineAsset(a))); }; diff --git a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte index 63950dac8a..bd6a5617f7 100644 --- a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte @@ -22,7 +22,7 @@ import { AssetAction } from '$lib/constants'; import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; - import { AssetStore, type TimelineAsset } from '$lib/stores/assets-store.svelte'; + import { AssetStore } from '$lib/stores/assets-store.svelte'; import { isFaceEditMode } from '$lib/stores/face-edit.svelte'; import { preferences, user } from '$lib/stores/user.store'; import { @@ -42,7 +42,7 @@ void assetStore.updateOptions({ isArchived: false, withStacked: true, withPartners: true }); onDestroy(() => assetStore.destroy()); - const assetInteraction = new AssetInteraction(); + const assetInteraction = new AssetInteraction(); let selectedAssets = $derived(assetInteraction.selectedAssets); let isAssetStackSelected = $derived(selectedAssets.length === 1 && !!selectedAssets[0].stack); diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte index e057730e0a..628b0a3105 100644 --- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -1,28 +1,41 @@