refactor: asset select manager (#27329)

This commit is contained in:
Jason Rasmussen
2026-03-27 14:23:33 -04:00
committed by GitHub
parent 14cce0cba3
commit 5889c42eb6
17 changed files with 33 additions and 68 deletions
@@ -16,7 +16,7 @@
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { handlePromiseError } from '$lib/utils'; import { handlePromiseError } from '$lib/utils';
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader'; import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
import type { AlbumResponseDto, SharedLinkResponseDto, UserResponseDto } from '@immich/sdk'; import type { AlbumResponseDto, SharedLinkResponseDto } from '@immich/sdk';
import { ActionButton, IconButton, Logo } from '@immich/ui'; import { ActionButton, IconButton, Logo } from '@immich/ui';
import { mdiDownload, mdiFileImagePlusOutline, mdiPresentationPlay } from '@mdi/js'; import { mdiDownload, mdiFileImagePlusOutline, mdiPresentationPlay } from '@mdi/js';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
@@ -26,10 +26,9 @@
interface Props { interface Props {
sharedLink: SharedLinkResponseDto; sharedLink: SharedLinkResponseDto;
user?: UserResponseDto | undefined;
} }
let { sharedLink, user = undefined }: Props = $props(); let { sharedLink }: Props = $props();
const album = sharedLink.album as AlbumResponseDto; const album = sharedLink.album as AlbumResponseDto;
@@ -97,11 +96,7 @@
<header> <header>
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar <AssetSelectControlBar>
ownerId={user?.id}
assets={assetMultiSelectManager.assets}
clearSelect={() => assetMultiSelectManager.clear()}
>
<SelectAllAssets {timelineManager} assetInteraction={assetMultiSelectManager} /> <SelectAllAssets {timelineManager} assetInteraction={assetMultiSelectManager} />
{#if sharedLink.allowDownload} {#if sharedLink.allowDownload}
<DownloadAction filename="{album.albumName}.zip" /> <DownloadAction filename="{album.albumName}.zip" />
@@ -336,11 +336,7 @@
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<div class="sticky top-0 z-1 dark"> <div class="sticky top-0 z-1 dark">
<AssetSelectControlBar <AssetSelectControlBar forceDark>
forceDark
assets={assetMultiSelectManager.assets}
clearSelect={() => assetMultiSelectManager.clear()}
>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CreateSharedLink /> <CreateSharedLink />
<IconButton <IconButton
@@ -80,10 +80,7 @@
<header class="fixed top-0 inset-s-0 w-full"> <header class="fixed top-0 inset-s-0 w-full">
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar <AssetSelectControlBar>
assets={assetMultiSelectManager.assets}
clearSelect={() => assetMultiSelectManager.clear()}
>
<IconButton <IconButton
shape="round" shape="round"
color="secondary" color="secondary"
@@ -22,7 +22,7 @@
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { getAssetBulkActions } from '$lib/services/asset.service'; import { getAssetBulkActions } from '$lib/services/asset.service';
import { mapSettings } from '$lib/stores/preferences.store'; import { mapSettings } from '$lib/stores/preferences.store';
import { preferences, user } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { import {
updateStackedAssetInTimeline, updateStackedAssetInTimeline,
updateUnstackedAssetInTimeline, updateUnstackedAssetInTimeline,
@@ -122,11 +122,7 @@
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
<Portal target="body"> <Portal target="body">
<AssetSelectControlBar <AssetSelectControlBar>
ownerId={$user.id}
assets={assetMultiSelectManager.assets}
clearSelect={() => assetMultiSelectManager.clear()}
>
<CreateSharedLink /> <CreateSharedLink />
<SelectAllAssets {timelineManager} assetInteraction={assetMultiSelectManager} /> <SelectAllAssets {timelineManager} assetInteraction={assetMultiSelectManager} />
<ActionButton action={Actions.AddToAlbum} /> <ActionButton action={Actions.AddToAlbum} />
@@ -1,32 +1,26 @@
<script lang="ts" module>
import { setAssetControlContext } from '$lib/utils/context';
import { t } from 'svelte-i18n';
</script>
<script lang="ts"> <script lang="ts">
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
import { assetMultiSelectManager } from '$lib/managers/asset-multi-select-manager.svelte';
import { setAssetControlContext } from '$lib/utils/context';
import { mdiClose } from '@mdi/js'; import { mdiClose } from '@mdi/js';
import type { Snippet } from 'svelte'; import type { Snippet } from 'svelte';
import ControlAppBar from '../shared-components/control-app-bar.svelte'; import { t } from 'svelte-i18n';
type Props = { type Props = {
assets: TimelineAsset[];
clearSelect: () => void;
ownerId?: string | undefined;
children?: Snippet; children?: Snippet;
forceDark?: boolean; forceDark?: boolean;
}; };
let { assets, clearSelect, ownerId = undefined, children, forceDark }: Props = $props(); let { children, forceDark }: Props = $props();
setAssetControlContext({ setAssetControlContext(assetMultiSelectManager.asControlContext());
getAssets: () => assets,
getOwnedAssets: () => (ownerId === undefined ? assets : assets.filter((asset) => asset.ownerId === ownerId)), const onClose = () => assetMultiSelectManager.clear();
clearSelect: () => clearSelect(),
}); const assets = $derived(assetMultiSelectManager.assets);
</script> </script>
<ControlAppBar onClose={clearSelect} {forceDark} backIcon={mdiClose} tailwindClasses="bg-white shadow-md"> <ControlAppBar {onClose} {forceDark} backIcon={mdiClose} tailwindClasses="bg-white shadow-md">
{#snippet leading()} {#snippet leading()}
<div class="font-medium {forceDark ? 'text-immich-dark-primary' : 'text-primary'}"> <div class="font-medium {forceDark ? 'text-immich-dark-primary' : 'text-primary'}">
<p class="block sm:hidden">{assets.length}</p> <p class="block sm:hidden">{assets.length}</p>
@@ -43,7 +43,8 @@ export class AssetMultiSelectManager {
asControlContext(): AssetControlContext { asControlContext(): AssetControlContext {
return { return {
getOwnedAssets: () => this.assets.filter((asset) => asset.ownerId === this.#userId), getOwnedAssets: () =>
this.#userId ? this.assets.filter((asset) => asset.ownerId === this.#userId) : this.assets,
getAssets: () => this.assets, getAssets: () => this.assets,
clearSelect: () => this.clear(), clearSelect: () => this.clear(),
}; };
@@ -451,10 +451,7 @@
</main> </main>
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar <AssetSelectControlBar>
assets={assetMultiSelectManager.assets}
clearSelect={() => assetMultiSelectManager.clear()}
>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
<CreateSharedLink /> <CreateSharedLink />
@@ -59,7 +59,7 @@
</UserPageLayout> </UserPageLayout>
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar assets={assetMultiSelectManager.assets} clearSelect={() => assetMultiSelectManager.clear()}> <AssetSelectControlBar>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
<ArchiveAction <ArchiveAction
@@ -63,7 +63,7 @@
<!-- Multiselection mode app bar --> <!-- Multiselection mode app bar -->
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar assets={assetMultiSelectManager.assets} clearSelect={() => assetMultiSelectManager.clear()}> <AssetSelectControlBar>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
<FavoriteAction removeFavorite onFavorite={(assetIds) => timelineManager.removeAssets(assetIds)} /> <FavoriteAction removeFavorite onFavorite={(assetIds) => timelineManager.removeAssets(assetIds)} />
@@ -110,7 +110,7 @@
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<div class="fixed top-0 start-0 w-full"> <div class="fixed top-0 start-0 w-full">
<AssetSelectControlBar assets={assetMultiSelectManager.assets} clearSelect={() => assetMultiSelectManager.clear()}> <AssetSelectControlBar>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
<CreateSharedLink /> <CreateSharedLink />
@@ -74,7 +74,7 @@
<!-- Multi-selection mode app bar --> <!-- Multi-selection mode app bar -->
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar assets={assetMultiSelectManager.assets} clearSelect={() => assetMultiSelectManager.clear()}> <AssetSelectControlBar>
<SelectAllAssets withText {timelineManager} assetInteraction={assetMultiSelectManager} /> <SelectAllAssets withText {timelineManager} assetInteraction={assetMultiSelectManager} />
<SetVisibilityAction unlock onVisibilitySet={handleMoveOffLockedFolder} /> <SetVisibilityAction unlock onVisibilitySet={handleMoveOffLockedFolder} />
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}> <ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
@@ -39,7 +39,7 @@
</main> </main>
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar assets={assetMultiSelectManager.assets} clearSelect={() => assetMultiSelectManager.clear()}> <AssetSelectControlBar>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
<CreateSharedLink /> <CreateSharedLink />
@@ -458,7 +458,7 @@
<header> <header>
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar assets={assetMultiSelectManager.assets} clearSelect={() => assetMultiSelectManager.clear()}> <AssetSelectControlBar>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
<CreateSharedLink /> <CreateSharedLink />
@@ -25,7 +25,7 @@
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte'; import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { getAssetBulkActions } from '$lib/services/asset.service'; import { getAssetBulkActions } from '$lib/services/asset.service';
import { preferences, user } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { getAssetMediaUrl, memoryLaneTitle } from '$lib/utils'; import { getAssetMediaUrl, memoryLaneTitle } from '$lib/utils';
import { import {
updateStackedAssetInTimeline, updateStackedAssetInTimeline,
@@ -112,11 +112,7 @@
</UserPageLayout> </UserPageLayout>
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar <AssetSelectControlBar>
ownerId={$user.id}
assets={assetMultiSelectManager.assets}
clearSelect={() => assetMultiSelectManager.clear()}
>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
@@ -318,10 +318,7 @@
<section> <section>
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<div class="fixed top-0 start-0 w-full z-2"> <div class="fixed top-0 start-0 w-full z-2">
<AssetSelectControlBar <AssetSelectControlBar>
assets={assetMultiSelectManager.assets}
clearSelect={() => assetMultiSelectManager.clear()}
>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
@@ -27,7 +27,7 @@
import { Route } from '$lib/route'; import { Route } from '$lib/route';
import { getAssetBulkActions } from '$lib/services/asset.service'; import { getAssetBulkActions } from '$lib/services/asset.service';
import { getTagActions } from '$lib/services/tag.service'; import { getTagActions } from '$lib/services/tag.service';
import { preferences, user } from '$lib/stores/user.store'; import { preferences } from '$lib/stores/user.store';
import { joinPaths, TreeNode } from '$lib/utils/tree-utils'; import { joinPaths, TreeNode } from '$lib/utils/tree-utils';
import { getAllTags, type TagResponseDto } from '@immich/sdk'; import { getAllTags, type TagResponseDto } from '@immich/sdk';
import { ActionButton, CommandPaletteDefaultProvider, Text } from '@immich/ui'; import { ActionButton, CommandPaletteDefaultProvider, Text } from '@immich/ui';
@@ -113,11 +113,7 @@
<section> <section>
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<div class="fixed top-0 start-0 w-full"> <div class="fixed top-0 start-0 w-full">
<AssetSelectControlBar <AssetSelectControlBar>
ownerId={$user.id}
assets={assetMultiSelectManager.assets}
clearSelect={() => assetMultiSelectManager.clear()}
>
{@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())} {@const Actions = getAssetBulkActions($t, assetMultiSelectManager.asControlContext())}
<CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} /> <CommandPaletteDefaultProvider name={$t('assets')} actions={Object.values(Actions)} />
<CreateSharedLink /> <CreateSharedLink />
@@ -68,7 +68,7 @@
{/if} {/if}
{#if assetMultiSelectManager.selectionActive} {#if assetMultiSelectManager.selectionActive}
<AssetSelectControlBar assets={assetMultiSelectManager.assets} clearSelect={() => assetMultiSelectManager.clear()}> <AssetSelectControlBar>
<SelectAllAssets {timelineManager} assetInteraction={assetMultiSelectManager} /> <SelectAllAssets {timelineManager} assetInteraction={assetMultiSelectManager} />
<DeleteAssets force onAssetDelete={(assetIds) => timelineManager.removeAssets(assetIds)} /> <DeleteAssets force onAssetDelete={(assetIds) => timelineManager.removeAssets(assetIds)} />
<RestoreAssets onRestore={(assetIds) => timelineManager.removeAssets(assetIds)} /> <RestoreAssets onRestore={(assetIds) => timelineManager.removeAssets(assetIds)} />