diff --git a/i18n/en.json b/i18n/en.json index 210e05459dc87..42965e06a8b87 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 ff91f7b316187..3cc8543108cd5 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 9cf1b6b981bca..b7db849166745 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 93cf836c82315..8b4d35a5f67ca 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 62034886b9872..ffd0094c722bc 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 b8a4c648c17f0..7a49f2fbe34ec 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 379b6b00d62ff..3d065ab2f17c3 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 2d3bd92d488af..ab4b91f9cc09e 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 6a3195f44718a..1a61ea6b23842 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 6aa2b3007a972..aef8447d00c36 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 32367e78a8b52..db73952b3cc25 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 ef20a94b86d3c..de2c1ef85a2ae 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 5fa7030173b43..a9721c02f288f 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} /> + +