mirror of
https://github.com/immich-app/immich
synced 2025-10-17 18:19:27 +00:00
* Fix: Disable 'As profile picture' option for videos in context menu asset-viewer-nav-bar.svelte This commit modifies the context menu behavior to disable the "As profile picture" option when interacting with video assets. Previously, the option was available for all asset types, including videos, which could lead to confusion when this displayed an error. With this change, the "As profile picture" option is conditionally rendered based on the asset type. If the asset is a video, the option is not displayed in the context menu. This adjustment enhances the web experience by preventing users from attempting to set a video as their profile picture, which is not supported by the system. Fixes: #7724 * Switched to check if photo instead of video
217 lines
7 KiB
Svelte
217 lines
7 KiB
Svelte
<script lang="ts">
|
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
|
import { user } from '$lib/stores/user.store';
|
|
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
|
import { getAssetJobName } from '$lib/utils';
|
|
import { clickOutside } from '$lib/utils/click-outside';
|
|
import { getContextMenuPosition } from '$lib/utils/context-menu';
|
|
import { AssetJobName, AssetTypeEnum, type AssetResponseDto } from '@immich/sdk';
|
|
import {
|
|
mdiAlertOutline,
|
|
mdiArrowLeft,
|
|
mdiContentCopy,
|
|
mdiDeleteOutline,
|
|
mdiDotsVertical,
|
|
mdiHeart,
|
|
mdiHeartOutline,
|
|
mdiInformationOutline,
|
|
mdiMagnifyMinusOutline,
|
|
mdiMagnifyPlusOutline,
|
|
mdiMotionPauseOutline,
|
|
mdiPlaySpeed,
|
|
mdiShareVariantOutline,
|
|
} from '@mdi/js';
|
|
import { createEventDispatcher } from 'svelte';
|
|
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
|
|
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
|
|
|
export let asset: AssetResponseDto;
|
|
export let showCopyButton: boolean;
|
|
export let showZoomButton: boolean;
|
|
export let showMotionPlayButton: boolean;
|
|
export let isMotionPhotoPlaying = false;
|
|
export let showDownloadButton: boolean;
|
|
export let showDetailButton: boolean;
|
|
export let showShareButton: boolean;
|
|
export let showSlideshow = false;
|
|
export let hasStackChildren = false;
|
|
|
|
$: isOwner = asset.ownerId === $user?.id;
|
|
|
|
type MenuItemEvent =
|
|
| 'addToAlbum'
|
|
| 'addToSharedAlbum'
|
|
| 'asProfileImage'
|
|
| 'download'
|
|
| 'playSlideShow'
|
|
| 'runJob'
|
|
| 'unstack';
|
|
|
|
const dispatch = createEventDispatcher<{
|
|
back: void;
|
|
stopMotionPhoto: void;
|
|
playMotionPhoto: void;
|
|
download: void;
|
|
showDetail: void;
|
|
favorite: void;
|
|
delete: void;
|
|
toggleArchive: void;
|
|
addToAlbum: void;
|
|
addToSharedAlbum: void;
|
|
asProfileImage: void;
|
|
runJob: AssetJobName;
|
|
playSlideShow: void;
|
|
unstack: void;
|
|
showShareModal: void;
|
|
}>();
|
|
|
|
let contextMenuPosition = { x: 0, y: 0 };
|
|
let isShowAssetOptions = false;
|
|
|
|
const showOptionsMenu = (event: MouseEvent) => {
|
|
contextMenuPosition = getContextMenuPosition(event, 'top-right');
|
|
isShowAssetOptions = !isShowAssetOptions;
|
|
};
|
|
|
|
const onJobClick = (name: AssetJobName) => {
|
|
isShowAssetOptions = false;
|
|
dispatch('runJob', name);
|
|
};
|
|
|
|
const onMenuClick = (eventName: MenuItemEvent) => {
|
|
isShowAssetOptions = false;
|
|
dispatch(eventName);
|
|
};
|
|
</script>
|
|
|
|
<div
|
|
class="z-[1001] flex h-16 place-items-center justify-between bg-gradient-to-b from-black/40 px-3 transition-transform duration-200"
|
|
>
|
|
<div class="text-white">
|
|
<CircleIconButton isOpacity={true} icon={mdiArrowLeft} on:click={() => dispatch('back')} />
|
|
</div>
|
|
<div class="flex w-[calc(100%-3rem)] justify-end gap-2 overflow-hidden text-white">
|
|
{#if showShareButton}
|
|
<CircleIconButton
|
|
isOpacity={true}
|
|
icon={mdiShareVariantOutline}
|
|
on:click={() => dispatch('showShareModal')}
|
|
title="Share"
|
|
/>
|
|
{/if}
|
|
{#if asset.isOffline}
|
|
<CircleIconButton
|
|
isOpacity={true}
|
|
icon={mdiAlertOutline}
|
|
on:click={() => dispatch('showDetail')}
|
|
title="Asset Offline"
|
|
/>
|
|
{/if}
|
|
{#if showMotionPlayButton}
|
|
{#if isMotionPhotoPlaying}
|
|
<CircleIconButton
|
|
isOpacity={true}
|
|
icon={mdiMotionPauseOutline}
|
|
title="Stop Motion Photo"
|
|
on:click={() => dispatch('stopMotionPhoto')}
|
|
/>
|
|
{:else}
|
|
<CircleIconButton
|
|
isOpacity={true}
|
|
icon={mdiPlaySpeed}
|
|
title="Play Motion Photo"
|
|
on:click={() => dispatch('playMotionPhoto')}
|
|
/>
|
|
{/if}
|
|
{/if}
|
|
{#if showZoomButton}
|
|
<CircleIconButton
|
|
isOpacity={true}
|
|
hideMobile={true}
|
|
icon={$photoZoomState && $photoZoomState.currentZoom > 1 ? mdiMagnifyMinusOutline : mdiMagnifyPlusOutline}
|
|
title="Zoom Image"
|
|
on:click={() => {
|
|
const zoomImage = new CustomEvent('zoomImage');
|
|
window.dispatchEvent(zoomImage);
|
|
}}
|
|
/>
|
|
{/if}
|
|
{#if showCopyButton}
|
|
<CircleIconButton
|
|
isOpacity={true}
|
|
icon={mdiContentCopy}
|
|
title="Copy Image"
|
|
on:click={() => {
|
|
const copyEvent = new CustomEvent('copyImage');
|
|
window.dispatchEvent(copyEvent);
|
|
}}
|
|
/>
|
|
{/if}
|
|
{#if showDetailButton}
|
|
<CircleIconButton
|
|
isOpacity={true}
|
|
icon={mdiInformationOutline}
|
|
on:click={() => dispatch('showDetail')}
|
|
title="Info"
|
|
/>
|
|
{/if}
|
|
{#if isOwner}
|
|
<CircleIconButton
|
|
isOpacity={true}
|
|
icon={asset.isFavorite ? mdiHeart : mdiHeartOutline}
|
|
on:click={() => dispatch('favorite')}
|
|
title={asset.isFavorite ? 'Unfavorite' : 'Favorite'}
|
|
/>
|
|
{/if}
|
|
|
|
{#if isOwner}
|
|
{#if !asset.isReadOnly || !asset.isExternal}
|
|
<CircleIconButton isOpacity={true} icon={mdiDeleteOutline} on:click={() => dispatch('delete')} title="Delete" />
|
|
{/if}
|
|
<div use:clickOutside on:outclick={() => (isShowAssetOptions = false)}>
|
|
<CircleIconButton isOpacity={true} icon={mdiDotsVertical} on:click={showOptionsMenu} title="More" />
|
|
{#if isShowAssetOptions}
|
|
<ContextMenu {...contextMenuPosition} direction="left">
|
|
{#if showSlideshow}
|
|
<MenuOption on:click={() => onMenuClick('playSlideShow')} text="Slideshow" />
|
|
{/if}
|
|
{#if showDownloadButton}
|
|
<MenuOption on:click={() => onMenuClick('download')} text="Download" />
|
|
{/if}
|
|
<MenuOption on:click={() => onMenuClick('addToAlbum')} text="Add to Album" />
|
|
<MenuOption on:click={() => onMenuClick('addToSharedAlbum')} text="Add to Shared Album" />
|
|
|
|
{#if isOwner}
|
|
<MenuOption
|
|
on:click={() => dispatch('toggleArchive')}
|
|
text={asset.isArchived ? 'Unarchive' : 'Archive'}
|
|
/>
|
|
{#if asset.type === AssetTypeEnum.Image}
|
|
<MenuOption on:click={() => onMenuClick('asProfileImage')} text="As profile picture" />
|
|
{/if}
|
|
|
|
{#if hasStackChildren}
|
|
<MenuOption on:click={() => onMenuClick('unstack')} text="Un-Stack" />
|
|
{/if}
|
|
|
|
<MenuOption
|
|
on:click={() => onJobClick(AssetJobName.RefreshMetadata)}
|
|
text={getAssetJobName(AssetJobName.RefreshMetadata)}
|
|
/>
|
|
<MenuOption
|
|
on:click={() => onJobClick(AssetJobName.RegenerateThumbnail)}
|
|
text={getAssetJobName(AssetJobName.RegenerateThumbnail)}
|
|
/>
|
|
{#if asset.type === AssetTypeEnum.Video}
|
|
<MenuOption
|
|
on:click={() => onJobClick(AssetJobName.TranscodeVideo)}
|
|
text={getAssetJobName(AssetJobName.TranscodeVideo)}
|
|
/>
|
|
{/if}
|
|
{/if}
|
|
</ContextMenu>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|