Add initial select logic to asset viewer

This commit is contained in:
CJPeckover
2025-09-16 00:54:46 -04:00
parent 52903e510e
commit cf539cc033
3 changed files with 181 additions and 130 deletions
@@ -19,9 +19,12 @@
import ShareAction from '$lib/components/asset-viewer/actions/share-action.svelte'; import ShareAction from '$lib/components/asset-viewer/actions/share-action.svelte';
import ShowDetailAction from '$lib/components/asset-viewer/actions/show-detail-action.svelte'; import ShowDetailAction from '$lib/components/asset-viewer/actions/show-detail-action.svelte';
import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte'; import UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte';
import Icon from '$lib/components/elements/icon.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte'; import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AppRoute } from '$lib/constants'; import { AppRoute } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { user } from '$lib/stores/user.store'; import { user } from '$lib/stores/user.store';
import { photoZoomState } from '$lib/stores/zoom-image.store'; import { photoZoomState } from '$lib/stores/zoom-image.store';
@@ -41,6 +44,7 @@
import { IconButton } from '@immich/ui'; import { IconButton } from '@immich/ui';
import { import {
mdiAlertOutline, mdiAlertOutline,
mdiCheckCircle,
mdiCogRefreshOutline, mdiCogRefreshOutline,
mdiCompare, mdiCompare,
mdiContentCopy, mdiContentCopy,
@@ -59,12 +63,14 @@
interface Props { interface Props {
asset: AssetResponseDto; asset: AssetResponseDto;
assetInteraction: AssetInteraction;
album?: AlbumResponseDto | null; album?: AlbumResponseDto | null;
person?: PersonResponseDto | null; person?: PersonResponseDto | null;
stack?: StackResponseDto | null; stack?: StackResponseDto | null;
showCloseButton?: boolean; showCloseButton?: boolean;
showDetailButton: boolean; showDetailButton: boolean;
showSlideshow?: boolean; showSlideshow?: boolean;
onSelectAsset: (asset: TimelineAsset) => void;
onZoomImage: () => void; onZoomImage: () => void;
onCopyImage?: () => Promise<void>; onCopyImage?: () => Promise<void>;
preAction: PreAction; preAction: PreAction;
@@ -79,12 +85,14 @@
let { let {
asset, asset,
assetInteraction,
album = null, album = null,
person = null, person = null,
stack = null, stack = null,
showCloseButton = true, showCloseButton = true,
showDetailButton, showDetailButton,
showSlideshow = false, showSlideshow = false,
onSelectAsset,
onZoomImage, onZoomImage,
onCopyImage, onCopyImage,
preAction, preAction,
@@ -102,6 +110,7 @@
let isLocked = $derived(asset.visibility === AssetVisibility.Locked); let isLocked = $derived(asset.visibility === AssetVisibility.Locked);
let smartSearchEnabled = $derived($featureFlags.loaded && $featureFlags.smartSearch); let smartSearchEnabled = $derived($featureFlags.loaded && $featureFlags.smartSearch);
let selected = $derived(assetInteraction.hasSelectedAsset(asset.id));
// $: showEditorButton = // $: showEditorButton =
// isOwner && // isOwner &&
// asset.type === AssetTypeEnum.Image && // asset.type === AssetTypeEnum.Image &&
@@ -122,6 +131,31 @@
{/if} {/if}
</div> </div>
<div class="flex gap-2 overflow-x-auto dark" data-testid="asset-viewer-navbar-actions"> <div class="flex gap-2 overflow-x-auto dark" data-testid="asset-viewer-navbar-actions">
{#if assetInteraction.selectionActive}
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">
{#if selected}
{$t('selected')}
{:else}
{$t('select')}
{/if}
</p>
<button
type="button"
onclick={()=>onSelectAsset(toTimelineAsset(asset))}
class={['focus:outline-none']}
role="checkbox"
tabindex={-1}
aria-checked={selected}
>
{#if selected}
<div class="rounded-full bg-[#D9DCEF] dark:bg-[#232932]">
<Icon path={mdiCheckCircle} size="24" class="text-primary" />
</div>
{:else}
<Icon path={mdiCheckCircle} size="24" class="text-white/80 hover:text-white" />
{/if}
</button>
{:else}
<CastButton /> <CastButton />
{#if !asset.isTrashed && $user && !isLocked} {#if !asset.isTrashed && $user && !isLocked}
@@ -176,7 +210,13 @@
{#if isOwner} {#if isOwner}
<DeleteAction {asset} {onAction} {preAction} /> <DeleteAction {asset} {onAction} {preAction} />
<ButtonContextMenu direction="left" align="top-right" color="secondary" title={$t('more')} icon={mdiDotsVertical}> <ButtonContextMenu
direction="left"
align="top-right"
color="secondary"
title={$t('more')}
icon={mdiDotsVertical}
>
{#if showSlideshow && !isLocked} {#if showSlideshow && !isLocked}
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} /> <MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
{/if} {/if}
@@ -231,7 +271,8 @@
{#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled} {#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled}
<MenuOption <MenuOption
icon={mdiCompare} icon={mdiCompare}
onClick={() => goto(`${AppRoute.SEARCH}?query={"queryAssetId":"${stack?.primaryAssetId ?? asset.id}"}`)} onClick={() =>
goto(`${AppRoute.SEARCH}?query={"queryAssetId":"${stack?.primaryAssetId ?? asset.id}"}`)}
text={$t('view_similar_photos')} text={$t('view_similar_photos')}
/> />
{/if} {/if}
@@ -266,5 +307,6 @@
{/if} {/if}
</ButtonContextMenu> </ButtonContextMenu>
{/if} {/if}
{/if}
</div> </div>
</div> </div>
@@ -10,6 +10,7 @@
import { authManager } from '$lib/managers/auth-manager.svelte'; import { authManager } from '$lib/managers/auth-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types'; import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { closeEditorCofirm } from '$lib/stores/asset-editor.store'; import { closeEditorCofirm } from '$lib/stores/asset-editor.store';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { isShowDetail } from '$lib/stores/preferences.store'; import { isShowDetail } from '$lib/stores/preferences.store';
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
@@ -49,6 +50,7 @@
interface Props { interface Props {
asset: AssetResponseDto; asset: AssetResponseDto;
assetInteraction: AssetInteraction;
preloadAssets?: TimelineAsset[]; preloadAssets?: TimelineAsset[];
showNavigation?: boolean; showNavigation?: boolean;
withStacked?: boolean; withStacked?: boolean;
@@ -58,6 +60,7 @@
preAction?: PreAction | undefined; preAction?: PreAction | undefined;
onAction?: OnAction | undefined; onAction?: OnAction | undefined;
showCloseButton?: boolean; showCloseButton?: boolean;
onSelectAsset: (asset: TimelineAsset) => void;
onClose: (asset: AssetResponseDto) => void; onClose: (asset: AssetResponseDto) => void;
onNext: () => Promise<HasAsset>; onNext: () => Promise<HasAsset>;
onPrevious: () => Promise<HasAsset>; onPrevious: () => Promise<HasAsset>;
@@ -67,6 +70,7 @@
let { let {
asset = $bindable(), asset = $bindable(),
assetInteraction,
preloadAssets = $bindable([]), preloadAssets = $bindable([]),
showNavigation = true, showNavigation = true,
withStacked = false, withStacked = false,
@@ -76,6 +80,7 @@
preAction = undefined, preAction = undefined,
onAction = undefined, onAction = undefined,
showCloseButton, showCloseButton,
onSelectAsset,
onClose, onClose,
onNext, onNext,
onPrevious, onPrevious,
@@ -391,12 +396,14 @@
<div class="col-span-4 col-start-1 row-span-1 row-start-1 transition-transform"> <div class="col-span-4 col-start-1 row-span-1 row-start-1 transition-transform">
<AssetViewerNavBar <AssetViewerNavBar
{asset} {asset}
{assetInteraction}
{album} {album}
{person} {person}
{stack} {stack}
{showCloseButton} {showCloseButton}
showDetailButton={enableDetailPanel} showDetailButton={enableDetailPanel}
showSlideshow={true} showSlideshow={true}
{onSelectAsset}
onZoomImage={zoomToggle} onZoomImage={zoomToggle}
onCopyImage={copyImage} onCopyImage={copyImage}
preAction={handlePreAction} preAction={handlePreAction}
@@ -529,7 +536,7 @@
</div> </div>
{/if} {/if}
{#if enableDetailPanel && $slideshowState === SlideshowState.None && $isShowDetail && !isShowEditor} {#if enableDetailPanel && $slideshowState === SlideshowState.None && $isShowDetail && !isShowEditor && !assetInteraction.selectionActive}
<div <div
transition:fly={{ duration: 150 }} transition:fly={{ duration: 150 }}
id="detail-panel" id="detail-panel"
@@ -988,6 +988,8 @@
onNext={handleNext} onNext={handleNext}
onRandom={handleRandom} onRandom={handleRandom}
onClose={handleClose} onClose={handleClose}
onSelectAsset={handleSelectAssets}
{assetInteraction}
/> />
{/await} {/await}
{/if} {/if}