diff --git a/i18n/en.json b/i18n/en.json
index 3bb0ce1ad8..3a887782f4 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -1631,6 +1631,7 @@
"set_date_of_birth": "Set date of birth",
"set_profile_picture": "Set profile picture",
"set_slideshow_to_fullscreen": "Set Slideshow to fullscreen",
+ "set_stack_primary_asset": "Set as primary asset",
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
"setting_image_viewer_original_title": "Load original image",
diff --git a/web/src/lib/components/asset-viewer/actions/action.ts b/web/src/lib/components/asset-viewer/actions/action.ts
index fd37d253aa..d823f17df4 100644
--- a/web/src/lib/components/asset-viewer/actions/action.ts
+++ b/web/src/lib/components/asset-viewer/actions/action.ts
@@ -1,6 +1,6 @@
import type { AssetAction } from '$lib/constants';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
-import type { AlbumResponseDto } from '@immich/sdk';
+import type { AlbumResponseDto, StackResponseDto } from '@immich/sdk';
type ActionMap = {
[AssetAction.ARCHIVE]: { asset: TimelineAsset };
@@ -14,6 +14,7 @@ type ActionMap = {
[AssetAction.ADD_TO_ALBUM]: { asset: TimelineAsset; album: AlbumResponseDto };
[AssetAction.UNSTACK]: { assets: TimelineAsset[] };
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: TimelineAsset };
+ [AssetAction.SET_STACK_PRIMARY_ASSET]: { stack: StackResponseDto };
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
};
diff --git a/web/src/lib/components/asset-viewer/actions/set-stack-primary-asset.svelte b/web/src/lib/components/asset-viewer/actions/set-stack-primary-asset.svelte
new file mode 100644
index 0000000000..6ca1548f4b
--- /dev/null
+++ b/web/src/lib/components/asset-viewer/actions/set-stack-primary-asset.svelte
@@ -0,0 +1,26 @@
+
+
+
diff --git a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
index e562f60319..a376c37139 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer-nav-bar.svelte
@@ -13,6 +13,7 @@
import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/set-album-cover-action.svelte';
import SetFeaturedPhotoAction from '$lib/components/asset-viewer/actions/set-person-featured-action.svelte';
import SetProfilePictureAction from '$lib/components/asset-viewer/actions/set-profile-picture-action.svelte';
+ import SetStackPrimaryAsset from '$lib/components/asset-viewer/actions/set-stack-primary-asset.svelte';
import SetVisibilityAction from '$lib/components/asset-viewer/actions/set-visibility-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';
@@ -192,6 +193,9 @@
{#if stack}
+ {#if stack?.primaryAssetId !== asset.id}
+
+ {/if}
{/if}
{#if album}
diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
index e5d2554e2c..a448f96c32 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -338,7 +338,10 @@
await handleGetAllAlbums();
break;
}
-
+ case AssetAction.SET_STACK_PRIMARY_ASSET: {
+ stack = action.stack;
+ break;
+ }
case AssetAction.KEEP_THIS_DELETE_OTHERS:
case AssetAction.UNSTACK: {
closeViewer();
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte
index dec0c0096c..1b96353d37 100644
--- a/web/src/lib/components/photos-page/asset-grid.svelte
+++ b/web/src/lib/components/photos-page/asset-grid.svelte
@@ -31,7 +31,7 @@
import { deleteAssets, updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
import { archiveAssets, cancelMultiselect, selectAllAssets, stackAssets } from '$lib/utils/asset-utils';
import { navigate } from '$lib/utils/navigation';
- import { type ScrubberListener, type TimelinePlainYearMonth } from '$lib/utils/timeline-util';
+ import { toTimelineAsset, type ScrubberListener, type TimelinePlainYearMonth } from '$lib/utils/timeline-util';
import { AssetVisibility, getAssetInfo, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
import { DateTime } from 'luxon';
import { onMount, type Snippet } from 'svelte';
@@ -511,6 +511,20 @@
updateUnstackedAssetInTimeline(assetStore, action.assets);
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(
+ assetStore,
+ action.stack.assets.map((asset) => toTimelineAsset(asset)),
+ );
+ updateStackedAssetInTimeline(assetStore, {
+ stack: action.stack,
+ toDeleteIds: action.stack.assets
+ .filter((asset) => asset.id !== action.stack.primaryAssetId)
+ .map((asset) => asset.id),
+ });
+ break;
+ }
}
};
diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts
index ae53b4e7f3..c27727d4b3 100644
--- a/web/src/lib/constants.ts
+++ b/web/src/lib/constants.ts
@@ -10,6 +10,7 @@ export enum AssetAction {
ADD_TO_ALBUM = 'add-to-album',
UNSTACK = 'unstack',
KEEP_THIS_DELETE_OTHERS = 'keep-this-delete-others',
+ SET_STACK_PRIMARY_ASSET = 'set-stack-primary-asset',
SET_VISIBILITY_LOCKED = 'set-visibility-locked',
SET_VISIBILITY_TIMELINE = 'set-visibility-timeline',
}