mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 10:49:11 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			197 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Svelte
		
	
	
	
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			Svelte
		
	
	
	
	
	
| <script lang="ts">
 | |
|   import { page } from '$app/stores';
 | |
|   import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | |
|   import { photoZoomState } from '$lib/stores/zoom-image.store';
 | |
|   import { clickOutside } from '$lib/utils/click-outside';
 | |
|   import { getContextMenuPosition } from '$lib/utils/context-menu';
 | |
|   import { AssetJobName, AssetResponseDto, AssetTypeEnum, api } from '@api';
 | |
|   import {
 | |
|     mdiAlertOutline,
 | |
|     mdiArrowLeft,
 | |
|     mdiCloudDownloadOutline,
 | |
|     mdiContentCopy,
 | |
|     mdiDeleteOutline,
 | |
|     mdiDotsVertical,
 | |
|     mdiHeart,
 | |
|     mdiHeartOutline,
 | |
|     mdiInformationOutline,
 | |
|     mdiMagnifyMinusOutline,
 | |
|     mdiMagnifyPlusOutline,
 | |
|     mdiMotionPauseOutline,
 | |
|     mdiMoviePlayOutline,
 | |
|   } 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 showSlideshow = false;
 | |
| 
 | |
|   $: isOwner = asset.ownerId === $page.data.user?.id;
 | |
| 
 | |
|   type MenuItemEvent = 'addToAlbum' | 'addToSharedAlbum' | 'asProfileImage' | 'runJob' | 'playSlideShow';
 | |
| 
 | |
|   const dispatch = createEventDispatcher<{
 | |
|     goBack: 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;
 | |
|   }>();
 | |
| 
 | |
|   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('goBack')} />
 | |
|   </div>
 | |
|   <div class="flex w-[calc(100%-3rem)] justify-end gap-2 overflow-hidden text-white">
 | |
|     {#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={mdiMoviePlayOutline}
 | |
|           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 showDownloadButton}
 | |
|       <CircleIconButton
 | |
|         isOpacity={true}
 | |
|         icon={mdiCloudDownloadOutline}
 | |
|         on:click={() => dispatch('download')}
 | |
|         title="Download"
 | |
|       />
 | |
|     {/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="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}
 | |
|             <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'}
 | |
|               />
 | |
|               <MenuOption on:click={() => onMenuClick('asProfileImage')} text="As profile picture" />
 | |
|               <MenuOption
 | |
|                 on:click={() => onJobClick(AssetJobName.RefreshMetadata)}
 | |
|                 text={api.getAssetJobName(AssetJobName.RefreshMetadata)}
 | |
|               />
 | |
|               <MenuOption
 | |
|                 on:click={() => onJobClick(AssetJobName.RegenerateThumbnail)}
 | |
|                 text={api.getAssetJobName(AssetJobName.RegenerateThumbnail)}
 | |
|               />
 | |
|               {#if asset.type === AssetTypeEnum.Video}
 | |
|                 <MenuOption
 | |
|                   on:click={() => onJobClick(AssetJobName.TranscodeVideo)}
 | |
|                   text={api.getAssetJobName(AssetJobName.TranscodeVideo)}
 | |
|                 />
 | |
|               {/if}
 | |
|             {/if}
 | |
|           </ContextMenu>
 | |
|         {/if}
 | |
|       </div>
 | |
|     {/if}
 | |
|   </div>
 | |
| </div>
 |