diff --git a/i18n/en.json b/i18n/en.json index 210e05459dc8..42965e06a8b8 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -67,6 +67,7 @@ "confirm_reprocess_all_faces": "Are you sure you want to reprocess all faces? This will also clear named people.", "confirm_user_password_reset": "Are you sure you want to reset {user}'s password?", "confirm_user_pin_code_reset": "Are you sure you want to reset {user}'s PIN code?", + "copy_config_to_clipboard_description": "Copy the current system config as a JSON object to the clipboard", "create_job": "Create job", "cron_expression": "Cron expression", "cron_expression_description": "Set the scanning interval using the cron format. For more information please refer to e.g. Crontab Guru", @@ -74,6 +75,8 @@ "disable_login": "Disable login", "duplicate_detection_job_description": "Run machine learning on assets to detect similar images. Relies on Smart Search", "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.", + "export_config_as_json_description": "Download the current system config as a JSON file", + "external_libraries_page_description": "Admin external library page", "external_library_management": "External Library Management", "face_detection": "Face detection", "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 @@ "image_thumbnail_description": "Small thumbnail with stripped metadata, used when viewing groups of photos like the main timeline", "image_thumbnail_quality_description": "Thumbnail quality from 1-100. Higher is better, but produces larger files and can reduce app responsiveness.", "image_thumbnail_title": "Thumbnail Settings", + "import_config_from_json_description": "Import system config by uploading a JSON config file", "job_concurrency": "{job} concurrency", "job_created": "Job created", "job_not_concurrency_safe": "This job is not concurrency-safe.", @@ -110,6 +114,7 @@ "job_status": "Job Status", "jobs_delayed": "{jobCount, plural, other {# delayed}}", "jobs_failed": "{jobCount, plural, other {# failed}}", + "jobs_page_description": "Admin jobs page", "library_created": "Created library: {library}", "library_deleted": "Library deleted", "library_details": "Library details", @@ -182,6 +187,7 @@ "maintenance_start": "Start maintenance mode", "maintenance_start_error": "Failed to start maintenance mode.", "manage_concurrency": "Manage Concurrency", + "manage_concurrency_description": "Navigate to the jobs page to manage job concurrency", "manage_log_settings": "Manage log settings", "map_dark_style": "Dark style", "map_enable_description": "Enable map features", @@ -287,8 +293,10 @@ "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.", "server_settings": "Server Settings", "server_settings_description": "Manage server settings", + "server_stats_page_description": "Admin server statistics page", "server_welcome_message": "Welcome message", "server_welcome_message_description": "A message that is displayed on the login page.", + "settings_page_description": "Admin settings page", "sidecar_job": "Sidecar metadata", "sidecar_job_description": "Discover or synchronize sidecar metadata from the filesystem", "slideshow_duration_description": "Number of seconds to display each image", @@ -407,6 +415,8 @@ "user_restore_scheduled_removal": "Restore user - scheduled removal on {date, date, long}", "user_settings": "User Settings", "user_settings_description": "Manage user settings", + "user_successfully_removed": "User {email} has been successfully removed.", + "users_page_description": "Admin users page", "version_check_enabled_description": "Enable version check", "version_check_implications": "The version check feature relies on periodic communication with github.com", "version_check_settings": "Version Check", @@ -727,6 +737,7 @@ "collapse_all": "Collapse all", "color": "Color", "color_theme": "Color theme", + "command": "Command", "comment_deleted": "Comment deleted", "comment_options": "Comment options", "comments_and_likes": "Comments & likes", @@ -1511,6 +1522,7 @@ "other_variables": "Other variables", "owned": "Owned", "owner": "Owner", + "page": "Page", "partner": "Partner", "partner_can_access": "{partner} can access", "partner_can_access_assets": "All your photos and videos except those in Archived and Deleted", @@ -2071,6 +2083,7 @@ "to_select": "to select", "to_trash": "Trash", "toggle_settings": "Toggle settings", + "toggle_theme_description": "Toggle theme", "total": "Total", "total_usage": "Total usage", "trash": "Trash", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff91f7b31618..3cc8543108cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -717,8 +717,8 @@ importers: specifier: file:../open-api/typescript-sdk version: link:../open-api/typescript-sdk '@immich/ui': - specifier: ^0.49.1 - version: 0.49.1(svelte@5.43.12) + specifier: ^0.49.2 + version: 0.49.2(svelte@5.43.12) '@mapbox/mapbox-gl-rtl-text': specifier: 0.2.3 version: 0.2.3(mapbox-gl@1.13.3) @@ -2983,8 +2983,8 @@ packages: peerDependencies: svelte: ^5.0.0 - '@immich/ui@0.49.1': - resolution: {integrity: sha512-E8x3iLnGRvkso1XeG3qZGPPjX8l8CoKcrTKxDvn59OjhnK0aZDs1Fv+Nq0lyOhSsH6qyV9vjDbLmhLje6D+thg==} + '@immich/ui@0.49.2': + resolution: {integrity: sha512-7Tn/pG5LobXt0FoNICTxQyxjpADRGTy/Yr69Zb/hrAkFxvYUSykK13SPc3rTXiw0rd3ykkNKru8N7kfeCxqHqQ==} peerDependencies: svelte: ^5.0.0 @@ -14708,7 +14708,7 @@ snapshots: dependencies: svelte: 5.43.12 - '@immich/ui@0.49.1(svelte@5.43.12)': + '@immich/ui@0.49.2(svelte@5.43.12)': dependencies: '@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.43.12) '@internationalized/date': 3.10.0 diff --git a/web/package.json b/web/package.json index 9cf1b6b981bc..b7db84916674 100644 --- a/web/package.json +++ b/web/package.json @@ -28,7 +28,7 @@ "@formatjs/icu-messageformat-parser": "^2.9.8", "@immich/justified-layout-wasm": "^0.4.3", "@immich/sdk": "file:../open-api/typescript-sdk", - "@immich/ui": "^0.49.1", + "@immich/ui": "^0.49.2", "@mapbox/mapbox-gl-rtl-text": "0.2.3", "@mdi/js": "^7.4.47", "@photo-sphere-viewer/core": "^5.14.0", diff --git a/web/src/lib/services/library.service.ts b/web/src/lib/services/library.service.ts index 93cf836c8231..8b4d35a5f67c 100644 --- a/web/src/lib/services/library.service.ts +++ b/web/src/lib/services/library.service.ts @@ -23,17 +23,22 @@ import { modalManager, toastManager, type ActionItem } from '@immich/ui'; import { mdiPencilOutline, mdiPlusBoxOutline, mdiSync, mdiTrashCanOutline } from '@mdi/js'; import type { MessageFormatter } from 'svelte-i18n'; -export const getLibrariesActions = ($t: MessageFormatter) => { +export const getLibrariesActions = ($t: MessageFormatter, libraries: LibraryResponseDto[]) => { const ScanAll: ActionItem = { title: $t('scan_all_libraries'), + type: $t('command'), icon: mdiSync, onAction: () => void handleScanAllLibraries(), + shortcuts: { shift: true, key: 'r' }, + $if: () => libraries.length > 0, }; const Create: ActionItem = { title: $t('create_library'), + type: $t('command'), icon: mdiPlusBoxOutline, onAction: () => void handleCreateLibrary(), + shortcuts: { shift: true, key: 'n' }, }; return { ScanAll, Create }; @@ -42,33 +47,41 @@ export const getLibrariesActions = ($t: MessageFormatter) => { export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponseDto) => { const Rename: ActionItem = { icon: mdiPencilOutline, + type: $t('command'), title: $t('rename'), onAction: () => void modalManager.show(LibraryRenameModal, { library }), + shortcuts: { key: 'r' }, }; const Delete: ActionItem = { icon: mdiTrashCanOutline, + type: $t('command'), title: $t('delete'), color: 'danger', onAction: () => void handleDeleteLibrary(library), + shortcuts: { key: 'Backspace' }, }; const AddFolder: ActionItem = { icon: mdiPlusBoxOutline, + type: $t('command'), title: $t('add'), onAction: () => void modalManager.show(LibraryFolderAddModal, { library }), }; const AddExclusionPattern: ActionItem = { icon: mdiPlusBoxOutline, + type: $t('command'), title: $t('add'), onAction: () => void modalManager.show(LibraryExclusionPatternAddModal, { library }), }; const Scan: ActionItem = { icon: mdiSync, + type: $t('command'), title: $t('scan_library'), onAction: () => void handleScanLibrary(library), + shortcuts: { shift: true, key: 'r' }, }; return { Rename, Delete, AddFolder, AddExclusionPattern, Scan }; @@ -77,12 +90,14 @@ export const getLibraryActions = ($t: MessageFormatter, library: LibraryResponse export const getLibraryFolderActions = ($t: MessageFormatter, library: LibraryResponseDto, folder: string) => { const Edit: ActionItem = { icon: mdiPencilOutline, + type: $t('command'), title: $t('edit'), onAction: () => void modalManager.show(LibraryFolderEditModal, { folder, library }), }; const Delete: ActionItem = { icon: mdiTrashCanOutline, + type: $t('command'), title: $t('delete'), onAction: () => void handleDeleteLibraryFolder(library, folder), }; @@ -97,12 +112,14 @@ export const getLibraryExclusionPatternActions = ( ) => { const Edit: ActionItem = { icon: mdiPencilOutline, + type: $t('command'), title: $t('edit'), onAction: () => void modalManager.show(LibraryExclusionPatternEditModal, { exclusionPattern, library }), }; const Delete: ActionItem = { icon: mdiTrashCanOutline, + type: $t('command'), title: $t('delete'), onAction: () => void handleDeleteExclusionPattern(library, exclusionPattern), }; diff --git a/web/src/lib/services/system-config.service.ts b/web/src/lib/services/system-config.service.ts index 62034886b987..ffd0094c722b 100644 --- a/web/src/lib/services/system-config.service.ts +++ b/web/src/lib/services/system-config.service.ts @@ -17,21 +17,33 @@ export const getSystemConfigActions = ( ) => { const CopyToClipboard: ActionItem = { title: $t('copy_to_clipboard'), + description: $t('admin.copy_config_to_clipboard_description'), + type: $t('command'), icon: mdiContentCopy, onAction: () => void handleCopyToClipboard(config), + shortcuts: { shift: true, key: 'c' }, }; const Download: ActionItem = { title: $t('export_as_json'), + description: $t('admin.export_config_as_json_description'), + type: $t('command'), icon: mdiDownload, onAction: () => handleDownloadConfig(config), + shortcuts: [ + { shift: true, key: 's' }, + { shift: true, key: 'd' }, + ], }; const Upload: ActionItem = { title: $t('import_from_json'), + description: $t('admin.import_config_from_json_description'), + type: $t('command'), icon: mdiUpload, $if: () => !featureFlags.configFile, onAction: () => handleUploadConfig(), + shortcuts: { shift: true, key: 'u' }, }; return { CopyToClipboard, Download, Upload }; diff --git a/web/src/lib/services/user-admin.service.ts b/web/src/lib/services/user-admin.service.ts index b8a4c648c17f..7a49f2fbe34e 100644 --- a/web/src/lib/services/user-admin.service.ts +++ b/web/src/lib/services/user-admin.service.ts @@ -34,8 +34,10 @@ import { get } from 'svelte/store'; export const getUserAdminsActions = ($t: MessageFormatter) => { const Create: ActionItem = { title: $t('create_user'), + type: $t('command'), icon: mdiPlusBoxOutline, onAction: () => void modalManager.show(UserCreateModal, {}), + shortcuts: { shift: true, key: 'n' }, }; return { Create }; @@ -45,34 +47,39 @@ export const getUserAdminActions = ($t: MessageFormatter, user: UserAdminRespons const Update: ActionItem = { icon: mdiPencilOutline, title: $t('edit'), - onAction: () => void modalManager.show(UserEditModal, { user }), + onAction: () => modalManager.show(UserEditModal, { user }), }; const Delete: ActionItem = { icon: mdiTrashCanOutline, title: $t('delete'), + type: $t('command'), color: 'danger', $if: () => get(authUser).id !== user.id && !user.deletedAt, - onAction: () => void modalManager.show(UserDeleteConfirmModal, { user }), + onAction: () => modalManager.show(UserDeleteConfirmModal, { user }), + shortcuts: { key: 'Backspace' }, }; const Restore: ActionItem = { icon: mdiDeleteRestore, title: $t('restore'), + type: $t('command'), color: 'primary', $if: () => !!user.deletedAt && user.status === UserStatus.Deleted, - onAction: () => void modalManager.show(UserRestoreConfirmModal, { user }), + onAction: () => modalManager.show(UserRestoreConfirmModal, { user }), }; const ResetPassword: ActionItem = { icon: mdiLockReset, title: $t('reset_password'), + type: $t('command'), $if: () => get(authUser).id !== user.id, onAction: () => void handleResetPasswordUserAdmin(user), }; const ResetPinCode: ActionItem = { icon: mdiLockSmart, + type: $t('command'), title: $t('reset_pin_code'), onAction: () => void handleResetPinCodeUserAdmin(user), }; diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 379b6b00d62f..3d065ab2f17c 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -1,5 +1,5 @@ + {page.data.meta?.title || 'Web'} - Immich diff --git a/web/src/routes/+layout.ts b/web/src/routes/+layout.ts index 2d3bd92d488a..ab4b91f9cc09 100644 --- a/web/src/routes/+layout.ts +++ b/web/src/routes/+layout.ts @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { serverConfigManager } from '$lib/managers/server-config-manager.svelte'; import { maintenanceCreateUrl, maintenanceReturnUrl, maintenanceShouldRedirect } from '$lib/utils/maintenance'; import { init } from '$lib/utils/server'; +import { commandPaletteManager } from '@immich/ui'; import type { LayoutLoad } from './$types'; export const ssr = false; @@ -21,6 +22,8 @@ export const load = (async ({ fetch, url }) => { error = initError; } + commandPaletteManager.enable(); + return { error, meta: { diff --git a/web/src/routes/admin/jobs-status/+page.svelte b/web/src/routes/admin/jobs-status/+page.svelte index 6a3195f44718..1a61ea6b2384 100644 --- a/web/src/routes/admin/jobs-status/+page.svelte +++ b/web/src/routes/admin/jobs-status/+page.svelte @@ -1,4 +1,5 @@ + + {#snippet buttons()} @@ -74,22 +98,10 @@ {/if} - - diff --git a/web/src/routes/admin/library-management/+page.svelte b/web/src/routes/admin/library-management/+page.svelte index 6aa2b3007a97..aef8447d00c3 100644 --- a/web/src/routes/admin/library-management/+page.svelte +++ b/web/src/routes/admin/library-management/+page.svelte @@ -9,7 +9,7 @@ import { locale } from '$lib/stores/preferences.store'; import { getBytesWithUnit } from '$lib/utils/byte-units'; import { getLibrary, getLibraryStatistics, getUserAdmin, type LibraryResponseDto } from '@immich/sdk'; - import { Button } from '@immich/ui'; + import { Button, CommandPaletteContext } from '@immich/ui'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; import type { PageData } from './$types'; @@ -49,7 +49,7 @@ delete owners[id]; }; - const { Create, ScanAll } = $derived(getLibrariesActions($t)); + const { Create, ScanAll } = $derived(getLibrariesActions($t, libraries)); + + {#snippet buttons()}
- {#if libraries.length > 0} - - {/if} +
{/snippet} diff --git a/web/src/routes/admin/library-management/[id]/+page.svelte b/web/src/routes/admin/library-management/[id]/+page.svelte index 32367e78a8b5..db73952b3cc2 100644 --- a/web/src/routes/admin/library-management/[id]/+page.svelte +++ b/web/src/routes/admin/library-management/[id]/+page.svelte @@ -15,7 +15,18 @@ getLibraryFolderActions, } from '$lib/services/library.service'; import { getBytesWithUnit } from '$lib/utils/byte-units'; - import { Card, CardBody, CardHeader, CardTitle, Code, Container, Heading, Icon, modalManager } from '@immich/ui'; + import { + Card, + CardBody, + CardHeader, + CardTitle, + Code, + CommandPaletteContext, + Container, + Heading, + Icon, + modalManager, + } from '@immich/ui'; import { mdiCameraIris, mdiChartPie, mdiFilterMinusOutline, mdiFolderOutline, mdiPlayCircle } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; @@ -39,6 +50,8 @@ onLibraryDelete={({ id }) => id === library.id && goto(AppRoute.ADMIN_LIBRARY_MANAGEMENT)} /> + + + + {#snippet buttons()} diff --git a/web/src/routes/admin/users/+page.svelte b/web/src/routes/admin/users/+page.svelte index ef20a94b86d3..de2c1ef85a2a 100644 --- a/web/src/routes/admin/users/+page.svelte +++ b/web/src/routes/admin/users/+page.svelte @@ -6,7 +6,7 @@ import { locale } from '$lib/stores/preferences.store'; import { getByteUnitString } from '$lib/utils/byte-units'; import { searchUsersAdmin, type UserAdminResponseDto } from '@immich/sdk'; - import { Button, HStack, Icon } from '@immich/ui'; + import { Button, CommandPaletteContext, HStack, Icon } from '@immich/ui'; import { mdiInfinity } from '@mdi/js'; import { t } from 'svelte-i18n'; import type { PageData } from './$types'; @@ -43,6 +43,8 @@ {onUserAdminDeleted} /> + + {#snippet buttons()} diff --git a/web/src/routes/admin/users/[id]/+page.svelte b/web/src/routes/admin/users/[id]/+page.svelte index 5fa7030173b4..a9721c02f288 100644 --- a/web/src/routes/admin/users/[id]/+page.svelte +++ b/web/src/routes/admin/users/[id]/+page.svelte @@ -22,6 +22,7 @@ CardHeader, CardTitle, Code, + CommandPaletteContext, Container, getByteUnitString, Heading, @@ -105,6 +106,8 @@ {onUserAdminDeleted} /> + +