From 683f503647c5ccda445403e156dfe910f72b4a82 Mon Sep 17 00:00:00 2001
From: martabal <74269598+martabal@users.noreply.github.com>
Date: Sat, 25 May 2024 12:58:49 +0200
Subject: [PATCH] feat: use shortcut to favorite and archive on asset-grid
---
web/src/lib/actions/focus.ts | 4 +-
.../photos-page/actions/archive-action.svelte | 43 ++++---------------
.../actions/favorite-action.svelte | 41 +++---------------
.../components/photos-page/asset-grid.svelte | 42 ++++++++++++++----
web/src/lib/utils/actions.ts | 35 ++++++++++++++-
web/src/lib/utils/asset-utils.ts | 7 +++
.../[[assetId=id]]/+page.svelte | 8 +++-
.../[[assetId=id]]/+page.svelte | 8 +++-
.../[[assetId=id]]/+page.svelte | 6 ++-
web/src/routes/(user)/people/+page.svelte | 6 +--
.../[[assetId=id]]/+page.svelte | 7 ++-
.../(user)/photos/[[assetId=id]]/+page.svelte | 7 ++-
12 files changed, 121 insertions(+), 93 deletions(-)
diff --git a/web/src/lib/actions/focus.ts b/web/src/lib/actions/focus.ts
index 81185625f7..78413d8187 100644
--- a/web/src/lib/actions/focus.ts
+++ b/web/src/lib/actions/focus.ts
@@ -1,3 +1,3 @@
-export const initInput = (element: HTMLInputElement) => {
- element.focus();
+export const initInput = (element: HTMLInputElement, timeOut: number = 0) => {
+ setTimeout(() => element.focus(), timeOut);
};
diff --git a/web/src/lib/components/photos-page/actions/archive-action.svelte b/web/src/lib/components/photos-page/actions/archive-action.svelte
index f111db8036..7a52076ecd 100644
--- a/web/src/lib/components/photos-page/actions/archive-action.svelte
+++ b/web/src/lib/components/photos-page/actions/archive-action.svelte
@@ -1,15 +1,9 @@
diff --git a/web/src/lib/components/photos-page/actions/favorite-action.svelte b/web/src/lib/components/photos-page/actions/favorite-action.svelte
index 520dd46564..8b5ab6a4ee 100644
--- a/web/src/lib/components/photos-page/actions/favorite-action.svelte
+++ b/web/src/lib/components/photos-page/actions/favorite-action.svelte
@@ -1,15 +1,9 @@
diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte
index b2ead89028..1545f19551 100644
--- a/web/src/lib/components/photos-page/asset-grid.svelte
+++ b/web/src/lib/components/photos-page/asset-grid.svelte
@@ -7,22 +7,23 @@
import { locale, showDeleteModal } from '$lib/stores/preferences.store';
import { isSearchEnabled } from '$lib/stores/search.store';
import { featureFlags } from '$lib/stores/server-config.store';
- import { deleteAssets } from '$lib/utils/actions';
+ import { archiveAssets, deleteAssets, favoriteAssets } from '$lib/utils/actions';
import { type ShortcutOptions, shortcuts } from '$lib/actions/shortcut';
import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
import type { AlbumResponseDto, AssetResponseDto } from '@immich/sdk';
import { DateTime } from 'luxon';
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
- import IntersectionObserver from '../asset-viewer/intersection-observer.svelte';
- import Portal from '../shared-components/portal/portal.svelte';
- import Scrollbar from '../shared-components/scrollbar/scrollbar.svelte';
- import ShowShortcuts from '../shared-components/show-shortcuts.svelte';
- import AssetDateGroup from './asset-date-group.svelte';
- import { stackAssets } from '$lib/utils/asset-utils';
- import DeleteAssetDialog from './delete-asset-dialog.svelte';
+ import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
+ import Portal from '$lib/components/shared-components/portal/portal.svelte';
+ import Scrollbar from '$lib/components/shared-components/scrollbar/scrollbar.svelte';
+ import ShowShortcuts from '$lib/components/shared-components/show-shortcuts.svelte';
+ import AssetDateGroup from '$lib/components/photos-page/asset-date-group.svelte';
+ import { handleFavoriteAssetGrid, stackAssets } from '$lib/utils/asset-utils';
+ import DeleteAssetDialog from '$lib/components/photos-page/delete-asset-dialog.svelte';
import { handlePromiseError } from '$lib/utils';
import { selectAllAssets } from '$lib/utils/asset-utils';
import { navigate } from '$lib/utils/navigation';
+ import { page } from '$app/stores';
export let isSelectionMode = false;
export let singleSelect = false;
@@ -34,6 +35,7 @@
export let isShared = false;
export let album: AlbumResponseDto | null = null;
export let isShowDeleteConfirmation = false;
+ export let isAllFavorite: boolean | null = null;
$: isTrashEnabled = $featureFlags.loaded && $featureFlags.trash;
@@ -93,6 +95,22 @@
handlePromiseError(trashOrDelete(true));
};
+ const onArchive = async () => {
+ const assets = Array.from($selectedAssets);
+ const ids = assets.map((asset) => asset.id);
+ await archiveAssets($page.url.pathname !== AppRoute.ARCHIVE, (assetIds) => assetStore.removeAssets(assetIds), ids);
+ assetInteractionStore.clearMultiselect();
+ };
+
+ const onFavorite = async () => {
+ await favoriteAssets(
+ !isAllFavorite,
+ (assets, isFavorite) => handleFavoriteAssetGrid(assets, isFavorite, assetStore),
+ Array.from($selectedAssets),
+ );
+ assetInteractionStore.clearMultiselect();
+ };
+
const onStackAssets = async () => {
const ids = await stackAssets(Array.from($selectedAssets));
if (ids) {
@@ -128,6 +146,14 @@
{ shortcut: { key: 'D', ctrl: true }, onShortcut: () => deselectAllAssets() },
{ shortcut: { key: 's' }, onShortcut: () => onStackAssets() },
);
+ // you're not supposed to manage your assets on the trash page: there's no button to manage it, then there's no reason to have shortcuts to do it.
+ if ($page.url.pathname !== AppRoute.TRASH) {
+ shortcuts.push({ shortcut: { key: 'a', shift: true }, onShortcut: onArchive });
+ // on some page, you can't favorite assets (like on `/parthner`)
+ if (isAllFavorite !== null) {
+ shortcuts.push({ shortcut: { key: 'f' }, onShortcut: onFavorite });
+ }
+ }
}
return shortcuts;
diff --git a/web/src/lib/utils/actions.ts b/web/src/lib/utils/actions.ts
index ecfd29a8fc..4c55487999 100644
--- a/web/src/lib/utils/actions.ts
+++ b/web/src/lib/utils/actions.ts
@@ -1,11 +1,11 @@
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
-import { deleteAssets as deleteBulk, type AssetResponseDto } from '@immich/sdk';
+import { deleteAssets as deleteBulk, updateAssets, type AssetResponseDto } from '@immich/sdk';
import { handleError } from './handle-error';
export type OnDelete = (assetIds: string[]) => void;
export type OnRestore = (ids: string[]) => void;
export type OnArchive = (ids: string[], isArchived: boolean) => void;
-export type OnFavorite = (ids: string[], favorite: boolean) => void;
+export type OnFavorite = (assets: AssetResponseDto[], isFavorite: boolean) => void;
export type OnStack = (ids: string[]) => void;
export type OnUnstack = (assets: AssetResponseDto[]) => void;
@@ -22,3 +22,34 @@ export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids:
handleError(error, 'Error deleting assets');
}
};
+
+export const favoriteAssets = async (isFavorite: boolean, onAssetFavorite: OnFavorite, assets: AssetResponseDto[]) => {
+ try {
+ const ids = assets.map((asset) => asset.id);
+ await updateAssets({ assetBulkUpdateDto: { ids, isFavorite } });
+
+ onAssetFavorite(assets, isFavorite);
+
+ notificationController.show({
+ message: isFavorite ? `Added ${ids.length} to favorites` : `Removed ${ids.length} from favorites`,
+ type: NotificationType.Info,
+ });
+ } catch (error) {
+ handleError(error, `Unable to ${isFavorite ? 'add to' : 'remove from'} favorites`);
+ }
+};
+
+export const archiveAssets = async (isArchived: boolean, onAssetArchive: OnArchive, ids: string[]) => {
+ try {
+ await updateAssets({ assetBulkUpdateDto: { ids, isArchived } });
+
+ onAssetArchive(ids, isArchived);
+
+ notificationController.show({
+ message: `${isArchived ? 'Archived' : 'Unarchived'} ${ids.length}`,
+ type: NotificationType.Info,
+ });
+ } catch (error) {
+ handleError(error, `Unable to ${isArchived ? 'archive' : 'unarchive'}`);
+ }
+};
diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts
index f94c8b4375..ae2d9028c9 100644
--- a/web/src/lib/utils/asset-utils.ts
+++ b/web/src/lib/utils/asset-utils.ts
@@ -397,3 +397,10 @@ export const selectAllAssets = async (assetStore: AssetStore, assetInteractionSt
export const delay = async (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};
+
+export const handleFavoriteAssetGrid = (assets: AssetResponseDto[], isFavorite: boolean, assetStore: AssetStore) => {
+ for (const asset of assets) {
+ asset.isFavorite = isFavorite;
+ }
+ assetStore.triggerUpdate();
+};
diff --git a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 32fb7c01ee..69513ceb06 100644
--- a/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/albums/[albumId=id]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -43,7 +43,7 @@
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { user } from '$lib/stores/user.store';
import { handlePromiseError, s } from '$lib/utils';
- import { downloadAlbum } from '$lib/utils/asset-utils';
+ import { downloadAlbum, handleFavoriteAssetGrid } from '$lib/utils/asset-utils';
import { clickOutside } from '$lib/actions/click-outside';
import { getContextMenuPosition } from '$lib/utils/context-menu';
import { openFileUploadDialog } from '$lib/utils/file-uploader';
@@ -412,7 +412,10 @@
{#if isAllUserOwned}
- assetStore.triggerUpdate()} />
+ handleFavoriteAssetGrid(assets, isFavorite, assetStore)}
+ />
{/if}
@@ -546,6 +549,7 @@
{album}
{assetStore}
{assetInteractionStore}
+ {isAllFavorite}
isShared={album.albumUsers.length > 0}
isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL}
singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL}
diff --git a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 2f943a4ffd..dff88e9d4f 100644
--- a/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/archive/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -14,6 +14,7 @@
import { AssetAction } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store';
+ import { handleFavoriteAssetGrid } from '$lib/utils/asset-utils';
import type { PageData } from './$types';
import { mdiPlus, mdiDotsVertical } from '@mdi/js';
@@ -35,7 +36,10 @@
- assetStore.triggerUpdate()} />
+ handleFavoriteAssetGrid(assets, isFavorite, assetStore)}
+ />
assetStore.removeAssets(assetIds)} />
@@ -44,7 +48,7 @@
{/if}
-
+
diff --git a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
index 1d080d1003..92730326ef 100644
--- a/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/favorites/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -16,6 +16,7 @@
import { AssetAction } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { AssetStore } from '$lib/stores/assets.store';
+ import { handleFavoriteAssetGrid } from '$lib/utils/asset-utils';
import type { PageData } from './$types';
import { mdiDotsVertical, mdiPlus } from '@mdi/js';
@@ -31,7 +32,10 @@
{#if $isMultiSelectState}
assetInteractionStore.clearMultiselect()}>
- assetStore.removeAssets(assetIds)} />
+ handleFavoriteAssetGrid(assets, isFavorite, assetStore)}
+ />
diff --git a/web/src/routes/(user)/people/+page.svelte b/web/src/routes/(user)/people/+page.svelte
index e1fdb9cacc..f4aad4c79d 100644
--- a/web/src/routes/(user)/people/+page.svelte
+++ b/web/src/routes/(user)/people/+page.svelte
@@ -34,6 +34,7 @@
import { clearQueryParam } from '$lib/utils/navigation';
import SearchPeople from '$lib/components/faces-page/people-search.svelte';
import LinkButton from '$lib/components/elements/buttons/link-button.svelte';
+ import { initInput } from '$lib/actions/focus';
export let data: PageData;
@@ -62,7 +63,6 @@
let handleSearchPeople: (force?: boolean, name?: string) => Promise;
let showPeople: PersonResponseDto[] = [];
let countVisiblePeople: number;
- let changeNameInputEl: HTMLInputElement | null;
let innerHeight: number;
for (const person of people) {
@@ -244,8 +244,6 @@
personName = detail.name;
personMerge1 = detail;
edittingPerson = detail;
-
- setTimeout(() => changeNameInputEl?.focus(), 100);
};
const handleSetBirthDate = (detail: PersonResponseDto) => {
@@ -463,7 +461,7 @@
name="name"
type="text"
bind:value={personName}
- bind:this={changeNameInputEl}
+ use:initInput={100}
/>
diff --git a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
index cfcc3eeeb7..c18c4000ae 100644
--- a/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
+++ b/web/src/routes/(user)/people/[personId]/[[photos=photos]]/[[assetId=id]]/+page.svelte
@@ -56,6 +56,7 @@
import { onMount } from 'svelte';
import type { PageData } from './$types';
import { listNavigation } from '$lib/actions/list-navigation';
+ import { handleFavoriteAssetGrid } from '$lib/utils/asset-utils';
export let data: PageData;
@@ -383,7 +384,10 @@
- assetStore.triggerUpdate()} />
+ handleFavoriteAssetGrid(assets, isFavorite, assetStore)}
+ />
@@ -434,6 +438,7 @@
{#key refreshAssetGrid}
- assetStore.triggerUpdate()} />
+ handleFavoriteAssetGrid(assets, isFavorite, assetStore)}
+ />
{#if $selectedAssets.size > 1 || isAssetStackSelected}
@@ -92,6 +96,7 @@