feat: add shortcuts to asset-grid

This commit is contained in:
martabal 2024-01-19 15:49:30 +01:00
parent b4d1470586
commit e6e7f78f9f
No known key found for this signature in database
GPG Key ID: C00196E3148A52BD
12 changed files with 205 additions and 127 deletions

View File

@ -1,57 +1,32 @@
<script lang="ts"> <script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import {
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { api } from '@api';
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 { mdiArchiveArrowUpOutline, mdiArchiveArrowDownOutline, mdiTimerSand } from '@mdi/js'; import { mdiArchiveArrowUpOutline, mdiArchiveArrowDownOutline, mdiTimerSand } from '@mdi/js';
import type { OnArchive } from '$lib/utils/actions'; import { archiveAssets, type OnArchive } from '$lib/utils/actions';
import { isAllArchived } from '$lib/stores/asset-interaction.store';
export let onArchive: OnArchive | undefined = undefined; export let onArchive: OnArchive | undefined = undefined;
export let menuItem = false; export let menuItem = false;
export let unarchive = false;
$: text = unarchive ? 'Unarchive' : 'Archive'; $: text = $isAllArchived ? 'Unarchive' : 'Archive';
$: icon = unarchive ? mdiArchiveArrowUpOutline : mdiArchiveArrowDownOutline; $: icon = $isAllArchived ? mdiArchiveArrowUpOutline : mdiArchiveArrowDownOutline;
let loading = false; let loading = false;
const { clearSelect, getOwnedAssets } = getAssetControlContext(); const { clearSelect, getOwnedAssets } = getAssetControlContext();
const handleArchive = async () => { const handleArchive = async () => {
const isArchived = !unarchive; const isArchived = !$isAllArchived;
loading = true; loading = true;
try { const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isArchived !== isArchived);
const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isArchived !== isArchived); if (await archiveAssets(isArchived, onArchive, assets)) {
const ids = assets.map(({ id }) => id);
if (ids.length > 0) {
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, isArchived } });
}
for (const asset of assets) {
asset.isArchived = isArchived;
}
onArchive?.(ids, isArchived);
notificationController.show({
message: `${isArchived ? 'Archived' : 'Unarchived'} ${ids.length}`,
type: NotificationType.Info,
});
clearSelect(); clearSelect();
} catch (error) {
handleError(error, `Unable to ${isArchived ? 'archive' : 'unarchive'}`);
} finally {
loading = false;
} }
loading = false;
}; };
</script> </script>

View File

@ -1,58 +1,33 @@
<script lang="ts"> <script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import {
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
import { api } from '@api';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { mdiHeartMinusOutline, mdiHeartOutline, mdiTimerSand } from '@mdi/js'; import { mdiHeartMinusOutline, mdiHeartOutline, mdiTimerSand } from '@mdi/js';
import type { OnFavorite } from '$lib/utils/actions'; import { favoriteAssets, type OnFavorite } from '$lib/utils/actions';
import { isAllFavorite } from '$lib/stores/asset-interaction.store';
export let onFavorite: OnFavorite | undefined = undefined; export let onFavorite: OnFavorite | undefined = undefined;
export let menuItem = false; export let menuItem = false;
export let removeFavorite: boolean;
$: text = removeFavorite ? 'Remove from Favorites' : 'Favorite'; $: text = $isAllFavorite ? 'Remove from Favorites' : 'Favorite';
$: icon = removeFavorite ? mdiHeartMinusOutline : mdiHeartOutline; $: icon = $isAllFavorite ? mdiHeartMinusOutline : mdiHeartOutline;
let loading = false; let loading = false;
const { clearSelect, getOwnedAssets } = getAssetControlContext(); const { clearSelect, getOwnedAssets } = getAssetControlContext();
const handleFavorite = async () => { const handleFavorite = async () => {
const isFavorite = !removeFavorite; const isFavorite = !$isAllFavorite;
loading = true; loading = true;
try { const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isFavorite !== isFavorite);
const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isFavorite !== isFavorite);
const ids = assets.map(({ id }) => id);
if (ids.length > 0) {
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, isFavorite } });
}
for (const asset of assets) {
asset.isFavorite = isFavorite;
}
onFavorite?.(ids, isFavorite);
notificationController.show({
message: isFavorite ? `Added ${ids.length} to favorites` : `Removed ${ids.length} from favorites`,
type: NotificationType.Info,
});
if (await favoriteAssets(isFavorite, onFavorite, assets)) {
clearSelect(); clearSelect();
} catch (error) {
handleError(error, `Unable to ${isFavorite ? 'add to' : 'remove from'} favorites`);
} finally {
loading = false;
} }
loading = false;
}; };
</script> </script>

View File

@ -1,10 +1,11 @@
<script lang="ts"> <script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store'; import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { BucketPosition, type AssetStore, isSelectAllCancelled } from '$lib/stores/assets.store';
import { handleError } from '$lib/utils/handle-error';
import { get } from 'svelte/store'; import { get } from 'svelte/store';
import { mdiTimerSand, mdiSelectAll } from '@mdi/js'; import { mdiTimerSand, mdiSelectAll } from '@mdi/js';
import { selectAll } from '$lib/utils/actions';
import type { AssetStore } from '$lib/stores/assets.store';
export let assetStore: AssetStore; export let assetStore: AssetStore;
export let assetInteractionStore: AssetInteractionStore; export let assetInteractionStore: AssetInteractionStore;
@ -12,25 +13,11 @@
let selecting = false; let selecting = false;
const handleSelectAll = async () => { const handleSelectAll = async () => {
try { selecting = true;
$isSelectAllCancelled = false;
selecting = true;
const assetGridState = get(assetStore); selectAll(get(assetStore), assetStore, assetInteractionStore);
for (const bucket of assetGridState.buckets) {
if ($isSelectAllCancelled) {
break;
}
await assetStore.loadBucket(bucket.bucketDate, BucketPosition.Unknown);
for (const asset of bucket.assets) {
assetInteractionStore.selectAsset(asset);
}
}
selecting = false; selecting = false;
} catch (e) {
handleError(e, 'Error selecting all assets');
}
}; };
</script> </script>

View File

@ -2,7 +2,12 @@
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { AppRoute, AssetAction } from '$lib/constants'; import { AppRoute, AssetAction } from '$lib/constants';
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store'; import {
isAllUserOwned,
type AssetInteractionStore,
isAllFavorite,
isAllArchived,
} from '$lib/stores/asset-interaction.store';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store'; import { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store';
import { locale, showDeleteModal } from '$lib/stores/preferences.store'; import { locale, showDeleteModal } from '$lib/stores/preferences.store';
@ -19,8 +24,11 @@
import AssetDateGroup from './asset-date-group.svelte'; import AssetDateGroup from './asset-date-group.svelte';
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { shouldIgnoreShortcut } from '$lib/utils/shortcut'; import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
import { deleteAssets } from '$lib/utils/actions'; import { archiveAssets, deleteAssets, favoriteAssets, selectAll } from '$lib/utils/actions';
import DeleteAssetDialog from './delete-asset-dialog.svelte'; import DeleteAssetDialog from './delete-asset-dialog.svelte';
import { downloadArchive, downloadFile } from '$lib/utils/asset-utils';
import { user } from '$lib/stores/user.store';
import { get } from 'svelte/store';
export let isSelectionMode = false; export let isSelectionMode = false;
export let singleSelect = false; export let singleSelect = false;
@ -47,6 +55,9 @@
$: idsSelectedAssets = Array.from($selectedAssets) $: idsSelectedAssets = Array.from($selectedAssets)
.filter((a) => !a.isExternal) .filter((a) => !a.isExternal)
.map((a) => a.id); .map((a) => a.id);
$: $isAllUserOwned = Array.from($selectedAssets).every((asset) => asset.ownerId === $user.id);
$: $isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
$: $isAllArchived = Array.from($selectedAssets).every((asset) => asset.isArchived);
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>(); const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
@ -70,24 +81,30 @@
assetStore.disconnect(); assetStore.disconnect();
}); });
const trashOrDelete = (force: boolean = false) => { const trashOrDelete = async (force: boolean = false) => {
isShowDeleteConfirmation = false; isShowDeleteConfirmation = false;
deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets); if (
assetInteractionStore.clearMultiselect(); await deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets)
) {
assetInteractionStore.clearMultiselect();
}
}; };
const handleKeyboardPress = (event: KeyboardEvent) => { const handleKeyboardPress = async (event: KeyboardEvent) => {
if ($isSearchEnabled || shouldIgnoreShortcut(event)) { if ($isSearchEnabled || shouldIgnoreShortcut(event)) {
return; return;
} }
const key = event.key; const key = event.key;
const shiftKey = event.shiftKey; const shiftKey = event.shiftKey;
const ctrlKey = event.ctrlKey;
const assets = Array.from($selectedAssets);
if (!$showAssetViewer) { if (!$showAssetViewer) {
switch (key) { switch (key) {
case 'Escape': case '/':
dispatch('escape'); event.preventDefault();
goto(AppRoute.EXPLORE);
return; return;
case '?': case '?':
if (event.shiftKey) { if (event.shiftKey) {
@ -95,9 +112,43 @@
showShortcuts = !showShortcuts; showShortcuts = !showShortcuts;
} }
return; return;
case '/': case 'a':
event.preventDefault(); case 'A':
goto(AppRoute.EXPLORE); if ($isMultiSelectState) {
if (ctrlKey) {
event.preventDefault();
selectAll(get(assetStore), assetStore, assetInteractionStore);
return;
}
if ($isAllUserOwned) {
if (
await archiveAssets(
!$isAllArchived,
(assetIds) => {
for (const assetId of assetIds) {
assetStore.removeAsset(assetId);
}
},
assets,
)
) {
assetInteractionStore.clearMultiselect();
}
}
}
return;
case 'd':
case 'D':
if ($isMultiSelectState) {
assetInteractionStore.clearMultiselect();
if (assets.length === 1) {
await downloadFile(assets[0]);
return;
}
await downloadArchive('immich.zip', { assetIds: assets.map((asset) => asset.id) });
}
return; return;
case 'Delete': case 'Delete':
if ($isMultiSelectState) { if ($isMultiSelectState) {
@ -113,6 +164,16 @@
trashOrDelete(force); trashOrDelete(force);
} }
return; return;
case 'Escape':
dispatch('escape');
return;
case 'f':
if ($isMultiSelectState && $isAllUserOwned) {
if (await favoriteAssets(!$isAllFavorite, undefined, assets)) {
assetInteractionStore.clearMultiselect();
}
}
return;
} }
} }
}; };

View File

@ -147,3 +147,7 @@ export function createAssetInteractionStore(): AssetInteractionStore {
}, },
}; };
} }
export const isAllUserOwned = writable<boolean>();
export const isAllFavorite = writable<boolean>();
export const isAllArchived = writable<boolean>();

View File

@ -1,6 +1,9 @@
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
import { api } from '@api'; import { api, AssetResponseDto } from '@api';
import { handleError } from './handle-error'; import { handleError } from './handle-error';
import { isSelectAllCancelled, type AssetStore, BucketPosition } from '$lib/stores/assets.store';
import { get } from 'svelte/store';
import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
export type OnDelete = (assetId: string) => void; export type OnDelete = (assetId: string) => void;
export type OnRestore = (ids: string[]) => void; export type OnRestore = (ids: string[]) => void;
@ -8,7 +11,7 @@ export type OnArchive = (ids: string[], isArchived: boolean) => void;
export type OnFavorite = (ids: string[], favorite: boolean) => void; export type OnFavorite = (ids: string[], favorite: boolean) => void;
export type OnStack = (ids: string[]) => void; export type OnStack = (ids: string[]) => void;
export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => { export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]): Promise<boolean> => {
try { try {
await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } }); await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } });
for (const id of ids) { for (const id of ids) {
@ -19,7 +22,92 @@ export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids:
message: `${force ? 'Permanently deleted' : 'Trashed'} ${ids.length} assets`, message: `${force ? 'Permanently deleted' : 'Trashed'} ${ids.length} assets`,
type: NotificationType.Info, type: NotificationType.Info,
}); });
return true;
} catch (e) { } catch (e) {
handleError(e, 'Error deleting assets'); handleError(e, 'Error deleting assets');
} }
return false;
};
export const favoriteAssets = async (
isFavorite: boolean,
onFavorite: OnFavorite | undefined,
assets: AssetResponseDto[],
): Promise<boolean> => {
try {
const ids = assets.map(({ id }) => id);
if (ids.length > 0) {
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, isFavorite } });
}
for (const asset of assets) {
asset.isFavorite = isFavorite;
}
onFavorite?.(ids, isFavorite);
notificationController.show({
message: isFavorite ? `Added ${ids.length} to favorites` : `Removed ${ids.length} from favorites`,
type: NotificationType.Info,
});
return true;
} catch (error) {
handleError(error, `Unable to ${isFavorite ? 'add to' : 'remove from'} favorites`);
}
return false;
};
export const archiveAssets = async (
isArchived: boolean,
onArchive: OnArchive | undefined,
assets: AssetResponseDto[],
) => {
try {
const ids = assets.map(({ id }) => id);
if (ids.length > 0) {
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, isArchived } });
}
for (const asset of assets) {
asset.isArchived = isArchived;
}
onArchive?.(ids, isArchived);
notificationController.show({
message: `${isArchived ? 'Archived' : 'Unarchived'} ${ids.length}`,
type: NotificationType.Info,
});
return true;
} catch (error) {
handleError(error, `Unable to ${isArchived ? 'archive' : 'unarchive'}`);
}
return false;
};
export const selectAll = async (
assetGridState: AssetStore,
assetStore: AssetStore,
assetInteractionStore: AssetInteractionStore,
): Promise<boolean> => {
isSelectAllCancelled.set(false);
try {
for (const bucket of assetGridState.buckets) {
if (get(isSelectAllCancelled)) {
break;
}
await assetStore.loadBucket(bucket.bucketDate, BucketPosition.Unknown);
for (const asset of bucket.assets) {
assetInteractionStore.selectAsset(asset);
}
}
return true;
} catch (e) {
handleError(e, 'Error selecting all assets');
}
return false;
}; };

View File

@ -29,7 +29,7 @@
} from '$lib/components/shared-components/notification/notification'; } from '$lib/components/shared-components/notification/notification';
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte'; import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
import { AppRoute, dateFormats } from '$lib/constants'; import { AppRoute, dateFormats } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store'; import { createAssetInteractionStore, isAllUserOwned } from '$lib/stores/asset-interaction.store';
import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store'; import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { AssetStore } from '$lib/stores/assets.store'; import { AssetStore } from '$lib/stores/assets.store';
@ -110,8 +110,6 @@
const { selectedAssets: timelineSelected } = timelineInteractionStore; const { selectedAssets: timelineSelected } = timelineInteractionStore;
$: isOwned = $user.id == album.ownerId; $: isOwned = $user.id == album.ownerId;
$: isAllUserOwned = Array.from($selectedAssets).every((asset) => asset.ownerId === $user.id);
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
$: { $: {
if (isShowActivity) { if (isShowActivity) {
assetGridWidth = globalWidth - (globalWidth < 768 ? 360 : 460); assetGridWidth = globalWidth - (globalWidth < 768 ? 360 : 460);
@ -440,15 +438,15 @@
<AddToAlbum shared /> <AddToAlbum shared />
</AssetSelectContextMenu> </AssetSelectContextMenu>
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
{#if isAllUserOwned} {#if $isAllUserOwned}
<FavoriteAction menuItem removeFavorite={isAllFavorite} /> <FavoriteAction menuItem />
<ArchiveAction menuItem />
{/if} {/if}
<ArchiveAction menuItem />
<DownloadAction menuItem filename="{album.albumName}.zip" /> <DownloadAction menuItem filename="{album.albumName}.zip" />
{#if isOwned || isAllUserOwned} {#if isOwned || $isAllUserOwned}
<RemoveFromAlbum menuItem bind:album onRemove={(assetIds) => handleRemoveAssets(assetIds)} /> <RemoveFromAlbum menuItem bind:album onRemove={(assetIds) => handleRemoveAssets(assetIds)} />
{/if} {/if}
{#if isAllUserOwned} {#if $isAllUserOwned}
<DeleteAssets menuItem onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} /> <DeleteAssets menuItem onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />

View File

@ -23,13 +23,11 @@
const assetStore = new AssetStore({ isArchived: true }); const assetStore = new AssetStore({ isArchived: true });
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore; const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
</script> </script>
{#if $isMultiSelectState} {#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}> <AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
<ArchiveAction unarchive onArchive={(ids) => assetStore.removeAssets(ids)} /> <ArchiveAction onArchive={(ids) => assetStore.removeAssets(ids)} />
<CreateSharedLink /> <CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} /> <SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={mdiPlus} title="Add"> <AssetSelectContextMenu icon={mdiPlus} title="Add">
@ -39,7 +37,7 @@
<DeleteAssets onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} /> <DeleteAssets onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
<AssetSelectContextMenu icon={mdiDotsVertical} title="Add"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Add">
<DownloadAction menuItem /> <DownloadAction menuItem />
<FavoriteAction menuItem removeFavorite={isAllFavorite} /> <FavoriteAction menuItem />
</AssetSelectContextMenu> </AssetSelectContextMenu>
</AssetSelectControlBar> </AssetSelectControlBar>
{/if} {/if}

View File

@ -25,14 +25,12 @@
const assetStore = new AssetStore({ isFavorite: true }); const assetStore = new AssetStore({ isFavorite: true });
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore; const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllArchive = Array.from($selectedAssets).every((asset) => asset.isArchived);
</script> </script>
<!-- Multiselection mode app bar --> <!-- Multiselection mode app bar -->
{#if $isMultiSelectState} {#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}> <AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
<FavoriteAction removeFavorite onFavorite={(ids) => assetStore.removeAssets(ids)} /> <FavoriteAction onFavorite={(ids) => assetStore.removeAssets(ids)} />
<CreateSharedLink /> <CreateSharedLink />
<SelectAllAssets {assetStore} {assetInteractionStore} /> <SelectAllAssets {assetStore} {assetInteractionStore} />
<AssetSelectContextMenu icon={mdiPlus} title="Add"> <AssetSelectContextMenu icon={mdiPlus} title="Add">
@ -42,7 +40,7 @@
<DeleteAssets onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} /> <DeleteAssets onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
<DownloadAction menuItem /> <DownloadAction menuItem />
<ArchiveAction menuItem unarchive={isAllArchive} /> <ArchiveAction menuItem />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
</AssetSelectContextMenu> </AssetSelectContextMenu>

View File

@ -108,8 +108,6 @@
isSearchingPeople = false; isSearchingPeople = false;
}; };
$: isAllArchive = Array.from($selectedAssets).every((asset) => asset.isArchived);
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
$: $onPersonThumbnail === data.person.id && $: $onPersonThumbnail === data.person.id &&
(thumbnailData = api.getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`); (thumbnailData = api.getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`);
@ -394,8 +392,8 @@
<DeleteAssets onAssetDelete={(assetId) => $assetStore.removeAsset(assetId)} /> <DeleteAssets onAssetDelete={(assetId) => $assetStore.removeAsset(assetId)} />
<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} /> <FavoriteAction menuItem />
<ArchiveAction menuItem unarchive={isAllArchive} onArchive={(ids) => $assetStore.removeAssets(ids)} /> <ArchiveAction menuItem onArchive={(ids) => $assetStore.removeAssets(ids)} />
<MenuOption text="Fix incorrect match" on:click={handleReassignAssets} /> <MenuOption text="Fix incorrect match" on:click={handleReassignAssets} />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />

View File

@ -31,8 +31,6 @@
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore; const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
const handleEscape = () => { const handleEscape = () => {
if ($showAssetViewer) { if ($showAssetViewer) {
return; return;
@ -65,7 +63,7 @@
onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} onAssetDelete={(assetId) => assetStore.removeAsset(assetId)}
/> />
<AssetSelectContextMenu icon={mdiDotsVertical} title="Menu"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Menu">
<FavoriteAction menuItem removeFavorite={isAllFavorite} /> <FavoriteAction menuItem />
<DownloadAction menuItem /> <DownloadAction menuItem />
<ArchiveAction menuItem onArchive={(ids) => assetStore.removeAssets(ids)} /> <ArchiveAction menuItem onArchive={(ids) => assetStore.removeAssets(ids)} />
{#if $selectedAssets.size > 1} {#if $selectedAssets.size > 1}

View File

@ -96,8 +96,6 @@
let selectedAssets: Set<AssetResponseDto> = new Set(); let selectedAssets: Set<AssetResponseDto> = new Set();
$: isMultiSelectionMode = selectedAssets.size > 0; $: isMultiSelectionMode = selectedAssets.size > 0;
$: isAllArchived = Array.from(selectedAssets).every((asset) => asset.isArchived);
$: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
$: searchResultAssets = data.results?.assets.items; $: searchResultAssets = data.results?.assets.items;
const onAssetDelete = (assetId: string) => { const onAssetDelete = (assetId: string) => {
@ -122,8 +120,8 @@
<AssetSelectContextMenu icon={mdiDotsVertical} title="Add"> <AssetSelectContextMenu icon={mdiDotsVertical} title="Add">
<DownloadAction menuItem /> <DownloadAction menuItem />
<FavoriteAction menuItem removeFavorite={isAllFavorite} /> <FavoriteAction menuItem />
<ArchiveAction menuItem unarchive={isAllArchived} /> <ArchiveAction menuItem />
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
</AssetSelectContextMenu> </AssetSelectContextMenu>