1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-22 01:47:08 +02:00

feat(web): rework context menus: add icons and reorder items (#8090)

This commit is contained in:
Ethan Margaillan 2024-03-21 19:39:33 +01:00 committed by GitHub
parent 1abb0bdae8
commit 8ed6ed4d2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 204 additions and 84 deletions

View File

@ -5,20 +5,31 @@
import { getAssetJobName } from '$lib/utils'; import { getAssetJobName } from '$lib/utils';
import { clickOutside } from '$lib/utils/click-outside'; import { clickOutside } from '$lib/utils/click-outside';
import { getContextMenuPosition } from '$lib/utils/context-menu'; import { getContextMenuPosition } from '$lib/utils/context-menu';
import { AssetJobName, AssetTypeEnum, type AssetResponseDto } from '@immich/sdk'; import { AssetJobName, AssetTypeEnum, type AssetResponseDto, type AlbumResponseDto } from '@immich/sdk';
import { import {
mdiAccountCircleOutline,
mdiAlertOutline, mdiAlertOutline,
mdiArchiveArrowDownOutline,
mdiArchiveArrowUpOutline,
mdiArrowLeft, mdiArrowLeft,
mdiCogRefreshOutline,
mdiContentCopy, mdiContentCopy,
mdiDatabaseRefreshOutline,
mdiDeleteOutline, mdiDeleteOutline,
mdiDotsVertical, mdiDotsVertical,
mdiFolderDownloadOutline,
mdiHeart, mdiHeart,
mdiHeartOutline, mdiHeartOutline,
mdiImageAlbum,
mdiImageMinusOutline,
mdiImageOutline,
mdiImageRefreshOutline,
mdiInformationOutline, mdiInformationOutline,
mdiMagnifyMinusOutline, mdiMagnifyMinusOutline,
mdiMagnifyPlusOutline, mdiMagnifyPlusOutline,
mdiMotionPauseOutline, mdiMotionPauseOutline,
mdiPlaySpeed, mdiPlaySpeed,
mdiPresentationPlay,
mdiShareVariantOutline, mdiShareVariantOutline,
} from '@mdi/js'; } from '@mdi/js';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
@ -26,6 +37,7 @@
import MenuOption from '../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../shared-components/context-menu/menu-option.svelte';
export let asset: AssetResponseDto; export let asset: AssetResponseDto;
export let album: AlbumResponseDto | null = null;
export let showCopyButton: boolean; export let showCopyButton: boolean;
export let showZoomButton: boolean; export let showZoomButton: boolean;
export let showMotionPlayButton: boolean; export let showMotionPlayButton: boolean;
@ -42,6 +54,7 @@
| 'addToAlbum' | 'addToAlbum'
| 'addToSharedAlbum' | 'addToSharedAlbum'
| 'asProfileImage' | 'asProfileImage'
| 'setAsAlbumCover'
| 'download' | 'download'
| 'playSlideShow' | 'playSlideShow'
| 'runJob' | 'runJob'
@ -59,6 +72,7 @@
addToAlbum: void; addToAlbum: void;
addToSharedAlbum: void; addToSharedAlbum: void;
asProfileImage: void; asProfileImage: void;
setAsAlbumCover: void;
runJob: AssetJobName; runJob: AssetJobName;
playSlideShow: void; playSlideShow: void;
unstack: void; unstack: void;
@ -173,37 +187,55 @@
{#if isShowAssetOptions} {#if isShowAssetOptions}
<ContextMenu {...contextMenuPosition} direction="left"> <ContextMenu {...contextMenuPosition} direction="left">
{#if showSlideshow} {#if showSlideshow}
<MenuOption on:click={() => onMenuClick('playSlideShow')} text="Slideshow" /> <MenuOption icon={mdiPresentationPlay} on:click={() => onMenuClick('playSlideShow')} text="Slideshow" />
{/if} {/if}
{#if showDownloadButton} {#if showDownloadButton}
<MenuOption on:click={() => onMenuClick('download')} text="Download" /> <MenuOption icon={mdiFolderDownloadOutline} on:click={() => onMenuClick('download')} text="Download" />
{/if} {/if}
<MenuOption on:click={() => onMenuClick('addToAlbum')} text="Add to Album" /> <MenuOption icon={mdiImageAlbum} on:click={() => onMenuClick('addToAlbum')} text="Add to album" />
<MenuOption on:click={() => onMenuClick('addToSharedAlbum')} text="Add to Shared Album" /> <MenuOption
icon={mdiShareVariantOutline}
on:click={() => onMenuClick('addToSharedAlbum')}
text="Add to shared album"
/>
{#if isOwner} {#if isOwner}
{#if hasStackChildren}
<MenuOption icon={mdiImageMinusOutline} on:click={() => onMenuClick('unstack')} text="Un-stack" />
{/if}
{#if album}
<MenuOption
text="Set as album cover"
icon={mdiImageOutline}
on:click={() => onMenuClick('setAsAlbumCover')}
/>
{/if}
{#if asset.type === AssetTypeEnum.Image}
<MenuOption
icon={mdiAccountCircleOutline}
on:click={() => onMenuClick('asProfileImage')}
text="Set as profile picture"
/>
{/if}
<MenuOption <MenuOption
on:click={() => dispatch('toggleArchive')} on:click={() => dispatch('toggleArchive')}
icon={asset.isArchived ? mdiArchiveArrowUpOutline : mdiArchiveArrowDownOutline}
text={asset.isArchived ? 'Unarchive' : 'Archive'} text={asset.isArchived ? 'Unarchive' : 'Archive'}
/> />
{#if asset.type === AssetTypeEnum.Image} <hr />
<MenuOption on:click={() => onMenuClick('asProfileImage')} text="As profile picture" />
{/if}
{#if hasStackChildren}
<MenuOption on:click={() => onMenuClick('unstack')} text="Un-Stack" />
{/if}
<MenuOption <MenuOption
icon={mdiDatabaseRefreshOutline}
on:click={() => onJobClick(AssetJobName.RefreshMetadata)} on:click={() => onJobClick(AssetJobName.RefreshMetadata)}
text={getAssetJobName(AssetJobName.RefreshMetadata)} text={getAssetJobName(AssetJobName.RefreshMetadata)}
/> />
<MenuOption <MenuOption
icon={mdiImageRefreshOutline}
on:click={() => onJobClick(AssetJobName.RegenerateThumbnail)} on:click={() => onJobClick(AssetJobName.RegenerateThumbnail)}
text={getAssetJobName(AssetJobName.RegenerateThumbnail)} text={getAssetJobName(AssetJobName.RegenerateThumbnail)}
/> />
{#if asset.type === AssetTypeEnum.Video} {#if asset.type === AssetTypeEnum.Video}
<MenuOption <MenuOption
icon={mdiCogRefreshOutline}
on:click={() => onJobClick(AssetJobName.TranscodeVideo)} on:click={() => onJobClick(AssetJobName.TranscodeVideo)}
text={getAssetJobName(AssetJobName.TranscodeVideo)} text={getAssetJobName(AssetJobName.TranscodeVideo)}
/> />

View File

@ -4,7 +4,13 @@
import { getPeopleThumbnailUrl } from '$lib/utils'; import { getPeopleThumbnailUrl } from '$lib/utils';
import { getContextMenuPosition } from '$lib/utils/context-menu'; import { getContextMenuPosition } from '$lib/utils/context-menu';
import { type PersonResponseDto } from '@immich/sdk'; import { type PersonResponseDto } from '@immich/sdk';
import { mdiDotsVertical } from '@mdi/js'; import {
mdiAccountEditOutline,
mdiAccountMultipleCheckOutline,
mdiCalendarEditOutline,
mdiDotsVertical,
mdiEyeOffOutline,
} from '@mdi/js';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import IconButton from '../elements/buttons/icon-button.svelte'; import IconButton from '../elements/buttons/icon-button.svelte';
@ -83,10 +89,18 @@
{#if showContextMenu} {#if showContextMenu}
<Portal target="body"> <Portal target="body">
<ContextMenu {...contextMenuPosition} on:outclick={() => onMenuExit()}> <ContextMenu {...contextMenuPosition} on:outclick={() => onMenuExit()}>
<MenuOption on:click={() => onMenuClick('hide-person')} text="Hide Person" /> <MenuOption on:click={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text="Hide person" />
<MenuOption on:click={() => onMenuClick('change-name')} text="Change name" /> <MenuOption on:click={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text="Change name" />
<MenuOption on:click={() => onMenuClick('set-birth-date')} text="Set date of birth" /> <MenuOption
<MenuOption on:click={() => onMenuClick('merge-people')} text="Merge People" /> on:click={() => onMenuClick('set-birth-date')}
icon={mdiCalendarEditOutline}
text="Set date of birth"
/>
<MenuOption
on:click={() => onMenuClick('merge-people')}
icon={mdiAccountMultipleCheckOutline}
text="Merge people"
/>
</ContextMenu> </ContextMenu>
</Portal> </Portal>
{/if} {/if}

View File

@ -11,6 +11,7 @@
import { createAlbum, type AlbumResponseDto } from '@immich/sdk'; import { createAlbum, type AlbumResponseDto } from '@immich/sdk';
import { getMenuContext } from '../asset-select-context-menu.svelte'; import { getMenuContext } from '../asset-select-context-menu.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiImageAlbum, mdiShareVariantOutline } from '@mdi/js';
export let shared = false; export let shared = false;
let showAlbumPicker = false; let showAlbumPicker = false;
@ -53,7 +54,11 @@
}; };
</script> </script>
<MenuOption on:click={() => (showAlbumPicker = true)} text={shared ? 'Add to Shared Album' : 'Add to Album'} /> <MenuOption
on:click={() => (showAlbumPicker = true)}
text={shared ? 'Add to shared album' : 'Add to album'}
icon={shared ? mdiShareVariantOutline : mdiImageAlbum}
/>
{#if showAlbumPicker} {#if showAlbumPicker}
<AlbumSelectionModal <AlbumSelectionModal

View File

@ -56,7 +56,7 @@
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption {text} on:click={handleArchive} /> <MenuOption {text} {icon} on:click={handleArchive} />
{/if} {/if}
{#if !menuItem} {#if !menuItem}

View File

@ -4,7 +4,7 @@
NotificationType, NotificationType,
notificationController, notificationController,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { getAssetJobMessage, getAssetJobName } from '$lib/utils'; import { getAssetJobIcon, getAssetJobMessage, getAssetJobName } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { AssetJobName, AssetTypeEnum, runAssetJobs } from '@immich/sdk'; import { AssetJobName, AssetTypeEnum, runAssetJobs } from '@immich/sdk';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
@ -33,6 +33,6 @@
{#each jobs as job} {#each jobs as job}
{#if isAllVideos || job !== AssetJobName.TranscodeVideo} {#if isAllVideos || job !== AssetJobName.TranscodeVideo}
<MenuOption text={getAssetJobName(job)} on:click={() => handleRunJob(job)} /> <MenuOption text={getAssetJobName(job)} icon={getAssetJobIcon(job)} on:click={() => handleRunJob(job)} />
{/if} {/if}
{/each} {/each}

View File

@ -7,6 +7,7 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiCalendarEditOutline } from '@mdi/js';
export let menuItem = false; export let menuItem = false;
const { clearSelect, getOwnedAssets } = getAssetControlContext(); const { clearSelect, getOwnedAssets } = getAssetControlContext();
@ -26,7 +27,7 @@
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption text="Change date" on:click={() => (isShowChangeDate = true)} /> <MenuOption text="Change date" icon={mdiCalendarEditOutline} on:click={() => (isShowChangeDate = true)} />
{/if} {/if}
{#if isShowChangeDate} {#if isShowChangeDate}
<ChangeDate <ChangeDate

View File

@ -6,6 +6,7 @@
import { updateAssets } from '@immich/sdk'; import { updateAssets } from '@immich/sdk';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiMapMarkerMultipleOutline } from '@mdi/js';
export let menuItem = false; export let menuItem = false;
const { clearSelect, getOwnedAssets } = getAssetControlContext(); const { clearSelect, getOwnedAssets } = getAssetControlContext();
@ -26,7 +27,11 @@
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption text="Change location" on:click={() => (isShowChangeLocation = true)} /> <MenuOption
text="Change location"
icon={mdiMapMarkerMultipleOutline}
on:click={() => (isShowChangeLocation = true)}
/>
{/if} {/if}
{#if isShowChangeLocation} {#if isShowChangeLocation}
<ChangeLocation <ChangeLocation

View File

@ -21,6 +21,8 @@
let isShowConfirmation = false; let isShowConfirmation = false;
let loading = false; let loading = false;
$: label = force ? 'Permanently delete' : 'Delete';
const handleTrash = async () => { const handleTrash = async () => {
if (force) { if (force) {
isShowConfirmation = true; isShowConfirmation = true;
@ -46,11 +48,11 @@
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption text={force ? 'Permanently Delete' : 'Delete'} on:click={handleTrash} /> <MenuOption text={label} icon={mdiDeleteOutline} on:click={handleTrash} />
{:else if loading} {:else if loading}
<CircleIconButton title="Loading" icon={mdiTimerSand} /> <CircleIconButton title="Loading" icon={mdiTimerSand} />
{:else} {:else}
<CircleIconButton title="Delete" icon={mdiDeleteOutline} on:click={handleTrash} /> <CircleIconButton title={label} icon={mdiDeleteOutline} on:click={handleTrash} />
{/if} {/if}
{#if isShowConfirmation} {#if isShowConfirmation}

View File

@ -3,7 +3,7 @@
import { downloadArchive, downloadFile } from '$lib/utils/asset-utils'; import { downloadArchive, downloadFile } from '$lib/utils/asset-utils';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiCloudDownloadOutline } from '@mdi/js'; import { mdiCloudDownloadOutline, mdiFileDownloadOutline, mdiFolderDownloadOutline } from '@mdi/js';
export let filename = 'immich.zip'; export let filename = 'immich.zip';
export let menuItem = false; export let menuItem = false;
@ -21,10 +21,12 @@
clearSelect(); clearSelect();
await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) }); await downloadArchive(filename, { assetIds: assets.map((asset) => asset.id) });
}; };
$: menuItemIcon = getAssets().size === 1 ? mdiFileDownloadOutline : mdiFolderDownloadOutline;
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption text="Download" on:click={handleDownloadFiles} /> <MenuOption text="Download" icon={menuItemIcon} on:click={handleDownloadFiles} />
{:else} {:else}
<CircleIconButton title="Download" icon={mdiCloudDownloadOutline} on:click={handleDownloadFiles} /> <CircleIconButton title="Download" icon={mdiCloudDownloadOutline} on:click={handleDownloadFiles} />
{/if} {/if}

View File

@ -16,7 +16,7 @@
export let menuItem = false; export let menuItem = false;
export let removeFavorite: boolean; export let removeFavorite: boolean;
$: text = removeFavorite ? 'Remove from Favorites' : 'Favorite'; $: text = removeFavorite ? 'Remove from favorites' : 'Favorite';
$: icon = removeFavorite ? mdiHeartMinusOutline : mdiHeartOutline; $: icon = removeFavorite ? mdiHeartMinusOutline : mdiHeartOutline;
let loading = false; let loading = false;
@ -57,7 +57,7 @@
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption {text} on:click={handleFavorite} /> <MenuOption {text} {icon} on:click={handleFavorite} />
{/if} {/if}
{#if !menuItem} {#if !menuItem}

View File

@ -6,7 +6,7 @@
notificationController, notificationController,
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import { getAlbumInfo, removeAssetFromAlbum, type AlbumResponseDto } from '@immich/sdk'; import { getAlbumInfo, removeAssetFromAlbum, type AlbumResponseDto } from '@immich/sdk';
import { mdiDeleteOutline } from '@mdi/js'; import { mdiDeleteOutline, mdiImageRemoveOutline } from '@mdi/js';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
@ -50,7 +50,7 @@
</script> </script>
{#if menuItem} {#if menuItem}
<MenuOption text="Remove from album" on:click={() => (isShowConfirmation = true)} /> <MenuOption text="Remove from album" icon={mdiImageRemoveOutline} on:click={() => (isShowConfirmation = true)} />
{:else} {:else}
<CircleIconButton title="Remove from album" icon={mdiDeleteOutline} on:click={() => (isShowConfirmation = true)} /> <CircleIconButton title="Remove from album" icon={mdiDeleteOutline} on:click={() => (isShowConfirmation = true)} />
{/if} {/if}

View File

@ -8,6 +8,7 @@
import { handleError } from '$lib/utils/handle-error'; import { handleError } from '$lib/utils/handle-error';
import { updateAssets } from '@immich/sdk'; import { updateAssets } from '@immich/sdk';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiImageMultipleOutline } from '@mdi/js';
export let onStack: OnStack | undefined; export let onStack: OnStack | undefined;
@ -55,4 +56,4 @@
}; };
</script> </script>
<MenuOption text="Stack" on:click={handleStack} /> <MenuOption text="Stack" icon={mdiImageMultipleOutline} on:click={handleStack} />

View File

@ -29,9 +29,7 @@
<CircleIconButton {title} {icon} on:click={handleShowMenu} /> <CircleIconButton {title} {icon} on:click={handleShowMenu} />
{#if showContextMenu} {#if showContextMenu}
<ContextMenu {...contextMenuPosition}> <ContextMenu {...contextMenuPosition}>
<div class="flex flex-col rounded-lg"> <slot />
<slot />
</div>
</ContextMenu> </ContextMenu>
{/if} {/if}
</div> </div>

View File

@ -23,12 +23,14 @@
<div <div
transition:slide={{ duration: 200, easing: quintOut }} transition:slide={{ duration: 200, easing: quintOut }}
bind:this={menuElement} bind:this={menuElement}
class="absolute z-10 w-[200px] overflow-hidden rounded-lg shadow-lg" class="absolute z-10 min-w-[200px] w-max max-w-[300px] overflow-hidden rounded-lg shadow-lg"
style="left: {left}px; top: {top}px;" style="left: {left}px; top: {top}px;"
role="menu" role="menu"
use:clickOutside use:clickOutside
on:outclick on:outclick
on:escape on:escape
> >
<slot /> <div class="flex flex-col rounded-lg">
<slot />
</div>
</div> </div>

View File

@ -1,6 +1,9 @@
<script> <script>
import Icon from '$lib/components/elements/icon.svelte';
export let text = ''; export let text = '';
export let subtitle = ''; export let subtitle = '';
export let icon = '';
</script> </script>
<button <button
@ -9,7 +12,14 @@
role="menuitem" role="menuitem"
> >
{#if text} {#if text}
{text} {#if icon}
<p class="flex gap-2">
<Icon path={icon} size="18" />
{text}
</p>
{:else}
{text}
{/if}
{:else} {:else}
<slot /> <slot />
{/if} {/if}

View File

@ -12,6 +12,7 @@ import {
unlinkOAuthAccount, unlinkOAuthAccount,
type UserResponseDto, type UserResponseDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { mdiCogRefreshOutline, mdiDatabaseRefreshOutline, mdiImageRefreshOutline } from '@mdi/js';
interface DownloadRequestOptions<T = unknown> { interface DownloadRequestOptions<T = unknown> {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
@ -196,6 +197,16 @@ export const getAssetJobMessage = (job: AssetJobName) => {
return messages[job]; return messages[job];
}; };
export const getAssetJobIcon = (job: AssetJobName) => {
const names: Record<AssetJobName, string> = {
[AssetJobName.RefreshMetadata]: mdiDatabaseRefreshOutline,
[AssetJobName.RegenerateThumbnail]: mdiImageRefreshOutline,
[AssetJobName.TranscodeVideo]: mdiCogRefreshOutline,
};
return names[job];
};
export const copyToClipboard = async (secret: string) => { export const copyToClipboard = async (secret: string) => {
try { try {
await navigator.clipboard.writeText(secret); await navigator.clipboard.writeText(secret);

View File

@ -64,11 +64,14 @@
mdiArrowLeft, mdiArrowLeft,
mdiDeleteOutline, mdiDeleteOutline,
mdiDotsVertical, mdiDotsVertical,
mdiFileImagePlusOutline,
mdiFolderDownloadOutline, mdiFolderDownloadOutline,
mdiLink, mdiLink,
mdiPlus, mdiPlus,
mdiShareVariantOutline, mdiShareVariantOutline,
mdiPresentationPlay,
mdiCogOutline,
mdiImageOutline,
mdiImagePlusOutline,
} from '@mdi/js'; } from '@mdi/js';
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import type { PageData } from './$types'; import type { PageData } from './$types';
@ -385,23 +388,25 @@
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}> <AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
<CreateSharedLink /> <CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} /> <SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={mdiPlus} title="Add"> <AssetSelectContextMenu icon={mdiPlus} title="Add to...">
<AddToAlbum /> <AddToAlbum />
<AddToAlbum shared /> <AddToAlbum shared />
</AssetSelectContextMenu> </AssetSelectContextMenu>
{#if isAllUserOwned}
<FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
{/if}
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
<DownloadAction menuItem filename="{album.albumName}.zip" />
{#if isAllUserOwned} {#if isAllUserOwned}
<FavoriteAction menuItem removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} /> <ChangeDate menuItem />
<ChangeLocation menuItem />
<ArchiveAction menuItem unarchive={isAllArchived} onArchive={() => assetStore.triggerUpdate()} /> <ArchiveAction menuItem unarchive={isAllArchived} onArchive={() => assetStore.triggerUpdate()} />
{/if} {/if}
<DownloadAction menuItem filename="{album.albumName}.zip" />
{#if isOwned || isAllUserOwned} {#if isOwned || isAllUserOwned}
<RemoveFromAlbum menuItem bind:album onRemove={handleRemoveAssets} /> <RemoveFromAlbum menuItem bind:album onRemove={handleRemoveAssets} />
{/if} {/if}
{#if isAllUserOwned} {#if isAllUserOwned}
<DeleteAssets menuItem onAssetDelete={handleRemoveAssets} /> <DeleteAssets menuItem onAssetDelete={handleRemoveAssets} />
<ChangeDate menuItem />
<ChangeLocation menuItem />
{/if} {/if}
</AssetSelectContextMenu> </AssetSelectContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
@ -410,9 +415,9 @@
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(backUrl)}> <ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(backUrl)}>
<svelte:fragment slot="trailing"> <svelte:fragment slot="trailing">
<CircleIconButton <CircleIconButton
title="Add Photos" title="Add photos"
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)} on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
icon={mdiFileImagePlusOutline} icon={mdiImagePlusOutline}
/> />
{#if isOwned} {#if isOwned}
@ -421,11 +426,6 @@
on:click={() => (viewMode = ViewMode.SELECT_USERS)} on:click={() => (viewMode = ViewMode.SELECT_USERS)}
icon={mdiShareVariantOutline} icon={mdiShareVariantOutline}
/> />
<CircleIconButton
title="Delete album"
on:click={() => (viewMode = ViewMode.CONFIRM_DELETE)}
icon={mdiDeleteOutline}
/>
{/if} {/if}
{#if album.assetCount > 0} {#if album.assetCount > 0}
@ -436,9 +436,22 @@
<CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical}> <CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical}>
{#if viewMode === ViewMode.ALBUM_OPTIONS} {#if viewMode === ViewMode.ALBUM_OPTIONS}
<ContextMenu {...contextMenuPosition}> <ContextMenu {...contextMenuPosition}>
<MenuOption on:click={handleStartSlideshow} text="Slideshow" /> <MenuOption icon={mdiPresentationPlay} text="Slideshow" on:click={handleStartSlideshow} />
<MenuOption on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)} text="Set album cover" /> <MenuOption
<MenuOption on:click={() => (viewMode = ViewMode.OPTIONS)} text="Options" /> icon={mdiImageOutline}
text="Select album cover"
on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)}
/>
<MenuOption
icon={mdiCogOutline}
text="Options"
on:click={() => (viewMode = ViewMode.OPTIONS)}
/>
<MenuOption
icon={mdiDeleteOutline}
text="Delete album"
on:click={() => (viewMode = ViewMode.CONFIRM_DELETE)}
/>
</ContextMenu> </ContextMenu>
{/if} {/if}
</CircleIconButton> </CircleIconButton>

View File

@ -31,14 +31,14 @@
<ArchiveAction unarchive onArchive={(assetIds) => assetStore.removeAssets(assetIds)} /> <ArchiveAction unarchive onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
<CreateSharedLink /> <CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} /> <SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={mdiPlus} title="Add"> <AssetSelectContextMenu icon={mdiPlus} title="Add to...">
<AddToAlbum /> <AddToAlbum />
<AddToAlbum shared /> <AddToAlbum shared />
</AssetSelectContextMenu> </AssetSelectContextMenu>
<DeleteAssets onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} /> <FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
<AssetSelectContextMenu icon={mdiDotsVertical} title="Add"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Add">
<DownloadAction menuItem /> <DownloadAction menuItem />
<FavoriteAction menuItem removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} /> <DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
</AssetSelectContextMenu> </AssetSelectContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
{/if} {/if}

View File

@ -34,16 +34,16 @@
<FavoriteAction removeFavorite onFavorite={(assetIds) => assetStore.removeAssets(assetIds)} /> <FavoriteAction removeFavorite onFavorite={(assetIds) => assetStore.removeAssets(assetIds)} />
<CreateSharedLink /> <CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} /> <SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={mdiPlus} title="Add"> <AssetSelectContextMenu icon={mdiPlus} title="Add to...">
<AddToAlbum /> <AddToAlbum />
<AddToAlbum shared /> <AddToAlbum shared />
</AssetSelectContextMenu> </AssetSelectContextMenu>
<DeleteAssets onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
<DownloadAction menuItem /> <DownloadAction menuItem />
<ArchiveAction menuItem unarchive={isAllArchive} onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
<ArchiveAction menuItem unarchive={isAllArchive} onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
</AssetSelectContextMenu> </AssetSelectContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
{/if} {/if}

View File

@ -44,7 +44,16 @@
type AssetResponseDto, type AssetResponseDto,
type PersonResponseDto, type PersonResponseDto,
} from '@immich/sdk'; } from '@immich/sdk';
import { mdiArrowLeft, mdiDotsVertical, mdiPlus } from '@mdi/js'; import {
mdiAccountBoxOutline,
mdiAccountMultipleCheckOutline,
mdiArrowLeft,
mdiCalendarEditOutline,
mdiDotsVertical,
mdiEyeOffOutline,
mdiEyeOutline,
mdiPlus,
} from '@mdi/js';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import { listNavigation } from '$lib/utils/list-navigation'; import { listNavigation } from '$lib/utils/list-navigation';
@ -395,18 +404,18 @@
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}> <AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
<CreateSharedLink /> <CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} /> <SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={mdiPlus} title="Add"> <AssetSelectContextMenu icon={mdiPlus} title="Add to...">
<AddToAlbum /> <AddToAlbum />
<AddToAlbum shared /> <AddToAlbum shared />
</AssetSelectContextMenu> </AssetSelectContextMenu>
<DeleteAssets onAssetDelete={(assetIds) => $assetStore.removeAssets(assetIds)} /> <FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
<AssetSelectContextMenu icon={mdiDotsVertical} title="Add"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Add">
<DownloadAction menuItem filename="{data.person.name || 'immich'}.zip" /> <DownloadAction menuItem filename="{data.person.name || 'immich'}.zip" />
<FavoriteAction menuItem removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} /> <MenuOption icon={mdiAccountMultipleCheckOutline} text="Fix incorrect match" on:click={handleReassignAssets} />
<ArchiveAction menuItem unarchive={isAllArchive} onArchive={(assetIds) => $assetStore.removeAssets(assetIds)} />
<MenuOption text="Fix incorrect match" on:click={handleReassignAssets} />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
<ArchiveAction menuItem unarchive={isAllArchive} onArchive={(assetIds) => $assetStore.removeAssets(assetIds)} />
<DeleteAssets menuItem onAssetDelete={(assetIds) => $assetStore.removeAssets(assetIds)} />
</AssetSelectContextMenu> </AssetSelectContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
{:else} {:else}
@ -414,13 +423,26 @@
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(previousRoute)}> <ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(previousRoute)}>
<svelte:fragment slot="trailing"> <svelte:fragment slot="trailing">
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
<MenuOption text="Change feature photo" on:click={() => (viewMode = ViewMode.SELECT_PERSON)} /> <MenuOption
<MenuOption text="Set date of birth" on:click={() => (viewMode = ViewMode.BIRTH_DATE)} /> text="Select featured photo"
<MenuOption text="Merge person" on:click={() => (viewMode = ViewMode.MERGE_PEOPLE)} /> icon={mdiAccountBoxOutline}
on:click={() => (viewMode = ViewMode.SELECT_PERSON)}
/>
<MenuOption <MenuOption
text={data.person.isHidden ? 'Unhide person' : 'Hide person'} text={data.person.isHidden ? 'Unhide person' : 'Hide person'}
icon={data.person.isHidden ? mdiEyeOutline : mdiEyeOffOutline}
on:click={() => toggleHidePerson()} on:click={() => toggleHidePerson()}
/> />
<MenuOption
text="Set date of birth"
icon={mdiCalendarEditOutline}
on:click={() => (viewMode = ViewMode.BIRTH_DATE)}
/>
<MenuOption
text="Merge people"
icon={mdiAccountMultipleCheckOutline}
on:click={() => (viewMode = ViewMode.MERGE_PEOPLE)}
/>
</AssetSelectContextMenu> </AssetSelectContextMenu>
</svelte:fragment> </svelte:fragment>
</ControlAppBar> </ControlAppBar>
@ -428,7 +450,7 @@
{#if viewMode === ViewMode.SELECT_PERSON} {#if viewMode === ViewMode.SELECT_PERSON}
<ControlAppBar on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}> <ControlAppBar on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}>
<svelte:fragment slot="leading">Select feature photo</svelte:fragment> <svelte:fragment slot="leading">Select featured photo</svelte:fragment>
</ControlAppBar> </ControlAppBar>
{/if} {/if}
{/if} {/if}

View File

@ -55,23 +55,25 @@
> >
<CreateSharedLink on:escape={() => (handleEscapeKey = true)} /> <CreateSharedLink on:escape={() => (handleEscapeKey = true)} />
<SelectAllAssets {assetStore} {assetInteractionStore} /> <SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={mdiPlus} title="Add"> <AssetSelectContextMenu icon={mdiPlus} title="Add to...">
<AddToAlbum /> <AddToAlbum />
<AddToAlbum shared /> <AddToAlbum shared />
</AssetSelectContextMenu> </AssetSelectContextMenu>
<DeleteAssets <FavoriteAction removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
on:escape={() => (handleEscapeKey = true)}
onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)}
/>
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
<FavoriteAction menuItem removeFavorite={isAllFavorite} onFavorite={() => assetStore.triggerUpdate()} />
<DownloadAction menuItem /> <DownloadAction menuItem />
<ArchiveAction menuItem onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
{#if $selectedAssets.size > 1} {#if $selectedAssets.size > 1}
<StackAction onStack={(assetIds) => assetStore.removeAssets(assetIds)} /> <StackAction onStack={(assetIds) => assetStore.removeAssets(assetIds)} />
{/if} {/if}
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
<ArchiveAction menuItem onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
<DeleteAssets
menuItem
on:escape={() => (handleEscapeKey = true)}
onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)}
/>
<hr />
<AssetJobActions /> <AssetJobActions />
</AssetSelectContextMenu> </AssetSelectContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>

View File

@ -209,18 +209,18 @@
<AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}> <AssetSelectControlBar assets={selectedAssets} clearSelect={() => (selectedAssets = new Set())}>
<CreateSharedLink /> <CreateSharedLink />
<CircleIconButton title="Select all" icon={mdiSelectAll} on:click={handleSelectAll} /> <CircleIconButton title="Select all" icon={mdiSelectAll} on:click={handleSelectAll} />
<AssetSelectContextMenu icon={mdiPlus} title="Add"> <AssetSelectContextMenu icon={mdiPlus} title="Add to...">
<AddToAlbum /> <AddToAlbum />
<AddToAlbum shared /> <AddToAlbum shared />
</AssetSelectContextMenu> </AssetSelectContextMenu>
<DeleteAssets {onAssetDelete} /> <FavoriteAction removeFavorite={isAllFavorite} onFavorite={triggerAssetUpdate} />
<AssetSelectContextMenu icon={mdiDotsVertical} title="Add"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Add">
<DownloadAction menuItem /> <DownloadAction menuItem />
<FavoriteAction menuItem removeFavorite={isAllFavorite} onFavorite={triggerAssetUpdate} />
<ArchiveAction menuItem unarchive={isAllArchived} onArchive={triggerAssetUpdate} />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
<ArchiveAction menuItem unarchive={isAllArchived} onArchive={triggerAssetUpdate} />
<DeleteAssets menuItem {onAssetDelete} />
</AssetSelectContextMenu> </AssetSelectContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
</div> </div>

View File

@ -76,13 +76,13 @@
<LinkButton on:click={handleRestoreTrash}> <LinkButton on:click={handleRestoreTrash}>
<div class="flex place-items-center gap-2 text-sm"> <div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiHistory} size="18" /> <Icon path={mdiHistory} size="18" />
Restore All Restore all
</div> </div>
</LinkButton> </LinkButton>
<LinkButton on:click={() => (isShowEmptyConfirmation = true)}> <LinkButton on:click={() => (isShowEmptyConfirmation = true)}>
<div class="flex place-items-center gap-2 text-sm"> <div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDeleteOutline} size="18" /> <Icon path={mdiDeleteOutline} size="18" />
Empty Trash Empty trash
</div> </div>
</LinkButton> </LinkButton>
</div> </div>