refactor(web): asset viewer actions (#11449)

* refactor(web): asset viewer actions

* motion photo slot and more refactoring
This commit is contained in:
Michel Heusschen 2024-07-31 18:25:38 +02:00 committed by GitHub
parent 3a3ea6135e
commit 281cfc95a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 682 additions and 476 deletions

View file

@ -1,135 +1,80 @@
<script lang="ts">
import type { OnAction } from '$lib/components/asset-viewer/actions/action';
import AddToAlbumAction from '$lib/components/asset-viewer/actions/add-to-album-action.svelte';
import ArchiveAction from '$lib/components/asset-viewer/actions/archive-action.svelte';
import CloseAction from '$lib/components/asset-viewer/actions/close-action.svelte';
import DeleteAction from '$lib/components/asset-viewer/actions/delete-action.svelte';
import DownloadAction from '$lib/components/asset-viewer/actions/download-action.svelte';
import FavoriteAction from '$lib/components/asset-viewer/actions/favorite-action.svelte';
import RestoreAction from '$lib/components/asset-viewer/actions/restore-action.svelte';
import SetAlbumCoverAction from '$lib/components/asset-viewer/actions/set-album-cover-action.svelte';
import SetProfilePictureAction from '$lib/components/asset-viewer/actions/set-profile-picture-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 UnstackAction from '$lib/components/asset-viewer/actions/unstack-action.svelte';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import DeleteButton from './delete-button.svelte';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { user } from '$lib/stores/user.store';
import { photoZoomState } from '$lib/stores/zoom-image.store';
import { getAssetJobName } from '$lib/utils';
import { getAssetJobName, getSharedLink } from '$lib/utils';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
import { AssetJobName, AssetTypeEnum, type AlbumResponseDto, type AssetResponseDto } from '@immich/sdk';
import {
mdiAccountCircleOutline,
mdiAlertOutline,
mdiArchiveArrowDownOutline,
mdiArchiveArrowUpOutline,
mdiArrowLeft,
mdiCogRefreshOutline,
mdiContentCopy,
mdiDatabaseRefreshOutline,
mdiDotsVertical,
mdiFolderDownloadOutline,
mdiHeart,
mdiHeartOutline,
mdiHistory,
mdiImageAlbum,
mdiImageMinusOutline,
mdiImageOutline,
mdiImageRefreshOutline,
mdiInformationOutline,
mdiMagnifyMinusOutline,
mdiMagnifyPlusOutline,
mdiMotionPauseOutline,
mdiPlaySpeed,
mdiPresentationPlay,
mdiShareVariantOutline,
mdiUpload,
} from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
import { canCopyImagesToClipboard } from 'copy-image-clipboard';
import { t } from 'svelte-i18n';
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
export let asset: AssetResponseDto;
export let album: AlbumResponseDto | null = null;
export let showCopyButton: boolean;
export let showZoomButton: boolean;
export let showMotionPlayButton: boolean;
export let isMotionPhotoPlaying = false;
export let showDownloadButton: boolean;
export let stackedAssets: AssetResponseDto[];
export let showDetailButton: boolean;
export let showShareButton: boolean;
export let showSlideshow = false;
export let hasStackChildren = false;
export let onZoomImage: () => void;
export let onCopyImage: () => void;
export let onAction: OnAction;
export let onRunJob: (name: AssetJobName) => void;
export let onPlaySlideshow: () => void;
export let onShowDetail: () => void;
export let onClose: () => void;
const sharedLink = getSharedLink();
$: isOwner = $user && asset.ownerId === $user?.id;
type EventTypes = {
back: void;
stopMotionPhoto: void;
playMotionPhoto: void;
download: void;
showDetail: void;
favorite: void;
delete: void;
permanentlyDelete: void;
toggleArchive: void;
addToAlbum: void;
restoreAsset: void;
addToSharedAlbum: void;
asProfileImage: void;
setAsAlbumCover: void;
runJob: AssetJobName;
playSlideShow: void;
unstack: void;
showShareModal: void;
};
const dispatch = createEventDispatcher<EventTypes>();
const onJobClick = (name: AssetJobName) => {
dispatch('runJob', name);
};
const onMenuClick = (eventName: keyof EventTypes) => {
dispatch(eventName);
};
$: showDownloadButton = sharedLink ? sharedLink.allowDownload : !asset.isOffline;
</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 color="opaque" icon={mdiArrowLeft} title={$t('go_back')} on:click={() => dispatch('back')} />
<CloseAction {onClose} />
</div>
<div
class="flex w-[calc(100%-3rem)] justify-end gap-2 overflow-hidden text-white"
data-testid="asset-viewer-navbar-actions"
>
{#if showShareButton}
<CircleIconButton
color="opaque"
icon={mdiShareVariantOutline}
on:click={() => dispatch('showShareModal')}
title={$t('share')}
/>
{#if !asset.isTrashed && $user}
<ShareAction {asset} />
{/if}
{#if asset.isOffline}
<CircleIconButton
color="opaque"
icon={mdiAlertOutline}
on:click={() => dispatch('showDetail')}
title={$t('asset_offline')}
/>
<CircleIconButton color="opaque" icon={mdiAlertOutline} on:click={onShowDetail} title={$t('asset_offline')} />
{/if}
{#if showMotionPlayButton}
{#if isMotionPhotoPlaying}
<CircleIconButton
color="opaque"
icon={mdiMotionPauseOutline}
title={$t('stop_motion_photo')}
on:click={() => dispatch('stopMotionPhoto')}
/>
{:else}
<CircleIconButton
color="opaque"
icon={mdiPlaySpeed}
title={$t('play_motion_photo')}
on:click={() => dispatch('playMotionPhoto')}
/>
{/if}
{#if asset.livePhotoVideoId}
<slot name="motion-photo" />
{/if}
{#if showZoomButton}
{#if asset.type === AssetTypeEnum.Image}
<CircleIconButton
color="opaque"
hideMobile={true}
@ -138,84 +83,50 @@
on:click={onZoomImage}
/>
{/if}
{#if showCopyButton}
{#if canCopyImagesToClipboard() && asset.type === AssetTypeEnum.Image}
<CircleIconButton color="opaque" icon={mdiContentCopy} title={$t('copy_image')} on:click={onCopyImage} />
{/if}
{#if !isOwner && showDownloadButton}
<CircleIconButton
color="opaque"
icon={mdiFolderDownloadOutline}
on:click={() => dispatch('download')}
title={$t('download')}
/>
<DownloadAction {asset} />
{/if}
{#if showDetailButton}
<CircleIconButton
color="opaque"
icon={mdiInformationOutline}
on:click={() => dispatch('showDetail')}
title={$t('info')}
/>
<ShowDetailAction {onShowDetail} />
{/if}
{#if isOwner}
<CircleIconButton
color="opaque"
icon={asset.isFavorite ? mdiHeart : mdiHeartOutline}
on:click={() => dispatch('favorite')}
title={asset.isFavorite ? $t('unfavorite') : $t('to_favorite')}
/>
<FavoriteAction {asset} {onAction} />
{/if}
{#if isOwner}
<DeleteButton
{asset}
on:delete={() => dispatch('delete')}
on:permanentlyDelete={() => dispatch('permanentlyDelete')}
/>
<DeleteAction {asset} {onAction} />
<ButtonContextMenu direction="left" align="top-right" color="opaque" title={$t('more')} icon={mdiDotsVertical}>
{#if showSlideshow}
<MenuOption icon={mdiPresentationPlay} onClick={() => onMenuClick('playSlideShow')} text={$t('slideshow')} />
<MenuOption icon={mdiPresentationPlay} text={$t('slideshow')} onClick={onPlaySlideshow} />
{/if}
{#if showDownloadButton}
<MenuOption icon={mdiFolderDownloadOutline} onClick={() => onMenuClick('download')} text={$t('download')} />
<DownloadAction {asset} menuItem />
{/if}
{#if asset.isTrashed}
<MenuOption icon={mdiHistory} onClick={() => onMenuClick('restoreAsset')} text={$t('restore')} />
<RestoreAction {asset} {onAction} />
{:else}
<MenuOption icon={mdiImageAlbum} onClick={() => onMenuClick('addToAlbum')} text={$t('add_to_album')} />
<MenuOption
icon={mdiShareVariantOutline}
onClick={() => onMenuClick('addToSharedAlbum')}
text={$t('add_to_shared_album')}
/>
<AddToAlbumAction {asset} {onAction} />
<AddToAlbumAction {asset} {onAction} shared />
{/if}
{#if isOwner}
{#if hasStackChildren}
<MenuOption icon={mdiImageMinusOutline} onClick={() => onMenuClick('unstack')} text={$t('unstack')} />
<UnstackAction {stackedAssets} {onAction} />
{/if}
{#if album}
<MenuOption
text={$t('set_as_album_cover')}
icon={mdiImageOutline}
onClick={() => onMenuClick('setAsAlbumCover')}
/>
<SetAlbumCoverAction {asset} {album} />
{/if}
{#if asset.type === AssetTypeEnum.Image}
<MenuOption
icon={mdiAccountCircleOutline}
onClick={() => onMenuClick('asProfileImage')}
text={$t('set_as_profile_picture')}
/>
<SetProfilePictureAction {asset} />
{/if}
<MenuOption
onClick={() => onMenuClick('toggleArchive')}
icon={asset.isArchived ? mdiArchiveArrowUpOutline : mdiArchiveArrowDownOutline}
text={asset.isArchived ? $t('unarchive') : $t('to_archive')}
/>
<ArchiveAction {asset} {onAction} />
<MenuOption
icon={mdiUpload}
onClick={() => openFileUploadDialog({ multiple: false, assetId: asset.id })}
@ -224,18 +135,18 @@
<hr />
<MenuOption
icon={mdiDatabaseRefreshOutline}
onClick={() => onJobClick(AssetJobName.RefreshMetadata)}
onClick={() => onRunJob(AssetJobName.RefreshMetadata)}
text={$getAssetJobName(AssetJobName.RefreshMetadata)}
/>
<MenuOption
icon={mdiImageRefreshOutline}
onClick={() => onJobClick(AssetJobName.RegenerateThumbnail)}
onClick={() => onRunJob(AssetJobName.RegenerateThumbnail)}
text={$getAssetJobName(AssetJobName.RegenerateThumbnail)}
/>
{#if asset.type === AssetTypeEnum.Video}
<MenuOption
icon={mdiCogRefreshOutline}
onClick={() => onJobClick(AssetJobName.TranscodeVideo)}
onClick={() => onRunJob(AssetJobName.TranscodeVideo)}
text={$getAssetJobName(AssetJobName.TranscodeVideo)}
/>
{/if}