Skip to content

Commit 276a2b8

Browse files
feat: command palette
1 parent c935ae4 commit 276a2b8

File tree

9 files changed

+206
-43
lines changed

9 files changed

+206
-43
lines changed

i18n/en.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,16 @@
6767
"confirm_reprocess_all_faces": "Are you sure you want to reprocess all faces? This will also clear named people.",
6868
"confirm_user_password_reset": "Are you sure you want to reset {user}'s password?",
6969
"confirm_user_pin_code_reset": "Are you sure you want to reset {user}'s PIN code?",
70+
"copy_config_to_clipboard_description": "Copy the current system config as a JSON object to the clipboard",
7071
"create_job": "Create job",
7172
"cron_expression": "Cron expression",
7273
"cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. <link>Crontab Guru</link>",
7374
"cron_expression_presets": "Cron expression presets",
7475
"disable_login": "Disable login",
7576
"duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search",
7677
"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.",
78+
"export_config_as_json_description": "Download the current system config as a JSON file",
79+
"external_libraries_page_description": "Admin external library page",
7780
"external_library_management": "External Library Management",
7881
"face_detection": "Face detection",
7982
"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.",
@@ -102,6 +105,7 @@
102105
"image_thumbnail_description": "Small thumbnail with stripped metadata, used when viewing groups of photos like the main timeline",
103106
"image_thumbnail_quality_description": "Thumbnail quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness.",
104107
"image_thumbnail_title": "Thumbnail Settings",
108+
"import_config_from_json_description": "Import system config by uploading a JSON config file",
105109
"job_concurrency": "{job} concurrency",
106110
"job_created": "Job created",
107111
"job_not_concurrency_safe": "This job is not concurrency-safe.",
@@ -110,6 +114,7 @@
110114
"job_status": "Job Status",
111115
"jobs_delayed": "{jobCount, plural, other {# delayed}}",
112116
"jobs_failed": "{jobCount, plural, other {# failed}}",
117+
"jobs_page_description": "Admin jobs page",
113118
"library_created": "Created library: {library}",
114119
"library_deleted": "Library deleted",
115120
"library_import_path_description": "Specify a folder to import. This folder, including subfolders, will be scanned for images and videos.",
@@ -174,6 +179,7 @@
174179
"machine_learning_smart_search_enabled_description": "If disabled, images will not be encoded for smart search.",
175180
"machine_learning_url_description": "The URL of the machine learning server. If more than one URL is provided, each server will be attempted one-at-a-time until one responds successfully, in order from first to last. Servers that don't respond will be temporarily ignored until they come back online.",
176181
"manage_concurrency": "Manage Concurrency",
182+
"manage_concurrency_description": "Navigate to the jobs page to manage job concurrency",
177183
"manage_log_settings": "Manage log settings",
178184
"map_dark_style": "Dark style",
179185
"map_enable_description": "Enable map features",
@@ -279,8 +285,10 @@
279285
"server_public_users_description": "All users (name and email) are listed when adding a user to shared albums. When disabled, the user list will only be available to admin users.",
280286
"server_settings": "Server Settings",
281287
"server_settings_description": "Manage server settings",
288+
"server_stats_page_description": "Admin server statistics page",
282289
"server_welcome_message": "Welcome message",
283290
"server_welcome_message_description": "A message that is displayed on the login page.",
291+
"settings_page_description": "Admin settings page",
284292
"sidecar_job": "Sidecar metadata",
285293
"sidecar_job_description": "Discover or synchronize sidecar metadata from the filesystem",
286294
"slideshow_duration_description": "Number of seconds to display each image",
@@ -400,6 +408,7 @@
400408
"user_settings": "User Settings",
401409
"user_settings_description": "Manage user settings",
402410
"user_successfully_removed": "User {email} has been successfully removed.",
411+
"users_page_description": "Admin users page",
403412
"version_check_enabled_description": "Enable version check",
404413
"version_check_implications": "The version check feature relies on periodic communication with github.com",
405414
"version_check_settings": "Version Check",
@@ -718,6 +727,7 @@
718727
"collapse_all": "Collapse all",
719728
"color": "Color",
720729
"color_theme": "Color theme",
730+
"command": "Command",
721731
"comment_deleted": "Comment deleted",
722732
"comment_options": "Comment options",
723733
"comments_and_likes": "Comments & likes",
@@ -1485,6 +1495,7 @@
14851495
"other_variables": "Other variables",
14861496
"owned": "Owned",
14871497
"owner": "Owner",
1498+
"page": "Page",
14881499
"partner": "Partner",
14891500
"partner_can_access": "{partner} can access",
14901501
"partner_can_access_assets": "All your photos and videos except those in Archived and Deleted",
@@ -2038,6 +2049,7 @@
20382049
"to_select": "to select",
20392050
"to_trash": "Trash",
20402051
"toggle_settings": "Toggle settings",
2052+
"toggle_theme_description": "Toggle theme",
20412053
"total": "Total",
20422054
"total_usage": "Total usage",
20432055
"trash": "Trash",

pnpm-lock.yaml

Lines changed: 9 additions & 9 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.40.2",
31+
"@immich/ui": "^0.42.3",
3232
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
3333
"@mdi/js": "^7.4.47",
3434
"@photo-sphere-viewer/core": "^5.11.5",

web/src/routes/+layout.svelte

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
88
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
99
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
10+
import { AppRoute } from '$lib/constants';
1011
import { eventManager } from '$lib/managers/event-manager.svelte';
12+
import { themeManager } from '$lib/managers/theme-manager.svelte';
1113
import VersionAnnouncementModal from '$lib/modals/VersionAnnouncementModal.svelte';
1214
import { serverConfig } from '$lib/stores/server-config.store';
1315
import { user } from '$lib/stores/user.store';
@@ -20,7 +22,8 @@
2022
import { copyToClipboard, getReleaseType } from '$lib/utils';
2123
import { isAssetViewerRoute } from '$lib/utils/navigation';
2224
import type { ServerVersionResponseDto } from '@immich/sdk';
23-
import { modalManager, setTranslations } from '@immich/ui';
25+
import { CommandPaletteContext, modalManager, setTranslations, type CommandItem } from '@immich/ui';
26+
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync, mdiThemeLightDark } from '@mdi/js';
2427
import { onMount, type Snippet } from 'svelte';
2528
import { t } from 'svelte-i18n';
2629
import { run } from 'svelte/legacy';
@@ -103,6 +106,57 @@
103106
}
104107
};
105108
109+
const userCommands: CommandItem[] = [
110+
{
111+
title: $t('theme'),
112+
description: $t('toggle_theme_description'),
113+
type: $t('command'),
114+
icon: mdiThemeLightDark,
115+
action: () => themeManager.toggleTheme(),
116+
shortcuts: { shift: true, key: 't' },
117+
},
118+
];
119+
120+
const adminCommands: CommandItem[] = [
121+
{
122+
title: $t('users'),
123+
description: $t('admin.users_page_description'),
124+
type: $t('page'),
125+
icon: mdiAccountMultipleOutline,
126+
href: AppRoute.ADMIN_USERS,
127+
},
128+
{
129+
title: $t('jobs'),
130+
description: $t('admin.jobs_page_description'),
131+
type: $t('page'),
132+
icon: mdiSync,
133+
href: AppRoute.ADMIN_JOBS,
134+
},
135+
{
136+
title: $t('settings'),
137+
description: $t('admin.jobs_page_description'),
138+
type: $t('page'),
139+
icon: mdiCog,
140+
href: AppRoute.ADMIN_SETTINGS,
141+
},
142+
{
143+
title: $t('external_libraries'),
144+
description: $t('admin.external_libraries_page_description'),
145+
type: $t('page'),
146+
icon: mdiBookshelf,
147+
href: AppRoute.ADMIN_LIBRARY_MANAGEMENT,
148+
},
149+
{
150+
title: $t('server_stats'),
151+
description: $t('admin.server_stats_page_description'),
152+
type: $t('page'),
153+
icon: mdiServer,
154+
href: AppRoute.ADMIN_STATS,
155+
},
156+
];
157+
158+
const commands = $derived([...userCommands, ...($user?.isAdmin ? adminCommands : [])]);
159+
106160
$effect(() => void handleRelease($release));
107161
</script>
108162

@@ -146,6 +200,8 @@
146200
}}
147201
/>
148202

203+
<CommandPaletteContext {commands} global />
204+
149205
{#if page.data.error}
150206
<ErrorLayout error={page.data.error}></ErrorLayout>
151207
{:else}

web/src/routes/+layout.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { init } from '$lib/utils/server';
2+
import { commandPaletteManager } from '@immich/ui';
23
import type { LayoutLoad } from './$types';
34

45
export const ssr = false;
@@ -12,6 +13,8 @@ export const load = (async ({ fetch }) => {
1213
error = initError;
1314
}
1415

16+
commandPaletteManager.enable();
17+
1518
return {
1619
error,
1720
meta: {

web/src/routes/admin/jobs-status/+page.svelte

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
type AllJobStatusResponseDto,
1313
type JobName,
1414
} from '@immich/sdk';
15-
import { Button, HStack, modalManager, Text } from '@immich/ui';
15+
import { Button, CommandPaletteContext, HStack, modalManager, Text, type CommandItem } from '@immich/ui';
1616
import { mdiCog, mdiPlay, mdiPlus } from '@mdi/js';
1717
import { onDestroy, onMount } from 'svelte';
1818
import { t } from 'svelte-i18n';
@@ -46,6 +46,27 @@
4646
}
4747
};
4848
49+
const handleCreateJob = () => modalManager.show(JobCreateModal);
50+
51+
const jobConcurrencyLink = `${AppRoute.ADMIN_SETTINGS}?isOpen=job`;
52+
53+
const commands: CommandItem[] = [
54+
{
55+
title: $t('admin.create_job'),
56+
type: $t('command'),
57+
icon: mdiPlus,
58+
action: () => void handleCreateJob(),
59+
shortcuts: { shift: true, key: 'n' },
60+
},
61+
{
62+
title: $t('admin.manage_concurrency'),
63+
description: $t('admin.manage_concurrency_description'),
64+
type: $t('page'),
65+
icon: mdiCog,
66+
href: jobConcurrencyLink,
67+
},
68+
];
69+
4970
onMount(async () => {
5071
while (running) {
5172
jobs = await getAllJobsStatus();
@@ -58,6 +79,8 @@
5879
});
5980
</script>
6081

82+
<CommandPaletteContext {commands} />
83+
6184
<AdminPageLayout title={data.meta.title}>
6285
{#snippet buttons()}
6386
<HStack gap={0}>
@@ -74,22 +97,10 @@
7497
</Text>
7598
</Button>
7699
{/if}
77-
<Button
78-
leadingIcon={mdiPlus}
79-
onclick={() => modalManager.show(JobCreateModal, {})}
80-
size="small"
81-
variant="ghost"
82-
color="secondary"
83-
>
100+
<Button leadingIcon={mdiPlus} onclick={handleCreateJob} size="small" variant="ghost" color="secondary">
84101
<Text class="hidden md:block">{$t('admin.create_job')}</Text>
85102
</Button>
86-
<Button
87-
leadingIcon={mdiCog}
88-
href="{AppRoute.ADMIN_SETTINGS}?isOpen=job"
89-
size="small"
90-
variant="ghost"
91-
color="secondary"
92-
>
103+
<Button leadingIcon={mdiCog} href={jobConcurrencyLink} size="small" variant="ghost" color="secondary">
93104
<Text class="hidden md:block">{$t('admin.manage_concurrency')}</Text>
94105
</Button>
95106
</HStack>

web/src/routes/admin/library-management/+page.svelte

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@
2626
type LibraryStatsResponseDto,
2727
type UserResponseDto,
2828
} from '@immich/sdk';
29-
import { Button, LoadingSpinner, modalManager, Text, toastManager } from '@immich/ui';
29+
import {
30+
Button,
31+
CommandPaletteContext,
32+
LoadingSpinner,
33+
modalManager,
34+
Text,
35+
toastManager,
36+
type CommandItem,
37+
} from '@immich/ui';
3038
import { mdiDotsVertical, mdiPlusBoxOutline, mdiSync } from '@mdi/js';
3139
import { onMount } from 'svelte';
3240
import { t } from 'svelte-i18n';
@@ -191,7 +199,7 @@
191199
}
192200
};
193201
194-
const onCreateNewLibraryClicked = async () => {
202+
const handleCreateNewLibrary = async () => {
195203
const result = await modalManager.show(LibraryUserPickerModal);
196204
if (result) {
197205
await handleCreate(result);
@@ -238,8 +246,31 @@
238246
await readLibraryList();
239247
}
240248
};
249+
250+
const commands: CommandItem[] = $derived([
251+
...(libraries.length > 0
252+
? [
253+
{
254+
title: $t('scan_all_libraries'),
255+
type: $t('command'),
256+
icon: mdiSync,
257+
action: () => handleScanAll(),
258+
shortcuts: { shift: true, key: 'r' },
259+
},
260+
]
261+
: []),
262+
{
263+
title: $t('create_library'),
264+
type: $t('command'),
265+
icon: mdiPlusBoxOutline,
266+
action: () => handleCreateNewLibrary(),
267+
shortcuts: { shift: true, key: 'n' },
268+
},
269+
]);
241270
</script>
242271

272+
<CommandPaletteContext {commands} />
273+
243274
<AdminPageLayout title={data.meta.title}>
244275
{#snippet buttons()}
245276
<div class="flex justify-end gap-2">
@@ -250,7 +281,7 @@
250281
{/if}
251282
<Button
252283
leadingIcon={mdiPlusBoxOutline}
253-
onclick={onCreateNewLibraryClicked}
284+
onclick={handleCreateNewLibrary}
254285
size="small"
255286
variant="ghost"
256287
color="secondary"
@@ -360,7 +391,7 @@
360391

361392
<!-- Empty message -->
362393
{:else}
363-
<EmptyPlaceholder text={$t('no_libraries_message')} onClick={onCreateNewLibraryClicked} />
394+
<EmptyPlaceholder text={$t('no_libraries_message')} onClick={handleCreateNewLibrary} />
364395
{/if}
365396
</div>
366397
</section>

0 commit comments

Comments
 (0)