dispatchClick('click', album)}
- on:keydown={() => dispatchClick('click', album)}
- on:mouseenter={() => (showVerticalDots = true)}
- on:mouseleave={() => (showVerticalDots = false)}
+ class="group relative rounded-2xl border border-transparent p-5 hover:bg-gray-100 hover:border-gray-200 dark:hover:border-gray-800 dark:hover:bg-gray-900"
data-testid="album-card"
>
-
- {#if showContextMenu}
+ {#if onShowContextMenu}
-
+
diff --git a/web/src/lib/components/album-page/album-card.ts b/web/src/lib/components/album-page/album-card.ts
deleted file mode 100644
index deb98e976..000000000
--- a/web/src/lib/components/album-page/album-card.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { AlbumResponseDto } from '@immich/sdk';
-
-export type OnShowContextMenu = {
- showalbumcontextmenu: OnShowContextMenuDetail;
-};
-
-export type OnClick = {
- click: OnClickDetail;
-};
-
-export type OnShowContextMenuDetail = { x: number; y: number };
-export type OnClickDetail = AlbumResponseDto;
diff --git a/web/src/lib/components/album-page/album-description.svelte b/web/src/lib/components/album-page/album-description.svelte
index 9e988ba75..e860fbcd2 100644
--- a/web/src/lib/components/album-page/album-description.svelte
+++ b/web/src/lib/components/album-page/album-description.svelte
@@ -2,6 +2,7 @@
import { autoGrowHeight } from '$lib/utils/autogrow';
import { updateAlbumInfo } from '@immich/sdk';
import { handleError } from '$lib/utils/handle-error';
+ import { shortcut } from '$lib/utils/shortcut';
export let id: string;
export let description: string;
@@ -37,6 +38,10 @@
on:focusout={handleUpdateDescription}
use:autoGrowHeight
placeholder="Add description"
+ use:shortcut={{
+ shortcut: { key: 'Enter', ctrl: true },
+ onShortcut: (e) => e.currentTarget.blur(),
+ }}
/>
{:else if description}
diff --git a/web/src/lib/components/album-page/album-options.svelte b/web/src/lib/components/album-page/album-options.svelte
index 6cbce418b..d5f816047 100644
--- a/web/src/lib/components/album-page/album-options.svelte
+++ b/web/src/lib/components/album-page/album-options.svelte
@@ -1,22 +1,55 @@
dispatch('close')}>
@@ -34,8 +67,16 @@
-
SHARING
-
+
SETTINGS
+
+ {#if order}
+
+ {/if}
import { updateAlbumInfo } from '@immich/sdk';
import { handleError } from '$lib/utils/handle-error';
+ import { shortcut } from '$lib/utils/shortcut';
export let id: string;
export let albumName: string;
@@ -29,7 +30,7 @@
e.key === 'Enter' && e.currentTarget.blur()}
+ use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: (e) => e.currentTarget.blur() }}
on:blur={handleUpdateName}
class="w-[99%] mb-2 border-b-2 border-transparent text-6xl text-immich-primary outline-none transition-all dark:text-immich-dark-primary {isOwned
? 'hover:border-gray-400'
diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte
index ab5eb91d5..08d610ceb 100644
--- a/web/src/lib/components/album-page/album-viewer.svelte
+++ b/web/src/lib/components/album-page/album-viewer.svelte
@@ -1,11 +1,9 @@
+ {
+ if (!$showAssetViewer && $isMultiSelectState) {
+ assetInteractionStore.clearMultiselect();
+ }
+ },
+ }}
+/>
+
{#if $isMultiSelectState}
-
-
+
+
diff --git a/web/src/lib/components/album-page/albums-controls.svelte b/web/src/lib/components/album-page/albums-controls.svelte
new file mode 100644
index 000000000..a58dbc0e3
--- /dev/null
+++ b/web/src/lib/components/album-page/albums-controls.svelte
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+ Create album
+
+
+
+ {
+ return {
+ title: option.title,
+ icon: option.sortDesc ? mdiArrowDownThin : mdiArrowUpThin,
+ };
+ }}
+ on:select={(event) => {
+ for (const key of sortByOptions) {
+ if (key.title === event.detail.title) {
+ key.sortDesc = !key.sortDesc;
+ $albumViewSettings.sortBy = key.title;
+ break;
+ }
+ }
+ }}
+/>
+
+ handleChangeListMode()}>
+
+ {#if $albumViewSettings.view === AlbumViewMode.List}
+
+
Cover
+ {:else}
+
+
List
+ {/if}
+
+
diff --git a/web/src/lib/components/album-page/albums-list.svelte b/web/src/lib/components/album-page/albums-list.svelte
new file mode 100644
index 000000000..607457e1c
--- /dev/null
+++ b/web/src/lib/components/album-page/albums-list.svelte
@@ -0,0 +1,282 @@
+
+
+
+
+{#if shouldShowEditAlbumForm}
+ (shouldShowEditAlbumForm = false)}>
+ successModifyAlbum()}
+ on:cancel={() => (shouldShowEditAlbumForm = false)}
+ />
+
+{/if}
+
+{#if albums.length > 0}
+
+ {#if $albumViewSettings.view === AlbumViewMode.Cover}
+
+ {:else if $albumViewSettings.view === AlbumViewMode.List}
+ chooseAlbumToDelete(album)}
+ onAlbumToEdit={(album) => handleEdit(album)}
+ />
+ {/if}
+
+
+{:else}
+
+{/if}
+
+
+{#if isShowContextMenu}
+
+
+ setAlbumToDelete()}>
+
+
+ Delete album
+
+
+
+
+{/if}
+
+{#if albumToDelete}
+ (albumToDelete = null)}
+ >
+
+ Are you sure you want to delete the album {albumToDelete.albumName}?
+ If this album is shared, other users will not be able to access it anymore.
+
+
+{/if}
diff --git a/web/src/lib/components/elements/table-header.svelte b/web/src/lib/components/album-page/albums-table-header.svelte
similarity index 57%
rename from web/src/lib/components/elements/table-header.svelte
rename to web/src/lib/components/album-page/albums-table-header.svelte
index 0b68dd0e5..b10e34e11 100644
--- a/web/src/lib/components/elements/table-header.svelte
+++ b/web/src/lib/components/album-page/albums-table-header.svelte
@@ -1,14 +1,15 @@
@@ -18,7 +19,7 @@
class="rounded-lg p-2 hover:bg-immich-dark-primary hover:dark:bg-immich-dark-primary/50"
on:click={() => handleSort()}
>
- {#if albumViewSettings === option.title}
+ {#if $albumViewSettings.sortBy === option.title}
{#if option.sortDesc}
↓
{:else}
diff --git a/web/src/lib/components/album-page/albums-table.svelte b/web/src/lib/components/album-page/albums-table.svelte
new file mode 100644
index 000000000..e77dc93d8
--- /dev/null
+++ b/web/src/lib/components/album-page/albums-table.svelte
@@ -0,0 +1,85 @@
+
+
+
+
+
+ {#each sortByOptions as option, index (index)}
+
+ {/each}
+ Action |
+
+
+
+ {#each albumsFiltered as album (album.id)}
+ goto(`${AppRoute.ALBUMS}/${album.id}`)}
+ >
+
+ {album.albumName} |
+
+ {album.assetCount}
+ {album.assetCount > 1 ? `items` : `item`}
+ |
+ {dateLocaleString(album.updatedAt)}
+ |
+ {dateLocaleString(album.createdAt)} |
+
+ {#if album.endDate}
+ {dateLocaleString(album.endDate)}
+ {:else}
+ ❌
+ {/if} |
+ {#if album.startDate}
+ {dateLocaleString(album.startDate)}
+ {:else}
+ ❌
+ {/if} |
+
+
+
+
+ |
+
+ {/each}
+
+
diff --git a/web/src/lib/components/album-page/thumbnail-selection.svelte b/web/src/lib/components/album-page/thumbnail-selection.svelte
index c12abc891..e558592b2 100644
--- a/web/src/lib/components/album-page/thumbnail-selection.svelte
+++ b/web/src/lib/components/album-page/thumbnail-selection.svelte
@@ -45,7 +45,7 @@
{#each album.assets as asset (asset.id)}
- (selectedThumbnail = asset)} selected={isSelected(asset.id)} />
+ (selectedThumbnail = asset)} selected={isSelected(asset.id)} />
{/each}
diff --git a/web/src/lib/components/asset-viewer/activity-viewer.svelte b/web/src/lib/components/asset-viewer/activity-viewer.svelte
index 46305314b..cf98c527f 100644
--- a/web/src/lib/components/asset-viewer/activity-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/activity-viewer.svelte
@@ -25,6 +25,7 @@
import { NotificationType, notificationController } from '../shared-components/notification/notification';
import UserAvatar from '../shared-components/user-avatar.svelte';
import { locale } from '$lib/stores/preferences.store';
+ import { shortcut } from '$lib/utils/shortcut';
const units: Intl.RelativeTimeFormatUnit[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'];
@@ -95,14 +96,6 @@
}
};
- const handleEnter = async (event: KeyboardEvent) => {
- if (event.key === 'Enter') {
- event.preventDefault();
- await handleSendComment();
- return;
- }
- };
-
const timeOptions = {
year: 'numeric',
month: '2-digit',
@@ -295,7 +288,10 @@
use:autoGrowHeight={'5px'}
placeholder={disabled ? 'Comments are disabled' : 'Say something'}
on:input={() => autoGrowHeight(textArea, '5px')}
- on:keypress={handleEnter}
+ use:shortcut={{
+ shortcut: { key: 'Enter' },
+ onShortcut: () => handleSendComment(),
+ }}
class="h-[18px] {disabled
? 'cursor-not-allowed'
: ''} w-full max-h-56 pr-2 items-center overflow-y-auto leading-4 outline-none resize-none bg-gray-200"
diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte
index b1f70cbeb..f9d2f787b 100644
--- a/web/src/lib/components/asset-viewer/asset-viewer.svelte
+++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte
@@ -13,7 +13,7 @@
import { getAssetJobMessage, isSharedLink, handlePromiseError } from '$lib/utils';
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
- import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
+ import { shortcuts } from '$lib/utils/shortcut';
import { SlideshowHistory } from '$lib/utils/slideshow-history';
import {
AssetJobName,
@@ -55,6 +55,7 @@
export let assetStore: AssetStore | null = null;
export let asset: AssetResponseDto;
+ export let preloadAssets: AssetResponseDto[] = [];
export let showNavigation = true;
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
$: isTrashEnabled = $featureFlags.trash;
@@ -103,6 +104,11 @@
$stackAssetsStore = [...$stackAssetsStore, asset].sort(
(a, b) => new Date(b.fileCreatedAt).getTime() - new Date(a.fileCreatedAt).getTime(),
);
+
+ // if its a stack, add the next stack image in addition to the next asset
+ if (asset.stackCount > 1) {
+ preloadAssets.push($stackAssetsStore[1]);
+ }
}
if (!$stackAssetsStore.map((a) => a.id).includes(asset.id)) {
@@ -250,68 +256,9 @@
isShowActivity = !isShowActivity;
};
- const handleKeypress = async (event: KeyboardEvent) => {
- if (shouldIgnoreShortcut(event)) {
- return;
- }
-
- const key = event.key;
- const shiftKey = event.shiftKey;
- const ctrlKey = event.ctrlKey;
-
- if (ctrlKey) {
- return;
- }
-
- switch (key) {
- case 'a':
- case 'A': {
- if (shiftKey) {
- await toggleArchive();
- }
- return;
- }
- case 'ArrowLeft': {
- await navigateAsset('previous');
- return;
- }
- case 'ArrowRight': {
- await navigateAsset('next');
- return;
- }
- case 'd':
- case 'D': {
- if (shiftKey) {
- await downloadFile(asset);
- }
- return;
- }
- case 'Delete': {
- await trashOrDelete(shiftKey);
- return;
- }
- case 'Escape': {
- if (isShowDeleteConfirmation) {
- isShowDeleteConfirmation = false;
- return;
- }
- if (isShowShareModal) {
- isShowShareModal = false;
- return;
- }
- closeViewer();
- return;
- }
- case 'f': {
- await toggleFavorite();
- return;
- }
- case 'i': {
- isShowActivity = false;
- $isShowDetail = !$isShowDetail;
- return;
- }
- }
+ const toggleDetailPanel = () => {
+ isShowActivity = false;
+ $isShowDetail = !$isShowDetail;
};
const handleCloseViewer = () => {
@@ -551,7 +498,19 @@
};
-
+ navigateAsset('previous') },
+ { shortcut: { key: 'ArrowRight' }, onShortcut: () => navigateAsset('next') },
+ { shortcut: { key: 'd', shift: true }, onShortcut: () => downloadFile(asset) },
+ { shortcut: { key: 'Delete' }, onShortcut: () => trashOrDelete(false) },
+ { shortcut: { key: 'Delete', shift: true }, onShortcut: () => trashOrDelete(true) },
+ { shortcut: { key: 'Escape' }, onShortcut: closeViewer },
+ { shortcut: { key: 'f' }, onShortcut: toggleFavorite },
+ { shortcut: { key: 'i' }, onShortcut: toggleDetailPanel },
+ ]}
+/>
+
{:else}
{:else}
-
+
{/if}
{:else}
- {#each $stackAssetsStore as stackedAsset (stackedAsset.id)}
+ {#each $stackAssetsStore as stackedAsset, index (stackedAsset.id)}
(asset = stackedAsset)}
+ onClick={() => {
+ asset = stackedAsset;
+ preloadAssets = index + 1 >= $stackAssetsStore.length ? [] : [$stackAssetsStore[index + 1]];
+ }}
on:mouse-event={(e) => handleStackedAssetMouseEvent(e, stackedAsset)}
readonly
thumbnailSize={stackedAsset.id == asset.id ? 65 : 60}
diff --git a/web/src/lib/components/asset-viewer/detail-panel.svelte b/web/src/lib/components/asset-viewer/detail-panel.svelte
index 334b406bb..318007280 100644
--- a/web/src/lib/components/asset-viewer/detail-panel.svelte
+++ b/web/src/lib/components/asset-viewer/detail-panel.svelte
@@ -41,6 +41,7 @@
import UserAvatar from '../shared-components/user-avatar.svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
+ import { shortcut } from '$lib/utils/shortcut';
export let asset: AssetResponseDto;
export let albums: AlbumResponseDto[] = [];
@@ -105,20 +106,6 @@
closeViewer: void;
}>();
- const handleKeypress = async (event: KeyboardEvent) => {
- if (event.target !== textArea) {
- return;
- }
- const ctrl = event.ctrlKey;
- switch (event.key) {
- case 'Enter': {
- if (ctrl && event.target === textArea) {
- await handleFocusOut();
- }
- }
- }
- };
-
const getMegapixel = (width: number, height: number): number | undefined => {
const megapixel = Math.round((height * width) / 1_000_000);
@@ -180,8 +167,6 @@
}
-
-