diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 13ac213b1b..b66863ad77 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -5,12 +5,12 @@ import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import Timeline from '$lib/components/timeline/Timeline.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { handleDownloadAlbum } from '$lib/services/album.service'; import { getGlobalActions } from '$lib/services/app.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; @@ -39,8 +39,6 @@ const options = $derived({ albumId: album.id, order: album.order }); let timelineManager = $state() as TimelineManager; - const assetInteraction = new AssetInteraction(); - dragAndDropFilesStore.subscribe((value) => { if (value.isDragging && value.files.length > 0) { handlePromiseError(fileUploadHandler({ files: value.files, albumId: album.id })); @@ -67,15 +65,15 @@ use:shortcut={{ shortcut: { key: 'Escape' }, onShortcut: () => { - if (!assetViewerManager.isViewing && assetInteraction.selectionActive) { - cancelMultiselect(assetInteraction); + if (!assetViewerManager.isViewing && assetMultiSelectManager.selectionActive) { + cancelMultiselect(assetMultiSelectManager); } }, }} />
- +

@@ -99,13 +97,13 @@

- {#if assetInteraction.selectionActive} + {#if assetMultiSelectManager.selectionActive} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - + {#if sharedLink.allowDownload} {/if} diff --git a/web/src/lib/components/memory-page/memory-viewer.svelte b/web/src/lib/components/memory-page/memory-viewer.svelte index ab3256df49..ad0b48b42f 100644 --- a/web/src/lib/components/memory-page/memory-viewer.svelte +++ b/web/src/lib/components/memory-page/memory-viewer.svelte @@ -19,13 +19,13 @@ import TagAction from '$lib/components/timeline/actions/TagAction.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import { QueryParameter } from '$lib/constants'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; import { memoryManager, type MemoryAsset } from '$lib/managers/memory-manager.svelte'; import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types'; import { Route } from '$lib/route'; import { getAssetBulkActions } from '$lib/services/asset.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { locale, videoViewerMuted, videoViewerVolume } from '$lib/stores/preferences.store'; import { preferences } from '$lib/stores/user.store'; import { getAssetMediaUrl, handlePromiseError, memoryLaneTitle } from '$lib/utils'; @@ -80,7 +80,6 @@ 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(); let progressBarController: Tween | undefined = $state(undefined); let videoPlayer: HTMLVideoElement | undefined = $state(); const asHref = (asset: { id: string }) => `?${QueryParameter.ID}=${asset.id}`; @@ -117,7 +116,7 @@ const handlePreviousMemory = () => handleNavigate(current?.previousMemory?.assets[0]); const handleEscape = async () => goto(Route.photos()); const handleSelectAll = () => - assetInteraction.selectAssets(current?.memory.assets.map((a) => toTimelineAsset(a)) || []); + assetMultiSelectManager.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! @@ -336,14 +335,14 @@ ]} /> -{#if assetInteraction.selectionActive} +{#if assetMultiSelectManager.selectionActive}
cancelMultiselect(assetInteraction)} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => cancelMultiselect(assetMultiSelectManager)} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} - + - - {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} + + {#if $preferences.tags.enabled && assetMultiSelectManager.isAllUserOwned} {/if} @@ -669,7 +672,7 @@ assets={currentTimelineAssets} {viewerAssets} viewport={galleryViewport} - {assetInteraction} + assetInteraction={assetMultiSelectManager} slidingWindowOffset={viewerHeight} arrowNavigation={false} /> diff --git a/web/src/lib/components/share-page/individual-shared-viewer.svelte b/web/src/lib/components/share-page/individual-shared-viewer.svelte index ec58bd7ea3..09a57a85b6 100644 --- a/web/src/lib/components/share-page/individual-shared-viewer.svelte +++ b/web/src/lib/components/share-page/individual-shared-viewer.svelte @@ -5,10 +5,10 @@ import RemoveFromSharedLink from '$lib/components/timeline/actions/RemoveFromSharedLinkAction.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import { AssetAction } from '$lib/constants'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte'; import type { Viewport } from '$lib/managers/timeline-manager/types'; import { Route } from '$lib/route'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store'; import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte'; import { handlePromiseError } from '$lib/utils'; @@ -31,7 +31,6 @@ let { sharedLink = $bindable(), isOwned }: Props = $props(); const viewport: Viewport = $state({ width: 0, height: 0 }); - const assetInteraction = new AssetInteraction(); let assets = $derived(sharedLink.assets); @@ -59,7 +58,7 @@ }; const handleSelectAll = () => { - assetInteraction.selectAssets(assets.map((asset) => toTimelineAsset(asset))); + assetMultiSelectManager.selectAssets(assets.map((asset) => toTimelineAsset(asset))); }; const handleAction = async (action: Action) => { @@ -76,14 +75,14 @@ {#if sharedLink?.allowUpload || assets.length > 1}
- +
- {#if assetInteraction.selectionActive} + {#if assetMultiSelectManager.selectionActive} cancelMultiselect(assetInteraction)} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => cancelMultiselect(assetMultiSelectManager)} > () as TimelineManager; - let selectedAssets = $derived(assetInteraction.selectedAssets); + let selectedAssets = $derived(assetMultiSelectManager.selectedAssets); let isAssetStackSelected = $derived(selectedAssets.length === 1 && !!selectedAssets[0].stack); let isLinkActionAvailable = $derived.by(() => { const isLivePhoto = selectedAssets.length === 1 && !!selectedAssets[0].livePhotoVideoId; @@ -55,7 +54,7 @@ selectedAssets.some((asset) => asset.isImage) && selectedAssets.some((asset) => asset.isVideo); - return assetInteraction.isAllUserOwned && (isLivePhoto || isLivePhotoCandidate); + return assetMultiSelectManager.isAllUserOwned && (isLivePhoto || isLivePhotoCandidate); }); const handleLink: OnLink = ({ still, motion }) => { @@ -70,11 +69,11 @@ const handleSetVisibility = (assetIds: string[]) => { timelineManager.removeAssets(assetIds); - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); }; const handleEscape = () => { - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); }; const timelineBoundingBox = $derived( @@ -91,7 +90,7 @@ $effect.pre(() => { void timelineOptions; - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); }); @@ -112,35 +111,35 @@ enableRouting={false} options={timelineOptions} onEscape={handleEscape} - {assetInteraction} + assetInteraction={assetMultiSelectManager} showArchiveIcon />
-{#if assetInteraction.selectionActive} - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} +{#if assetMultiSelectManager.selectionActive} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - + - {#if assetInteraction.isAllUserOwned} + {#if assetMultiSelectManager.isAllUserOwned} timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} /> - {#if assetInteraction.selectedAssets.length > 1 || isAssetStackSelected} + {#if assetMultiSelectManager.selectedAssets.length > 1 || isAssetStackSelected} updateStackedAssetInTimeline(timelineManager, result)} @@ -150,7 +149,7 @@ {#if isLinkActionAvailable} @@ -160,7 +159,7 @@ timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> {#if $preferences.tags.enabled} diff --git a/web/src/lib/components/timeline/Month.svelte b/web/src/lib/components/timeline/Month.svelte index c0b20b17bb..539b2efdf8 100644 --- a/web/src/lib/components/timeline/Month.svelte +++ b/web/src/lib/components/timeline/Month.svelte @@ -1,11 +1,11 @@ - + @@ -60,22 +58,22 @@ -{#if assetInteraction.selectionActive} +{#if assetMultiSelectManager.selectionActive} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> - + timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} /> diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte index b13146aab6..8babca6d58 100644 --- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -15,9 +15,9 @@ import TagAction from '$lib/components/timeline/actions/TagAction.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import Timeline from '$lib/components/timeline/Timeline.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { getAssetBulkActions } from '$lib/services/asset.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { preferences } from '$lib/stores/user.store'; import { ActionButton, CommandPaletteDefaultProvider } from '@immich/ui'; import { mdiDotsVertical } from '@mdi/js'; @@ -33,28 +33,26 @@ let timelineManager = $state() as TimelineManager; const options = { isFavorite: true, withStacked: true }; - const assetInteraction = new AssetInteraction(); - const handleEscape = () => { - if (assetInteraction.selectionActive) { - assetInteraction.clearMultiselect(); + if (assetMultiSelectManager.selectionActive) { + assetMultiSelectManager.clearMultiselect(); return; } }; const handleSetVisibility = (assetIds: string[]) => { timelineManager.removeAssets(assetIds); - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); }; - + {#snippet empty()} @@ -64,16 +62,16 @@ -{#if assetInteraction.selectionActive} +{#if assetMultiSelectManager.selectionActive} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} timelineManager.removeAssets(assetIds)} /> - + @@ -82,7 +80,7 @@ timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> {#if $preferences.tags.enabled} 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 3cafdcbc5b..6fd6e30c8a 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 @@ -19,10 +19,10 @@ import TagAction from '$lib/components/timeline/actions/TagAction.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import SkipLink from '$lib/elements/SkipLink.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import type { Viewport } from '$lib/managers/timeline-manager/types'; import { Route } from '$lib/route'; import { getAssetBulkActions } from '$lib/services/asset.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { foldersStore } from '$lib/stores/folders.svelte'; import { preferences } from '$lib/stores/user.store'; import { cancelMultiselect } from '$lib/utils/asset-utils'; @@ -40,7 +40,6 @@ let { data }: Props = $props(); const viewport: Viewport = $state({ width: 0, height: 0 }); - const assetInteraction = new AssetInteraction(); const handleNavigateToFolder = (folderName: string) => navigateToView(joinPaths(data.tree.path, folderName)); @@ -48,7 +47,7 @@ afterNavigate(function clearAssetSelection() { // Clear the asset selection when we navigate (like going to another folder) - cancelMultiselect(assetInteraction); + cancelMultiselect(assetMultiSelectManager); }); function navigateToView(path: string) { @@ -56,7 +55,7 @@ } async function triggerAssetUpdate() { - cancelMultiselect(assetInteraction); + cancelMultiselect(assetMultiSelectManager); if (data.tree.path) { await foldersStore.refreshAssetsByPath(data.tree.path); } @@ -68,7 +67,7 @@ return; } - assetInteraction.selectAssets(data.pathAssets.map((asset) => toTimelineAsset(asset))); + assetMultiSelectManager.selectAssets(data.pathAssets.map((asset) => toTimelineAsset(asset))); } @@ -100,7 +99,7 @@
-{#if assetInteraction.selectionActive} +{#if assetMultiSelectManager.selectionActive}
cancelMultiselect(assetInteraction)} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => cancelMultiselect(assetMultiSelectManager)} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} 0) { for (const id of ids) { @@ -148,8 +147,8 @@ - - {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} + + {#if $preferences.tags.enabled && assetMultiSelectManager.isAllUserOwned} {/if} diff --git a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte index 931c19341b..8c0059bf17 100644 --- a/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/locked/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -13,10 +13,10 @@ import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import Timeline from '$lib/components/timeline/Timeline.svelte'; import { AssetAction } from '$lib/constants'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { Route } from '$lib/route'; import { getUserActions } from '$lib/services/user.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetVisibility } from '@immich/sdk'; import { mdiDotsVertical } from '@mdi/js'; import { t } from 'svelte-i18n'; @@ -31,17 +31,15 @@ let timelineManager = $state() as TimelineManager; const options = { visibility: AssetVisibility.Locked }; - const assetInteraction = new AssetInteraction(); - const handleEscape = () => { - if (assetInteraction.selectionActive) { - assetInteraction.clearMultiselect(); + if (assetMultiSelectManager.selectionActive) { + assetMultiSelectManager.clearMultiselect(); return; } }; const handleMoveOffLockedFolder = (assetIds: string[]) => { - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); timelineManager.removeAssets(assetIds); }; @@ -57,14 +55,14 @@ @@ -75,12 +73,12 @@ -{#if assetInteraction.selectionActive} +{#if assetMultiSelectManager.selectionActive} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - + diff --git a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index 2412c0e0c9..ef1b1bc8b1 100644 --- a/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/partners/[userId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -5,9 +5,9 @@ import DownloadAction from '$lib/components/timeline/actions/DownloadAction.svelte'; import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte'; import Timeline from '$lib/components/timeline/Timeline.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { Route } from '$lib/route'; import { getAssetBulkActions } from '$lib/services/asset.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; import { AssetVisibility } from '@immich/sdk'; import { ActionButton, CommandPaletteDefaultProvider } from '@immich/ui'; import { mdiArrowLeft } from '@mdi/js'; @@ -26,26 +26,24 @@ withStacked: true, }); - const assetInteraction = new AssetInteraction(); - const handleEscape = () => { - if (assetInteraction.selectionActive) { - assetInteraction.clearMultiselect(); + if (assetMultiSelectManager.selectionActive) { + assetMultiSelectManager.clearMultiselect(); return; } };
- +
-{#if assetInteraction.selectionActive} +{#if assetMultiSelectManager.selectionActive} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte index a891c01b3e..259aaefbe0 100644 --- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -32,7 +32,7 @@ import { Route } from '$lib/route'; import { getAssetBulkActions } from '$lib/services/asset.service'; import { getPersonActions } from '$lib/services/person.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { locale } from '$lib/stores/preferences.store'; import { preferences } from '$lib/stores/user.store'; import { websocketEvents } from '$lib/stores/websocket'; @@ -65,7 +65,6 @@ let timelineManager = $state() as TimelineManager; const options = $derived({ visibility: AssetVisibility.Timeline, personId: data.person.id }); - const assetInteraction = new AssetInteraction(); let viewMode: PersonPageViewMode = $state(PersonPageViewMode.VIEW_ASSETS); let isEditingName = $state(false); @@ -106,8 +105,8 @@ }); const handleEscape = async () => { - if (assetInteraction.selectionActive) { - assetInteraction.clearMultiselect(); + if (assetMultiSelectManager.selectionActive) { + assetMultiSelectManager.clearMultiselect(); return; } @@ -127,8 +126,8 @@ }); const handleUnmerge = () => { - timelineManager.removeAssets(assetInteraction.selectedAssets.map((a) => a.id)); - assetInteraction.clearMultiselect(); + timelineManager.removeAssets(assetMultiSelectManager.selectedAssets.map((a) => a.id)); + assetMultiSelectManager.clearMultiselect(); viewMode = PersonPageViewMode.VIEW_ASSETS; }; @@ -154,7 +153,7 @@ handleError(error, $t('errors.unable_to_set_feature_photo')); } - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); viewMode = PersonPageViewMode.VIEW_ASSETS; }; @@ -283,7 +282,7 @@ const handleSetVisibility = (assetIds: string[]) => { timelineManager.removeAssets(assetIds); - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); }; const onPersonUpdate = async (response: PersonResponseDto) => { @@ -347,7 +346,7 @@ {person} bind:timelineManager {options} - {assetInteraction} + assetInteraction={assetMultiSelectManager} isSelectionMode={viewMode === PersonPageViewMode.SELECT_PERSON} singleSelect={viewMode === PersonPageViewMode.SELECT_PERSON} onSelect={handleSelectFeaturePhoto} @@ -458,18 +457,18 @@
- {#if assetInteraction.selectionActive} + {#if assetMultiSelectManager.selectionActive} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} - + timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} /> @@ -484,10 +483,10 @@ timelineManager.update(ids, (asset) => (asset.visibility = visibility))} /> - {#if $preferences.tags.enabled && assetInteraction.isAllUserOwned} + {#if $preferences.tags.enabled && assetMultiSelectManager.isAllUserOwned} {/if} @@ -522,7 +521,7 @@ {#if viewMode === PersonPageViewMode.UNASSIGN_ASSETS} a.id)} + assetIds={assetMultiSelectManager.selectedAssets.map((a) => a.id)} personAssets={person} onClose={() => (viewMode = PersonPageViewMode.VIEW_ASSETS)} onConfirm={handleUnmerge} diff --git a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte index 462f8fa3b6..94ec148e9f 100644 --- a/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/photos/[[assetId=id]]/+page.svelte @@ -24,7 +24,7 @@ import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { Route } from '$lib/route'; import { getAssetBulkActions } from '$lib/services/asset.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { preferences, user } from '$lib/stores/user.store'; import { getAssetMediaUrl, memoryLaneTitle } from '$lib/utils'; import { @@ -44,9 +44,7 @@ let timelineManager = $state() as TimelineManager; const options = { visibility: AssetVisibility.Timeline, withStacked: true, withPartners: true }; - const assetInteraction = new AssetInteraction(); - - let selectedAssets = $derived(assetInteraction.selectedAssets); + let selectedAssets = $derived(assetMultiSelectManager.selectedAssets); let isAssetStackSelected = $derived(selectedAssets.length === 1 && !!selectedAssets[0].stack); let isLinkActionAvailable = $derived.by(() => { const isLivePhoto = selectedAssets.length === 1 && !!selectedAssets[0].livePhotoVideoId; @@ -55,15 +53,15 @@ selectedAssets.some((asset) => asset.isImage) && selectedAssets.some((asset) => asset.isVideo); - return assetInteraction.isAllUserOwned && (isLivePhoto || isLivePhotoCandidate); + return assetMultiSelectManager.isAllUserOwned && (isLivePhoto || isLivePhotoCandidate); }); const handleEscape = () => { if (assetViewerManager.isViewing) { return; } - if (assetInteraction.selectionActive) { - assetInteraction.clearMultiselect(); + if (assetMultiSelectManager.selectionActive) { + assetMultiSelectManager.clearMultiselect(); return; } }; @@ -80,7 +78,7 @@ const handleSetVisibility = (assetIds: string[]) => { timelineManager.removeAssets(assetIds); - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); }; const items = $derived( @@ -94,12 +92,12 @@ ); - + -{#if assetInteraction.selectionActive} +{#if assetMultiSelectManager.selectionActive} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} - + - {#if assetInteraction.isAllUserOwned} + {#if assetMultiSelectManager.isAllUserOwned} timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} /> - {#if assetInteraction.selectedAssets.length > 1 || isAssetStackSelected} + {#if assetMultiSelectManager.selectedAssets.length > 1 || isAssetStackSelected} updateStackedAssetInTimeline(timelineManager, result)} @@ -144,7 +142,7 @@ {#if isLinkActionAvailable} 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 4f050b8629..ed44036e1e 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 @@ -23,7 +23,7 @@ import type { Viewport } from '$lib/managers/timeline-manager/types'; import { Route } from '$lib/route'; import { getAssetBulkActions } from '$lib/services/asset.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { lang, locale } from '$lib/stores/preferences.store'; import { preferences } from '$lib/stores/user.store'; import { handlePromiseError } from '$lib/utils'; @@ -62,8 +62,6 @@ let scrollY = $state(0); let scrollYHistory = 0; - const assetInteraction = new AssetInteraction(); - type SearchTerms = MetadataSearchDto & Pick; let searchQuery = $derived(page.url.searchParams.get(QueryParameter.QUERY)); let smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch); @@ -112,12 +110,12 @@ }; const handleSetVisibility = (assetIds: string[]) => { - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); onAssetDelete(assetIds); }; const handleSelectAll = () => { - assetInteraction.selectAssets(searchResultAssets.map((asset) => toTimelineAsset(asset))); + assetMultiSelectManager.selectAssets(searchResultAssets.map((asset) => toTimelineAsset(asset))); }; async function onSearchQueryUpdate() { @@ -226,7 +224,7 @@ } const onAlbumAddAssets = ({ assetIds }: { assetIds: string[] }) => { - cancelMultiselect(assetInteraction); + cancelMultiselect(assetMultiSelectManager); if (terms.isNotInAlbum) { const assetIdSet = new Set(assetIds); @@ -294,7 +292,7 @@ {#if searchResultAssets.length > 0}
- {#if assetInteraction.selectionActive} + {#if assetMultiSelectManager.selectionActive}
cancelMultiselect(assetInteraction)} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => cancelMultiselect(assetMultiSelectManager)} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} @@ -338,9 +336,9 @@ onclick={handleSelectAll} /> - {#if assetInteraction.isAllUserOwned} + {#if assetMultiSelectManager.isAllUserOwned} { for (const id of ids) { const asset = searchResultAssets.find((asset) => asset.id === id); @@ -357,7 +355,7 @@ - + {#if $preferences.tags.enabled} diff --git a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte index fefd8dd032..98f47dd1e0 100644 --- a/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/tags/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -26,7 +26,7 @@ import { Route } from '$lib/route'; import { getAssetBulkActions } from '$lib/services/asset.service'; import { getTagActions } from '$lib/services/tag.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { preferences, user } from '$lib/stores/user.store'; import { joinPaths, TreeNode } from '$lib/utils/tree-utils'; import { getAllTags, type TagResponseDto } from '@immich/sdk'; @@ -41,8 +41,6 @@ let { data }: Props = $props(); - const assetInteraction = new AssetInteraction(); - let tags = $derived(data.tags); const tree = $derived(TreeNode.fromTags(tags)); const tag = $derived(tree.traverse(data.path)); @@ -58,7 +56,7 @@ const handleSetVisibility = (assetIds: string[]) => { timelineManager.removeAssets(assetIds); - assetInteraction.clearMultiselect(); + assetMultiSelectManager.clearMultiselect(); }; const onRefresh = async () => { @@ -99,7 +97,7 @@ enableRouting={true} bind:timelineManager {options} - {assetInteraction} + assetInteraction={assetMultiSelectManager} removeAction={AssetAction.UNARCHIVE} > {#snippet empty()} @@ -113,20 +111,20 @@
- {#if assetInteraction.selectionActive} + {#if assetMultiSelectManager.selectionActive}
assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - {@const Actions = getAssetBulkActions($t, assetInteraction.asControlContext())} + {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} - + timelineManager.update(ids, (asset) => (asset.isFavorite = isFavorite))} > diff --git a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte index 8f7be29004..c4f5f5051e 100644 --- a/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/trash/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -13,7 +13,7 @@ import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { Route } from '$lib/route'; import { getTrashActions } from '$lib/services/trash.service'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import { handlePromiseError } from '$lib/utils'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; @@ -27,15 +27,13 @@ let timelineManager = $state() as TimelineManager; const options = { isTrashed: true }; - const assetInteraction = new AssetInteraction(); - if (!featureFlagsManager.value.trash) { handlePromiseError(goto(Route.photos())); } const handleEscape = () => { - if (assetInteraction.selectionActive) { - assetInteraction.clearMultiselect(); + if (assetMultiSelectManager.selectionActive) { + assetMultiSelectManager.clearMultiselect(); return; } }; @@ -45,12 +43,18 @@ {#if featureFlagsManager.value.trash} - +

{$t('trashed_items_will_be_permanently_deleted_after', { values: { days: serverConfigManager.value.trashDays }, @@ -63,12 +67,12 @@ {/if} -{#if assetInteraction.selectionActive} +{#if assetMultiSelectManager.selectionActive} assetInteraction.clearMultiselect()} + assets={assetMultiSelectManager.selectedAssets} + clearSelect={() => assetMultiSelectManager.clearMultiselect()} > - + timelineManager.removeAssets(assetIds)} /> timelineManager.removeAssets(assetIds)} /> diff --git a/web/src/routes/(user)/utilities/geolocation/+page.svelte b/web/src/routes/(user)/utilities/geolocation/+page.svelte index d8cd0c3850..96caa9ba94 100644 --- a/web/src/routes/(user)/utilities/geolocation/+page.svelte +++ b/web/src/routes/(user)/utilities/geolocation/+page.svelte @@ -10,7 +10,7 @@ import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import GeolocationPointPickerModal from '$lib/modals/GeolocationPointPickerModal.svelte'; import GeolocationUpdateConfirmModal from '$lib/modals/GeolocationUpdateConfirmModal.svelte'; - import { AssetInteraction } from '$lib/stores/asset-interaction.svelte'; + import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte'; import type { LatLng } from '$lib/types'; import { cancelMultiselect } from '$lib/utils/asset-utils'; import { setQueryValue } from '$lib/utils/navigation'; @@ -28,7 +28,6 @@ let { data }: Props = $props(); let isLoading = $state(false); - let assetInteraction = new AssetInteraction(); let point = $state(); let locationUpdated = $state(false); @@ -47,7 +46,7 @@ const confirmed = await modalManager.show(GeolocationUpdateConfirmModal, { point, - assetCount: assetInteraction.selectedAssets.length, + assetCount: assetMultiSelectManager.selectedAssets.length, }); if (!confirmed) { @@ -56,14 +55,14 @@ await updateAssets({ assetBulkUpdateDto: { - ids: assetInteraction.selectedAssets.map((asset) => asset.id), + ids: assetMultiSelectManager.selectedAssets.map((asset) => asset.id), latitude: point.lat, longitude: point.lng, }, }); const updatedAssets = await Promise.all( - assetInteraction.selectedAssets.map(async (asset) => { + assetMultiSelectManager.selectedAssets.map(async (asset) => { const updatedAsset = await getAssetInfo({ ...authManager.params, id: asset.id }); return toTimelineAsset(updatedAsset); }), @@ -78,8 +77,8 @@ if (event.key === 'Shift') { event.preventDefault(); } - if (event.key === 'Escape' && assetInteraction.selectionActive) { - cancelMultiselect(assetInteraction); + if (event.key === 'Escape' && assetMultiSelectManager.selectionActive) { + cancelMultiselect(assetMultiSelectManager); } }; const onKeyUp = (event: KeyboardEvent) => { @@ -89,7 +88,7 @@ }; const handleDeselectAll = () => { - cancelMultiselect(assetInteraction); + cancelMultiselect(assetMultiSelectManager); }; const handlePickPoint = async () => { @@ -101,8 +100,8 @@ point = selected; }; const handleEscape = () => { - if (assetInteraction.selectionActive) { - assetInteraction.clearMultiselect(); + if (assetMultiSelectManager.selectionActive) { + assetMultiSelectManager.clearMultiselect(); return; } }; @@ -168,7 +167,7 @@ size="small" color="secondary" variant="ghost" - disabled={!assetInteraction.selectionActive} + disabled={!assetMultiSelectManager.selectionActive} onclick={handleDeselectAll} > {$t('unselect_all')} @@ -177,11 +176,11 @@ leadingIcon={mdiMapMarkerMultipleOutline} size="small" color="primary" - disabled={assetInteraction.selectedAssets.length === 0} + disabled={assetMultiSelectManager.selectedAssets.length === 0} onclick={() => handleUpdate()} >

@@ -198,7 +197,7 @@ enableRouting={true} bind:timelineManager {options} - {assetInteraction} + assetInteraction={assetMultiSelectManager} removeAction={AssetAction.ARCHIVE} onEscape={handleEscape} withStacked diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 71bd66641c..8139fb94c8 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -99,6 +99,9 @@ if (isAssetViewerRoute(from) && isAssetViewerRoute(to)) { return; } + + eventManager.emit('AppNavigate'); + showNavigationLoadingBar = true; });