Skip to content

Commit 31f2c7b

Browse files
authored
feat: header context menu (#24374)
1 parent ba6687d commit 31f2c7b

File tree

22 files changed

+208
-230
lines changed

22 files changed

+208
-230
lines changed

i18n/en.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
"exclusion_pattern_description": "Exclusion patterns lets you ignore files and folders when scanning your library. This is useful if you have folders that contain files you don't want to import, such as RAW files.",
7979
"export_config_as_json_description": "Download the current system config as a JSON file",
8080
"external_libraries_page_description": "Admin external library page",
81-
"external_library_management": "External Library Management",
8281
"face_detection": "Face detection",
8382
"face_detection_description": "Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. \"Refresh\" (re-)processes all assets. \"Reset\" additionally clears all current face data. \"Missing\" queues assets that haven't been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.",
8483
"facial_recognition_job_description": "Group detected faces into people. This step runs after Face Detection is complete. \"Reset\" (re-)clusters all faces. \"Missing\" queues faces that don't have a person assigned.",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@formatjs/icu-messageformat-parser": "^2.9.8",
2929
"@immich/justified-layout-wasm": "^0.4.3",
3030
"@immich/sdk": "file:../open-api/typescript-sdk",
31-
"@immich/ui": "^0.49.2",
31+
"@immich/ui": "^0.50.0",
3232
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
3333
"@mdi/js": "^7.4.47",
3434
"@photo-sphere-viewer/core": "^5.14.0",
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script lang="ts">
2+
import type { HeaderButtonActionItem } from '$lib/types';
3+
import { Button } from '@immich/ui';
4+
5+
type Props = {
6+
action: HeaderButtonActionItem;
7+
};
8+
9+
const { action }: Props = $props();
10+
const { title, icon, color = 'secondary', onAction } = $derived(action);
11+
</script>
12+
13+
{#if action.$if?.() ?? true}
14+
<Button
15+
variant="ghost"
16+
size="small"
17+
{color}
18+
leadingIcon={icon}
19+
onclick={() => onAction(action)}
20+
title={action.data?.title}
21+
>
22+
{title}
23+
</Button>
24+
{/if}

web/src/lib/components/HeaderButton.svelte

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
11
<script lang="ts">
22
import PageContent from '$lib/components/layouts/PageContent.svelte';
3-
import TitleLayout from '$lib/components/layouts/TitleLayout.svelte';
43
import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
54
import AdminSidebar from '$lib/sidebars/AdminSidebar.svelte';
65
import { sidebarStore } from '$lib/stores/sidebar.svelte';
7-
import { AppShell, AppShellHeader, AppShellSidebar, Scrollable, type BreadcrumbItem } from '@immich/ui';
6+
import type { HeaderButtonActionItem } from '$lib/types';
7+
import {
8+
AppShell,
9+
AppShellHeader,
10+
AppShellSidebar,
11+
Breadcrumbs,
12+
Button,
13+
ContextMenuButton,
14+
HStack,
15+
MenuItemType,
16+
Scrollable,
17+
isMenuItemType,
18+
type BreadcrumbItem,
19+
} from '@immich/ui';
20+
import { mdiSlashForward } from '@mdi/js';
821
import type { Snippet } from 'svelte';
22+
import { t } from 'svelte-i18n';
923
1024
type Props = {
1125
breadcrumbs: BreadcrumbItem[];
12-
buttons?: Snippet;
26+
actions?: Array<HeaderButtonActionItem | MenuItemType>;
1327
children?: Snippet;
1428
};
1529
16-
let { breadcrumbs, buttons, children }: Props = $props();
30+
let { breadcrumbs, actions = [], children }: Props = $props();
1731
</script>
1832

1933
<AppShell>
@@ -24,11 +38,37 @@
2438
<AdminSidebar />
2539
</AppShellSidebar>
2640

27-
<TitleLayout {breadcrumbs} {buttons}>
41+
<div class="h-full flex flex-col">
42+
<div class="flex h-16 w-full justify-between items-center border-b py-2 px-4 md:px-2">
43+
<Breadcrumbs items={breadcrumbs} separator={mdiSlashForward} />
44+
45+
{#if actions.length > 0}
46+
<div class="hidden md:block">
47+
<HStack gap={0}>
48+
{#each actions as action, i (i)}
49+
{#if !isMenuItemType(action) && (action.$if?.() ?? true)}
50+
<Button
51+
variant="ghost"
52+
size="small"
53+
color={action.color ?? 'secondary'}
54+
leadingIcon={action.icon}
55+
onclick={() => action.onAction(action)}
56+
title={action.data?.title}
57+
>
58+
{action.title}
59+
</Button>
60+
{/if}
61+
{/each}
62+
</HStack>
63+
</div>
64+
65+
<ContextMenuButton aria-label={$t('open')} items={actions} class="md:hidden" />
66+
{/if}
67+
</div>
2868
<Scrollable class="grow">
2969
<PageContent>
3070
{@render children?.()}
3171
</PageContent>
3272
</Scrollable>
33-
</TitleLayout>
73+
</div>
3474
</AppShell>

web/src/lib/components/layouts/TitleLayout.svelte

Lines changed: 0 additions & 21 deletions
This file was deleted.

web/src/lib/services/library.service.ts

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResp
2828
title: $t('scan_all_libraries'),
2929
type: $t('command'),
3030
icon: mdiSync,
31-
onAction: () => void handleScanAllLibraries(),
31+
onAction: () => handleScanAllLibraries(),
3232
shortcuts: { shift: true, key: 'r' },
3333
$if: () => libraries.length > 0,
3434
};
@@ -37,7 +37,7 @@ export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResp
3737
title: $t('create_library'),
3838
type: $t('command'),
3939
icon: mdiPlusBoxOutline,
40-
onAction: () => void handleCreateLibrary(),
40+
onAction: () => handleCreateLibrary(),
4141
shortcuts: { shift: true, key: 'n' },
4242
};
4343

@@ -49,7 +49,7 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse
4949
icon: mdiPencilOutline,
5050
type: $t('command'),
5151
title: $t('rename'),
52-
onAction: () => void modalManager.show(LibraryRenameModal, { library }),
52+
onAction: () => modalManager.show(LibraryRenameModal, { library }),
5353
shortcuts: { key: 'r' },
5454
};
5555

@@ -58,29 +58,29 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse
5858
type: $t('command'),
5959
title: $t('delete'),
6060
color: 'danger',
61-
onAction: () => void handleDeleteLibrary(library),
61+
onAction: () => handleDeleteLibrary(library),
6262
shortcuts: { key: 'Backspace' },
6363
};
6464

6565
const AddFolder: ActionItem = {
6666
icon: mdiPlusBoxOutline,
6767
type: $t('command'),
6868
title: $t('add'),
69-
onAction: () => void modalManager.show(LibraryFolderAddModal, { library }),
69+
onAction: () => modalManager.show(LibraryFolderAddModal, { library }),
7070
};
7171

7272
const AddExclusionPattern: ActionItem = {
7373
icon: mdiPlusBoxOutline,
7474
type: $t('command'),
7575
title: $t('add'),
76-
onAction: () => void modalManager.show(LibraryExclusionPatternAddModal, { library }),
76+
onAction: () => modalManager.show(LibraryExclusionPatternAddModal, { library }),
7777
};
7878

7979
const Scan: ActionItem = {
8080
icon: mdiSync,
8181
type: $t('command'),
8282
title: $t('scan_library'),
83-
onAction: () => void handleScanLibrary(library),
83+
onAction: () => handleScanLibrary(library),
8484
shortcuts: { shift: true, key: 'r' },
8585
};
8686

@@ -92,14 +92,14 @@ export const getLibraryFolderActions = ($t: MessageFormatter, library: LibraryRe
9292
icon: mdiPencilOutline,
9393
type: $t('command'),
9494
title: $t('edit'),
95-
onAction: () => void modalManager.show(LibraryFolderEditModal, { folder, library }),
95+
onAction: () => modalManager.show(LibraryFolderEditModal, { folder, library }),
9696
};
9797

9898
const Delete: ActionItem = {
9999
icon: mdiTrashCanOutline,
100100
type: $t('command'),
101101
title: $t('delete'),
102-
onAction: () => void handleDeleteLibraryFolder(library, folder),
102+
onAction: () => handleDeleteLibraryFolder(library, folder),
103103
};
104104

105105
return { Edit, Delete };
@@ -114,14 +114,14 @@ export const getLibraryExclusionPatternActions = (
114114
icon: mdiPencilOutline,
115115
type: $t('command'),
116116
title: $t('edit'),
117-
onAction: () => void modalManager.show(LibraryExclusionPatternEditModal, { exclusionPattern, library }),
117+
onAction: () => modalManager.show(LibraryExclusionPatternEditModal, { exclusionPattern, library }),
118118
};
119119

120120
const Delete: ActionItem = {
121121
icon: mdiTrashCanOutline,
122122
type: $t('command'),
123123
title: $t('delete'),
124-
onAction: () => void handleDeleteExclusionPattern(library, exclusionPattern),
124+
onAction: () => handleDeleteExclusionPattern(library, exclusionPattern),
125125
};
126126

127127
return { Edit, Delete };
@@ -273,7 +273,7 @@ const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: st
273273
});
274274

275275
if (!confirmed) {
276-
return false;
276+
return;
277277
}
278278

279279
try {
@@ -285,10 +285,7 @@ const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: st
285285
toastManager.success($t('admin.library_updated'));
286286
} catch (error) {
287287
handleError(error, $t('errors.unable_to_update_library'));
288-
return false;
289288
}
290-
291-
return true;
292289
};
293290

294291
export const handleAddLibraryExclusionPattern = async (library: LibraryResponseDto, exclusionPattern: string) => {
@@ -345,9 +342,8 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi
345342
const $t = await getFormatter();
346343

347344
const confirmed = await modalManager.showDialog({ prompt: $t('admin.library_remove_exclusion_pattern_prompt') });
348-
349345
if (!confirmed) {
350-
return false;
346+
return;
351347
}
352348

353349
try {
@@ -361,8 +357,5 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi
361357
toastManager.success($t('admin.library_updated'));
362358
} catch (error) {
363359
handleError(error, $t('errors.unable_to_update_library'));
364-
return false;
365360
}
366-
367-
return true;
368361
};

0 commit comments

Comments
 (0)