Merge asset viewer components into asset-grid-asset-viewer

• Rename asset-viewer-actions.svelte to asset-grid-asset-viewer.svelte
• Move AssetViewer rendering from asset-viewer-and-actions into asset-grid-asset-viewer
• Remove asset-viewer-and-actions.svelte intermediate wrapper
• Remove unused isShowDeleteConfirmation prop
This commit is contained in:
midzelis 2025-08-14 21:06:22 +00:00
parent 14954c669b
commit 76efc48b1b
2 changed files with 191 additions and 12 deletions

View file

@ -0,0 +1,188 @@
<script lang="ts">
import type { Action } from '$lib/components/asset-viewer/actions/action';
import { AssetAction } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
import { navigate } from '$lib/utils/navigation';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { getAssetInfo, type AlbumResponseDto, type AssetResponseDto, type PersonResponseDto } from '@immich/sdk';
let { asset: viewingAsset, gridScrollTarget, mutex, preloadAssets } = assetViewingStore;
interface Props {
timelineManager: TimelineManager;
showSkeleton: boolean;
withStacked?: boolean;
isShared?: boolean;
album?: AlbumResponseDto | null;
person?: PersonResponseDto | null;
removeAction?:
| AssetAction.UNARCHIVE
| AssetAction.ARCHIVE
| AssetAction.FAVORITE
| AssetAction.UNFAVORITE
| AssetAction.SET_VISIBILITY_TIMELINE;
handlePreAction?: (action: Action) => Promise<void>;
handleAction?: (action: Action) => void;
handleNext?: () => Promise<boolean>;
handlePrevious?: () => Promise<boolean>;
handleRandom?: () => Promise<AssetResponseDto | undefined>;
handleClose?: (asset: { id: string }) => Promise<void>;
}
let {
timelineManager = $bindable(),
showSkeleton = $bindable(false),
removeAction,
withStacked = false,
isShared = false,
album = null,
person = null,
handlePreAction = $bindable(),
handleAction = $bindable(),
handleNext = $bindable(),
handlePrevious = $bindable(),
handleRandom = $bindable(),
handleClose = $bindable(),
}: Props = $props();
handlePrevious = async () => {
const release = await mutex.acquire();
const laterAsset = await timelineManager.getLaterAsset($viewingAsset);
if (laterAsset) {
const preloadAsset = await timelineManager.getLaterAsset(laterAsset);
const asset = await getAssetInfo({ ...authManager.params, id: laterAsset.id });
assetViewingStore.setAsset(asset, preloadAsset ? [preloadAsset] : []);
await navigate({ targetRoute: 'current', assetId: laterAsset.id });
}
release();
return !!laterAsset;
};
handleNext = async () => {
const release = await mutex.acquire();
const earlierAsset = await timelineManager.getEarlierAsset($viewingAsset);
if (earlierAsset) {
const preloadAsset = await timelineManager.getEarlierAsset(earlierAsset);
const asset = await getAssetInfo({ ...authManager.params, id: earlierAsset.id });
assetViewingStore.setAsset(asset, preloadAsset ? [preloadAsset] : []);
await navigate({ targetRoute: 'current', assetId: earlierAsset.id });
}
release();
return !!earlierAsset;
};
handleRandom = async () => {
const randomAsset = await timelineManager.getRandomAsset();
if (randomAsset) {
const asset = await getAssetInfo({ ...authManager.params, id: randomAsset.id });
assetViewingStore.setAsset(asset);
await navigate({ targetRoute: 'current', assetId: randomAsset.id });
return asset;
}
};
handleClose = async (asset: { id: string }) => {
assetViewingStore.showAssetViewer(false);
showSkeleton = true;
$gridScrollTarget = { at: asset.id };
await navigate({ targetRoute: 'current', assetId: null, assetGridRouteSearchParams: $gridScrollTarget });
};
handlePreAction = async (action: Action) => {
switch (action.type) {
case removeAction:
case AssetAction.TRASH:
case AssetAction.RESTORE:
case AssetAction.DELETE:
case AssetAction.ARCHIVE:
case AssetAction.SET_VISIBILITY_LOCKED:
case AssetAction.SET_VISIBILITY_TIMELINE: {
// find the next asset to show or close the viewer
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
(await handleNext()) || (await handlePrevious()) || (await handleClose(action.asset));
// delete after find the next one
timelineManager.removeAssets([action.asset.id]);
break;
}
}
};
handleAction = (action: Action) => {
switch (action.type) {
case AssetAction.ARCHIVE:
case AssetAction.UNARCHIVE:
case AssetAction.FAVORITE:
case AssetAction.UNFAVORITE: {
timelineManager.updateAssets([action.asset]);
break;
}
case AssetAction.ADD: {
timelineManager.addAssets([action.asset]);
break;
}
case AssetAction.UNSTACK: {
updateUnstackedAssetInTimeline(timelineManager, action.assets);
break;
}
case AssetAction.REMOVE_ASSET_FROM_STACK: {
timelineManager.addAssets([toTimelineAsset(action.asset)]);
if (action.stack) {
//Have to unstack then restack assets in timeline in order to update the stack count in the timeline.
updateUnstackedAssetInTimeline(
timelineManager,
action.stack.assets.map((asset) => toTimelineAsset(asset)),
);
updateStackedAssetInTimeline(timelineManager, {
stack: action.stack,
toDeleteIds: action.stack.assets
.filter((asset) => asset.id !== action.stack?.primaryAssetId)
.map((asset) => asset.id),
});
}
break;
}
case AssetAction.SET_STACK_PRIMARY_ASSET: {
//Have to unstack then restack assets in timeline in order for the currently removed new primary asset to be made visible.
updateUnstackedAssetInTimeline(
timelineManager,
action.stack.assets.map((asset) => toTimelineAsset(asset)),
);
updateStackedAssetInTimeline(timelineManager, {
stack: action.stack,
toDeleteIds: action.stack.assets
.filter((asset) => asset.id !== action.stack.primaryAssetId)
.map((asset) => asset.id),
});
break;
}
}
};
</script>
{#await import('../asset-viewer/asset-viewer.svelte') then { default: AssetViewer }}
<AssetViewer
{withStacked}
asset={$viewingAsset}
preloadAssets={$preloadAssets}
{isShared}
{album}
{person}
preAction={handlePreAction}
onAction={handleAction}
onPrevious={handlePrevious}
onNext={handleNext}
onRandom={handleRandom}
onClose={handleClose}
/>
{/await}

View file

@ -4,7 +4,7 @@
import { resizeObserver, type OnResizeCallback } from '$lib/actions/resize-observer';
import AssetDateGroupSelectionAware from '$lib/components/photos-page/asset-date-group-selection-aware.svelte';
import AssetGridActions from '$lib/components/photos-page/asset-grid-actions.svelte';
import AssetViewerAndActions from '$lib/components/photos-page/asset-viewer-and-actions.svelte';
import AssetGridAssetViewer from '$lib/components/photos-page/asset-grid-asset-viewer.svelte';
import Skeleton from '$lib/components/photos-page/skeleton.svelte';
import { AssetAction } from '$lib/constants';
import type { DayGroup } from '$lib/managers/timeline-manager/day-group.svelte';
@ -93,10 +93,6 @@
let scrubberWidth = $state(0);
// 60 is the bottom spacer element at 60px
let bottomSectionHeight = 60;
let leadout = $state(false);
const maxMd = $derived(mobileDevice.maxMd);
const usingMobileDevice = $derived(mobileDevice.pointerCoarse);
const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
@ -273,10 +269,6 @@
disposeHmr();
};
});
let onDateGroupSelect = <({ title, assets }: { title: string; assets: TimelineAsset[] }) => void>$state();
let onSelectAssets = <(asset: TimelineAsset) => Promise<void>>$state();
let onSelectAssetCandidates = <(asset: TimelineAsset | null) => void>$state();
</script>
<AssetGridActions {scrollToAsset} {timelineManager} {assetInteraction} bind:isShowDeleteConfirmation {onEscape}
@ -369,7 +361,7 @@
<Portal target="body">
{#if $showAssetViewer}
<AssetViewerAndActions
<AssetGridAssetViewer
bind:showSkeleton
{timelineManager}
{removeAction}
@ -377,8 +369,7 @@
{isShared}
{album}
{person}
{isShowDeleteConfirmation}
></AssetViewerAndActions>
/>
{/if}
</Portal>