mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
feat(web): Change the primary asset of a stack (#18913)
* - Add set primary primary asset button to asset viewer * - Cleanup - change AssetAction to contain a StackResponseDto - Properly update displayed stack at bottom of the asset viewer * - update the assetStore with the changed stack * - Cleanup
This commit is contained in:
parent
e0ac588ca8
commit
de2115d11e
@ -1631,6 +1631,7 @@
|
|||||||
"set_date_of_birth": "Set date of birth",
|
"set_date_of_birth": "Set date of birth",
|
||||||
"set_profile_picture": "Set profile picture",
|
"set_profile_picture": "Set profile picture",
|
||||||
"set_slideshow_to_fullscreen": "Set Slideshow to fullscreen",
|
"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_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_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",
|
"setting_image_viewer_original_title": "Load original image",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { AssetAction } from '$lib/constants';
|
import type { AssetAction } from '$lib/constants';
|
||||||
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
|
||||||
import type { AlbumResponseDto } from '@immich/sdk';
|
import type { AlbumResponseDto, StackResponseDto } from '@immich/sdk';
|
||||||
|
|
||||||
type ActionMap = {
|
type ActionMap = {
|
||||||
[AssetAction.ARCHIVE]: { asset: TimelineAsset };
|
[AssetAction.ARCHIVE]: { asset: TimelineAsset };
|
||||||
@ -14,6 +14,7 @@ type ActionMap = {
|
|||||||
[AssetAction.ADD_TO_ALBUM]: { asset: TimelineAsset; album: AlbumResponseDto };
|
[AssetAction.ADD_TO_ALBUM]: { asset: TimelineAsset; album: AlbumResponseDto };
|
||||||
[AssetAction.UNSTACK]: { assets: TimelineAsset[] };
|
[AssetAction.UNSTACK]: { assets: TimelineAsset[] };
|
||||||
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: TimelineAsset };
|
[AssetAction.KEEP_THIS_DELETE_OTHERS]: { asset: TimelineAsset };
|
||||||
|
[AssetAction.SET_STACK_PRIMARY_ASSET]: { stack: StackResponseDto };
|
||||||
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
|
[AssetAction.SET_VISIBILITY_LOCKED]: { asset: TimelineAsset };
|
||||||
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
|
[AssetAction.SET_VISIBILITY_TIMELINE]: { asset: TimelineAsset };
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
|
|
||||||
|
import { AssetAction } from '$lib/constants';
|
||||||
|
import { updateStack, type AssetResponseDto, type StackResponseDto } from '@immich/sdk';
|
||||||
|
import { mdiImageCheckOutline } from '@mdi/js';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import type { OnAction } from './action';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
stack: StackResponseDto;
|
||||||
|
asset: AssetResponseDto;
|
||||||
|
onAction: OnAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { stack, asset, onAction }: Props = $props();
|
||||||
|
|
||||||
|
const handleSetPrimaryAsset = async () => {
|
||||||
|
const updatedStack = await updateStack({ id: stack.id, stackUpdateDto: { primaryAssetId: asset.id } });
|
||||||
|
if (updatedStack) {
|
||||||
|
onAction({ type: AssetAction.SET_STACK_PRIMARY_ASSET, stack: updatedStack });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<MenuOption icon={mdiImageCheckOutline} onClick={handleSetPrimaryAsset} text={$t('set_stack_primary_asset')} />
|
@ -13,6 +13,7 @@
|
|||||||
import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/set-album-cover-action.svelte';
|
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 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 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 SetVisibilityAction from '$lib/components/asset-viewer/actions/set-visibility-action.svelte';
|
||||||
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';
|
||||||
@ -192,6 +193,9 @@
|
|||||||
{#if stack}
|
{#if stack}
|
||||||
<UnstackAction {stack} {onAction} />
|
<UnstackAction {stack} {onAction} />
|
||||||
<KeepThisDeleteOthersAction {stack} {asset} {onAction} />
|
<KeepThisDeleteOthersAction {stack} {asset} {onAction} />
|
||||||
|
{#if stack?.primaryAssetId !== asset.id}
|
||||||
|
<SetStackPrimaryAsset {stack} {asset} {onAction} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if album}
|
{#if album}
|
||||||
<SetAlbumCoverAction {asset} {album} />
|
<SetAlbumCoverAction {asset} {album} />
|
||||||
|
@ -338,7 +338,10 @@
|
|||||||
await handleGetAllAlbums();
|
await handleGetAllAlbums();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case AssetAction.SET_STACK_PRIMARY_ASSET: {
|
||||||
|
stack = action.stack;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case AssetAction.KEEP_THIS_DELETE_OTHERS:
|
case AssetAction.KEEP_THIS_DELETE_OTHERS:
|
||||||
case AssetAction.UNSTACK: {
|
case AssetAction.UNSTACK: {
|
||||||
closeViewer();
|
closeViewer();
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
import { deleteAssets, updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
|
import { deleteAssets, updateStackedAssetInTimeline, updateUnstackedAssetInTimeline } from '$lib/utils/actions';
|
||||||
import { archiveAssets, cancelMultiselect, selectAllAssets, stackAssets } from '$lib/utils/asset-utils';
|
import { archiveAssets, cancelMultiselect, selectAllAssets, stackAssets } from '$lib/utils/asset-utils';
|
||||||
import { navigate } from '$lib/utils/navigation';
|
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 { AssetVisibility, getAssetInfo, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { onMount, type Snippet } from 'svelte';
|
import { onMount, type Snippet } from 'svelte';
|
||||||
@ -511,6 +511,20 @@
|
|||||||
updateUnstackedAssetInTimeline(assetStore, action.assets);
|
updateUnstackedAssetInTimeline(assetStore, action.assets);
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ export enum AssetAction {
|
|||||||
ADD_TO_ALBUM = 'add-to-album',
|
ADD_TO_ALBUM = 'add-to-album',
|
||||||
UNSTACK = 'unstack',
|
UNSTACK = 'unstack',
|
||||||
KEEP_THIS_DELETE_OTHERS = 'keep-this-delete-others',
|
KEEP_THIS_DELETE_OTHERS = 'keep-this-delete-others',
|
||||||
|
SET_STACK_PRIMARY_ASSET = 'set-stack-primary-asset',
|
||||||
SET_VISIBILITY_LOCKED = 'set-visibility-locked',
|
SET_VISIBILITY_LOCKED = 'set-visibility-locked',
|
||||||
SET_VISIBILITY_TIMELINE = 'set-visibility-timeline',
|
SET_VISIBILITY_TIMELINE = 'set-visibility-timeline',
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user