mirror of
https://github.com/immich-app/immich.git
synced 2025-12-07 21:30:59 -08:00
Compare commits
11 Commits
fix/docs-f
...
feat/multi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af605876ad | ||
|
|
585199553f | ||
|
|
cb321afada | ||
|
|
22cbf39fcf | ||
|
|
097dc3d21e | ||
|
|
7fae893558 | ||
|
|
669a7a9a10 | ||
|
|
360b1fea2d | ||
|
|
8497364cc7 | ||
|
|
cf539cc033 | ||
|
|
52903e510e |
@@ -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 | null;
|
||||||
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,149 +131,182 @@
|
|||||||
{/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">
|
||||||
<CastButton />
|
{#if !!onSelectAsset && assetInteraction?.selectionActive}
|
||||||
|
<p class="text-lg text-immich-fg dark:text-immich-dark-fg">
|
||||||
{#if !asset.isTrashed && $user && !isLocked}
|
{#if selected}
|
||||||
<ShareAction {asset} />
|
{$t('selected')}
|
||||||
{/if}
|
{:else}
|
||||||
{#if asset.isOffline}
|
{$t('select')}
|
||||||
<IconButton
|
|
||||||
shape="round"
|
|
||||||
color="danger"
|
|
||||||
icon={mdiAlertOutline}
|
|
||||||
onclick={onShowDetail}
|
|
||||||
aria-label={$t('asset_offline')}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if asset.livePhotoVideoId}
|
|
||||||
{@render motionPhoto?.()}
|
|
||||||
{/if}
|
|
||||||
{#if asset.type === AssetTypeEnum.Image}
|
|
||||||
<IconButton
|
|
||||||
class="hidden sm:flex"
|
|
||||||
color="secondary"
|
|
||||||
variant="ghost"
|
|
||||||
shape="round"
|
|
||||||
icon={$photoZoomState && $photoZoomState.currentZoom > 1 ? mdiMagnifyMinusOutline : mdiMagnifyPlusOutline}
|
|
||||||
aria-label={$t('zoom_image')}
|
|
||||||
onclick={onZoomImage}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if canCopyImageToClipboard() && asset.type === AssetTypeEnum.Image}
|
|
||||||
<IconButton
|
|
||||||
color="secondary"
|
|
||||||
variant="ghost"
|
|
||||||
shape="round"
|
|
||||||
icon={mdiContentCopy}
|
|
||||||
aria-label={$t('copy_image')}
|
|
||||||
onclick={() => onCopyImage?.()}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !isOwner && showDownloadButton}
|
|
||||||
<DownloadAction asset={toTimelineAsset(asset)} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if showDetailButton}
|
|
||||||
<ShowDetailAction {onShowDetail} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isOwner}
|
|
||||||
<FavoriteAction {asset} {onAction} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isOwner}
|
|
||||||
<DeleteAction {asset} {onAction} {preAction} />
|
|
||||||
|
|
||||||
<ButtonContextMenu direction="left" align="top-right" color="secondary" title={$t('more')} icon={mdiDotsVertical}>
|
|
||||||
{#if showSlideshow && !isLocked}
|
|
||||||
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
|
|
||||||
{/if}
|
{/if}
|
||||||
{#if showDownloadButton}
|
</p>
|
||||||
<DownloadAction asset={toTimelineAsset(asset)} menuItem />
|
<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}
|
{/if}
|
||||||
|
</button>
|
||||||
|
{:else}
|
||||||
|
<CastButton />
|
||||||
|
|
||||||
{#if !isLocked}
|
{#if !asset.isTrashed && $user && !isLocked}
|
||||||
{#if asset.isTrashed}
|
<ShareAction {asset} />
|
||||||
<RestoreAction {asset} {onAction} />
|
{/if}
|
||||||
{:else}
|
{#if asset.isOffline}
|
||||||
<AddToAlbumAction {asset} {onAction} />
|
<IconButton
|
||||||
<AddToAlbumAction {asset} {onAction} shared />
|
shape="round"
|
||||||
{/if}
|
color="danger"
|
||||||
{/if}
|
icon={mdiAlertOutline}
|
||||||
|
onclick={onShowDetail}
|
||||||
|
aria-label={$t('asset_offline')}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if asset.livePhotoVideoId}
|
||||||
|
{@render motionPhoto?.()}
|
||||||
|
{/if}
|
||||||
|
{#if asset.type === AssetTypeEnum.Image}
|
||||||
|
<IconButton
|
||||||
|
class="hidden sm:flex"
|
||||||
|
color="secondary"
|
||||||
|
variant="ghost"
|
||||||
|
shape="round"
|
||||||
|
icon={$photoZoomState && $photoZoomState.currentZoom > 1 ? mdiMagnifyMinusOutline : mdiMagnifyPlusOutline}
|
||||||
|
aria-label={$t('zoom_image')}
|
||||||
|
onclick={onZoomImage}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if canCopyImageToClipboard() && asset.type === AssetTypeEnum.Image}
|
||||||
|
<IconButton
|
||||||
|
color="secondary"
|
||||||
|
variant="ghost"
|
||||||
|
shape="round"
|
||||||
|
icon={mdiContentCopy}
|
||||||
|
aria-label={$t('copy_image')}
|
||||||
|
onclick={() => onCopyImage?.()}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if isOwner}
|
{#if !isOwner && showDownloadButton}
|
||||||
{#if stack}
|
<DownloadAction asset={toTimelineAsset(asset)} />
|
||||||
<UnstackAction {stack} {onAction} />
|
{/if}
|
||||||
<KeepThisDeleteOthersAction {stack} {asset} {onAction} />
|
|
||||||
{#if stack?.primaryAssetId !== asset.id}
|
{#if showDetailButton}
|
||||||
<SetStackPrimaryAsset {stack} {asset} {onAction} />
|
<ShowDetailAction {onShowDetail} />
|
||||||
{#if stack?.assets?.length > 2}
|
{/if}
|
||||||
<RemoveAssetFromStack {asset} {stack} {onAction} />
|
|
||||||
{/if}
|
{#if isOwner}
|
||||||
{/if}
|
<FavoriteAction {asset} {onAction} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if isOwner}
|
||||||
|
<DeleteAction {asset} {onAction} {preAction} />
|
||||||
|
|
||||||
|
<ButtonContextMenu
|
||||||
|
direction="left"
|
||||||
|
align="top-right"
|
||||||
|
color="secondary"
|
||||||
|
title={$t('more')}
|
||||||
|
icon={mdiDotsVertical}
|
||||||
|
>
|
||||||
|
{#if showSlideshow && !isLocked}
|
||||||
|
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if album}
|
{#if showDownloadButton}
|
||||||
<SetAlbumCoverAction {asset} {album} />
|
<DownloadAction asset={toTimelineAsset(asset)} menuItem />
|
||||||
{/if}
|
|
||||||
{#if person}
|
|
||||||
<SetFeaturedPhotoAction {asset} {person} />
|
|
||||||
{/if}
|
|
||||||
{#if asset.type === AssetTypeEnum.Image && !isLocked}
|
|
||||||
<SetProfilePictureAction {asset} />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !isLocked}
|
{#if !isLocked}
|
||||||
<ArchiveAction {asset} {onAction} {preAction} />
|
{#if asset.isTrashed}
|
||||||
<MenuOption
|
<RestoreAction {asset} {onAction} />
|
||||||
icon={mdiUpload}
|
{:else}
|
||||||
onClick={() => openFileUploadDialog({ multiple: false, assetId: asset.id })}
|
<AddToAlbumAction {asset} {onAction} />
|
||||||
text={$t('replace_with_upload')}
|
<AddToAlbumAction {asset} {onAction} shared />
|
||||||
/>
|
|
||||||
{#if !asset.isArchived && !asset.isTrashed}
|
|
||||||
<MenuOption
|
|
||||||
icon={mdiImageSearch}
|
|
||||||
onClick={() => goto(`${AppRoute.PHOTOS}?at=${stack?.primaryAssetId ?? asset.id}`)}
|
|
||||||
text={$t('view_in_timeline')}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled}
|
|
||||||
<MenuOption
|
|
||||||
icon={mdiCompare}
|
|
||||||
onClick={() => goto(`${AppRoute.SEARCH}?query={"queryAssetId":"${stack?.primaryAssetId ?? asset.id}"}`)}
|
|
||||||
text={$t('view_similar_photos')}
|
|
||||||
/>
|
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !asset.isTrashed}
|
{#if isOwner}
|
||||||
<SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} />
|
{#if stack}
|
||||||
{/if}
|
<UnstackAction {stack} {onAction} />
|
||||||
<hr />
|
<KeepThisDeleteOthersAction {stack} {asset} {onAction} />
|
||||||
<MenuOption
|
{#if stack?.primaryAssetId !== asset.id}
|
||||||
icon={mdiHeadSyncOutline}
|
<SetStackPrimaryAsset {stack} {asset} {onAction} />
|
||||||
onClick={() => onRunJob(AssetJobName.RefreshFaces)}
|
{#if stack?.assets?.length > 2}
|
||||||
text={$getAssetJobName(AssetJobName.RefreshFaces)}
|
<RemoveAssetFromStack {asset} {stack} {onAction} />
|
||||||
/>
|
{/if}
|
||||||
<MenuOption
|
{/if}
|
||||||
icon={mdiDatabaseRefreshOutline}
|
{/if}
|
||||||
onClick={() => onRunJob(AssetJobName.RefreshMetadata)}
|
{#if album}
|
||||||
text={$getAssetJobName(AssetJobName.RefreshMetadata)}
|
<SetAlbumCoverAction {asset} {album} />
|
||||||
/>
|
{/if}
|
||||||
<MenuOption
|
{#if person}
|
||||||
icon={mdiImageRefreshOutline}
|
<SetFeaturedPhotoAction {asset} {person} />
|
||||||
onClick={() => onRunJob(AssetJobName.RegenerateThumbnail)}
|
{/if}
|
||||||
text={$getAssetJobName(AssetJobName.RegenerateThumbnail)}
|
{#if asset.type === AssetTypeEnum.Image && !isLocked}
|
||||||
/>
|
<SetProfilePictureAction {asset} />
|
||||||
{#if asset.type === AssetTypeEnum.Video}
|
{/if}
|
||||||
|
|
||||||
|
{#if !isLocked}
|
||||||
|
<ArchiveAction {asset} {onAction} {preAction} />
|
||||||
|
<MenuOption
|
||||||
|
icon={mdiUpload}
|
||||||
|
onClick={() => openFileUploadDialog({ multiple: false, assetId: asset.id })}
|
||||||
|
text={$t('replace_with_upload')}
|
||||||
|
/>
|
||||||
|
{#if !asset.isArchived && !asset.isTrashed}
|
||||||
|
<MenuOption
|
||||||
|
icon={mdiImageSearch}
|
||||||
|
onClick={() => goto(`${AppRoute.PHOTOS}?at=${stack?.primaryAssetId ?? asset.id}`)}
|
||||||
|
text={$t('view_in_timeline')}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if !asset.isArchived && !asset.isTrashed && smartSearchEnabled}
|
||||||
|
<MenuOption
|
||||||
|
icon={mdiCompare}
|
||||||
|
onClick={() =>
|
||||||
|
goto(`${AppRoute.SEARCH}?query={"queryAssetId":"${stack?.primaryAssetId ?? asset.id}"}`)}
|
||||||
|
text={$t('view_similar_photos')}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if !asset.isTrashed}
|
||||||
|
<SetVisibilityAction asset={toTimelineAsset(asset)} {onAction} {preAction} />
|
||||||
|
{/if}
|
||||||
|
<hr />
|
||||||
<MenuOption
|
<MenuOption
|
||||||
icon={mdiCogRefreshOutline}
|
icon={mdiHeadSyncOutline}
|
||||||
onClick={() => onRunJob(AssetJobName.TranscodeVideo)}
|
onClick={() => onRunJob(AssetJobName.RefreshFaces)}
|
||||||
text={$getAssetJobName(AssetJobName.TranscodeVideo)}
|
text={$getAssetJobName(AssetJobName.RefreshFaces)}
|
||||||
/>
|
/>
|
||||||
|
<MenuOption
|
||||||
|
icon={mdiDatabaseRefreshOutline}
|
||||||
|
onClick={() => onRunJob(AssetJobName.RefreshMetadata)}
|
||||||
|
text={$getAssetJobName(AssetJobName.RefreshMetadata)}
|
||||||
|
/>
|
||||||
|
<MenuOption
|
||||||
|
icon={mdiImageRefreshOutline}
|
||||||
|
onClick={() => onRunJob(AssetJobName.RegenerateThumbnail)}
|
||||||
|
text={$getAssetJobName(AssetJobName.RegenerateThumbnail)}
|
||||||
|
/>
|
||||||
|
{#if asset.type === AssetTypeEnum.Video}
|
||||||
|
<MenuOption
|
||||||
|
icon={mdiCogRefreshOutline}
|
||||||
|
onClick={() => onRunJob(AssetJobName.TranscodeVideo)}
|
||||||
|
text={$getAssetJobName(AssetJobName.TranscodeVideo)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/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"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
mdiCameraBurst,
|
mdiCameraBurst,
|
||||||
mdiCheckCircle,
|
mdiCheckCircle,
|
||||||
mdiHeart,
|
mdiHeart,
|
||||||
|
mdiMagnifyPlusOutline,
|
||||||
mdiMotionPauseOutline,
|
mdiMotionPauseOutline,
|
||||||
mdiMotionPlayOutline,
|
mdiMotionPlayOutline,
|
||||||
mdiRotate360,
|
mdiRotate360,
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
thumbnailHeight?: number;
|
thumbnailHeight?: number;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
selectionCandidate?: boolean;
|
selectionCandidate?: boolean;
|
||||||
|
selectionActive?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
disableLinkMouseOver?: boolean;
|
disableLinkMouseOver?: boolean;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
@@ -45,7 +47,7 @@
|
|||||||
imageClass?: ClassValue;
|
imageClass?: ClassValue;
|
||||||
brokenAssetClass?: ClassValue;
|
brokenAssetClass?: ClassValue;
|
||||||
dimmed?: boolean;
|
dimmed?: boolean;
|
||||||
onClick?: (asset: TimelineAsset) => void;
|
onClick?: (asset: TimelineAsset, forceView?: boolean) => void;
|
||||||
onSelect?: (asset: TimelineAsset) => void;
|
onSelect?: (asset: TimelineAsset) => void;
|
||||||
onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void;
|
onMouseEvent?: (event: { isMouseOver: boolean; selectedGroupIndex: number }) => void;
|
||||||
}
|
}
|
||||||
@@ -58,6 +60,7 @@
|
|||||||
thumbnailHeight = undefined,
|
thumbnailHeight = undefined,
|
||||||
selected = false,
|
selected = false,
|
||||||
selectionCandidate = false,
|
selectionCandidate = false,
|
||||||
|
selectionActive = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
disableLinkMouseOver = false,
|
disableLinkMouseOver = false,
|
||||||
readonly = false,
|
readonly = false,
|
||||||
@@ -92,6 +95,12 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onViewerIconClickedHandler = (e?: MouseEvent) => {
|
||||||
|
e?.stopPropagation();
|
||||||
|
e?.preventDefault();
|
||||||
|
onClick?.($state.snapshot(asset), true);
|
||||||
|
};
|
||||||
|
|
||||||
const callClickHandlers = () => {
|
const callClickHandlers = () => {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
onIconClickedHandler();
|
onIconClickedHandler();
|
||||||
@@ -344,6 +353,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- View Asset while selecting -->
|
||||||
|
{#if selectionActive && (usingMobileDevice || mouseOver)}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={onViewerIconClickedHandler}
|
||||||
|
class={['absolute focus:outline-none bottom-2 end-2', { 'cursor-not-allowed': disabled }]}
|
||||||
|
tabindex={-1}
|
||||||
|
{disabled}
|
||||||
|
>
|
||||||
|
<Icon path={mdiMagnifyPlusOutline} size="24" class="text-white/80 hover:text-white" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if (!loaded || thumbError) && asset.thumbhash}
|
{#if (!loaded || thumbError) && asset.thumbhash}
|
||||||
<canvas
|
<canvas
|
||||||
use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
|
use:thumbhash={{ base64ThumbHash: asset.thumbhash }}
|
||||||
|
|||||||
@@ -75,8 +75,9 @@
|
|||||||
assets: TimelineAsset[],
|
assets: TimelineAsset[],
|
||||||
groupTitle: string,
|
groupTitle: string,
|
||||||
asset: TimelineAsset,
|
asset: TimelineAsset,
|
||||||
|
forceView: boolean = false,
|
||||||
) => {
|
) => {
|
||||||
if (isSelectionMode || assetInteraction.selectionActive) {
|
if (!forceView && (isSelectionMode || assetInteraction.selectionActive)) {
|
||||||
assetSelectHandler(timelineManager, asset, assets, groupTitle);
|
assetSelectHandler(timelineManager, asset, assets, groupTitle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -218,11 +219,11 @@
|
|||||||
{showArchiveIcon}
|
{showArchiveIcon}
|
||||||
{asset}
|
{asset}
|
||||||
{groupIndex}
|
{groupIndex}
|
||||||
onClick={(asset) => {
|
onClick={(asset, forceView: boolean = false) => {
|
||||||
if (typeof onThumbnailClick === 'function') {
|
if (typeof onThumbnailClick === 'function') {
|
||||||
onThumbnailClick(asset, timelineManager, dayGroup, _onClick);
|
onThumbnailClick(asset, timelineManager, dayGroup, _onClick);
|
||||||
} else {
|
} else {
|
||||||
_onClick(timelineManager, dayGroup.getAssets(), dayGroup.groupTitle, asset);
|
_onClick(timelineManager, dayGroup.getAssets(), dayGroup.groupTitle, asset, forceView);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onSelect={(asset) => assetSelectHandler(timelineManager, asset, dayGroup.getAssets(), dayGroup.groupTitle)}
|
onSelect={(asset) => assetSelectHandler(timelineManager, asset, dayGroup.getAssets(), dayGroup.groupTitle)}
|
||||||
@@ -230,6 +231,7 @@
|
|||||||
selected={assetInteraction.hasSelectedAsset(asset.id) ||
|
selected={assetInteraction.hasSelectedAsset(asset.id) ||
|
||||||
dayGroup.monthGroup.timelineManager.albumAssets.has(asset.id)}
|
dayGroup.monthGroup.timelineManager.albumAssets.has(asset.id)}
|
||||||
selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)}
|
selectionCandidate={assetInteraction.hasSelectionCandidate(asset.id)}
|
||||||
|
selectionActive={assetInteraction.selectionActive}
|
||||||
disabled={dayGroup.monthGroup.timelineManager.albumAssets.has(asset.id)}
|
disabled={dayGroup.monthGroup.timelineManager.albumAssets.has(asset.id)}
|
||||||
thumbnailWidth={position.width}
|
thumbnailWidth={position.width}
|
||||||
thumbnailHeight={position.height}
|
thumbnailHeight={position.height}
|
||||||
|
|||||||
@@ -494,8 +494,8 @@
|
|||||||
>
|
>
|
||||||
<Thumbnail
|
<Thumbnail
|
||||||
readonly={disableAssetSelect}
|
readonly={disableAssetSelect}
|
||||||
onClick={() => {
|
onClick={(asset, forceView: boolean = false) => {
|
||||||
if (assetInteraction.selectionActive) {
|
if (assetInteraction.selectionActive && !forceView) {
|
||||||
handleSelectAssets(toTimelineAsset(currentAsset));
|
handleSelectAssets(toTimelineAsset(currentAsset));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -507,6 +507,7 @@
|
|||||||
asset={toTimelineAsset(currentAsset)}
|
asset={toTimelineAsset(currentAsset)}
|
||||||
selected={assetInteraction.hasSelectedAsset(currentAsset.id)}
|
selected={assetInteraction.hasSelectedAsset(currentAsset.id)}
|
||||||
selectionCandidate={assetInteraction.hasSelectionCandidate(currentAsset.id)}
|
selectionCandidate={assetInteraction.hasSelectionCandidate(currentAsset.id)}
|
||||||
|
selectionActive={assetInteraction.selectionActive}
|
||||||
thumbnailWidth={layout.width}
|
thumbnailWidth={layout.width}
|
||||||
thumbnailHeight={layout.height}
|
thumbnailHeight={layout.height}
|
||||||
/>
|
/>
|
||||||
@@ -528,10 +529,12 @@
|
|||||||
<Portal target="body">
|
<Portal target="body">
|
||||||
<AssetViewer
|
<AssetViewer
|
||||||
asset={$viewingAsset}
|
asset={$viewingAsset}
|
||||||
|
{assetInteraction}
|
||||||
onAction={handleAction}
|
onAction={handleAction}
|
||||||
onPrevious={handlePrevious}
|
onPrevious={handlePrevious}
|
||||||
onNext={handleNext}
|
onNext={handleNext}
|
||||||
onRandom={handleRandom}
|
onRandom={handleRandom}
|
||||||
|
onSelectAsset={handleSelectAssets}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
assetViewingStore.showAssetViewer(false);
|
assetViewingStore.showAssetViewer(false);
|
||||||
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
handlePromiseError(navigate({ targetRoute: 'current', assetId: null }));
|
||||||
|
|||||||
@@ -991,6 +991,8 @@
|
|||||||
onNext={handleNext}
|
onNext={handleNext}
|
||||||
onRandom={handleRandom}
|
onRandom={handleRandom}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
|
onSelectAsset={handleSelectAssets}
|
||||||
|
{assetInteraction}
|
||||||
/>
|
/>
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user