feat(frontend): add the command palette

This commit is contained in:
Elias Schneider
2026-10-27 23:32:53 +01:00
parent c3d7390be1
commit d6efcc4e95
17 changed files with 619 additions and 30 deletions
+24 -2
View File
@@ -2,7 +2,8 @@
import { onNavigate } from '$app/navigation';
import FormattedMessage from '#lib/components/formatted-message.svelte';
import AppSidebar from '#lib/components/shell/app-sidebar.svelte';
import { navGroups } from '#lib/components/shell/nav-items.ts';
import CommandPalette from '#lib/components/shell/command-palette.svelte';
import { checkApiRows, navGroups } from '#lib/components/shell/nav-items.ts';
import PhoneNav from '#lib/components/shell/phone-nav.svelte';
import * as Alert from '#lib/components/ui/alert/index.ts';
import * as Tooltip from '#lib/components/ui/tooltip/index.ts';
@@ -27,8 +28,28 @@
const { versionInformation, sqliteStorageWarning, user } = data;
const isAdmin = $derived(!!($userStore?.isAdmin || user?.isAdmin));
const verificationPending = $derived(isEmailVerificationPending(user, $appConfigStore));
const showUpdate = $derived(isAdmin && versionInformation.isUpToDate === false);
// A writable $derived(data.apiRows) would lose a re-check on the next navigation, because the root load re-runs every time and replaces data
let recheckedApiRows = $state<typeof data.apiRows>();
const apiRows = $derived(recheckedApiRows ?? data.apiRows);
// The page you are on keeps its row, and the palette always lists both (docs/design-system.md, PO11)
const groups = $derived(
navGroups({ isAdmin, verificationPending: isEmailVerificationPending(user, $appConfigStore) })
navGroups({
isAdmin,
verificationPending,
showApis:
apiRows.apis ||
showAdvanced.current ||
page.url.pathname.startsWith('/settings/admin/apis'),
showApiKeys:
apiRows.apiKeys ||
showAdvanced.current ||
page.url.pathname.startsWith('/settings/admin/api-keys')
})
);
const paletteGroups = $derived(
navGroups({ isAdmin, verificationPending, showApis: true, showApiKeys: true })
);
const collapsed = new PersistedState('sidebar-collapsed', false);
@@ -108,4 +129,5 @@
</main>
</div>
</div>
<CommandPalette groups={paletteGroups} isAdmin={isAdmin && !verificationPending} />
</Tooltip.Provider>
@@ -17,6 +17,7 @@
import type { ApiKey } from '#lib/types/api-key.type.ts';
import type { ListRequestOptions } from '#lib/types/list-request.type.ts';
import { axiosErrorToast } from '#lib/utils/error-util.ts';
import { openOnCreateParam } from '#lib/utils/create-param-util.ts';
import { LucideArrowUpRight, LucideBan, LucidePlus, LucideRefreshCcw } from '@lucide/svelte';
import { toast } from 'svelte-sonner';
import ApiKeyCreateSheet from './api-key-create-sheet.svelte';
@@ -30,8 +31,9 @@
let tableRef = $state<AdvancedTable<ApiKey>>();
let createOpen = $state(false);
openOnCreateParam(() => (createOpen = true));
let renewing = $state<ApiKey | null>(null);
let empty = $state(false);
let empty = $state(data.empty);
const formatDate = (date: string) =>
new Date(date).toLocaleDateString(getLocale(), { dateStyle: 'medium' });
@@ -14,6 +14,7 @@
} from '#lib/types/advanced-table.type.ts';
import type { Api } from '#lib/types/api.type.ts';
import type { ListRequestOptions } from '#lib/types/list-request.type.ts';
import { openOnCreateParam } from '#lib/utils/create-param-util.ts';
import { toast } from 'svelte-sonner';
import {
LucideArrowRight,
@@ -32,7 +33,8 @@
let tableRef = $state<AdvancedTable<Api>>();
let createOpen = $state(false);
let empty = $state(false);
openOnCreateParam(() => (createOpen = true));
let empty = $state(data.empty);
const href = (api: Api) => `/settings/admin/apis/${api.id}`;
@@ -18,6 +18,7 @@
import type { ListRequestOptions } from '#lib/types/list-request.type.ts';
import type { OidcClientWithAllowedGroups } from '#lib/types/oidc.type.ts';
import { encodeClientIdParam } from '#lib/utils/client-id-util.ts';
import { openOnCreateParam } from '#lib/utils/create-param-util.ts';
import { LucideArrowRight, LucideArrowUpRight, LucidePlus } from '@lucide/svelte';
import OidcClientCreateSheet from './oidc-client-create-sheet.svelte';
import {
@@ -33,7 +34,8 @@
const connectDocsUrl = 'https://pocket-id.org/docs/client-examples';
let tableRef = $state<AdvancedTable<OidcClientWithAllowedGroups>>();
let createOpen = $state(false);
let empty = $state(false);
openOnCreateParam(() => (createOpen = true));
let empty = $state(data.empty);
async function fetchClients(options: ListRequestOptions) {
const res = await oidcService.listClients(options);
@@ -16,6 +16,7 @@
} from '#lib/types/advanced-table.type.ts';
import type { ListRequestOptions } from '#lib/types/list-request.type.ts';
import type { UserGroupMinimal } from '#lib/types/user-group.type.ts';
import { openOnCreateParam } from '#lib/utils/create-param-util.ts';
import { LucideArrowRight, LucidePlus, LucideTrash2 } from '@lucide/svelte';
import { confirmDeleteGroup } from './user-group-actions';
import UserGroupCreateSheet from './user-group-create-sheet.svelte';
@@ -25,7 +26,8 @@
const userGroupService = new UserGroupService();
let tableRef = $state<AdvancedTable<UserGroupMinimal>>();
let createOpen = $state(false);
let empty = $state(false);
openOnCreateParam(() => (createOpen = true));
let empty = $state(data.empty);
const href = (group: UserGroupMinimal) => `/settings/admin/user-groups/${group.id}`;
const groupSlug = (group: UserGroupMinimal) =>
@@ -36,6 +36,7 @@
} from '@lucide/svelte';
import { SvelteMap } from 'svelte/reactivity';
import OidcClientAvatar from '#lib/components/oidc-client-avatar.svelte';
import { openOnCreateParam } from '#lib/utils/create-param-util.ts';
import PickerDialog, { groupsConfirmLabel } from '../user-groups/picker-dialog.svelte';
import type { PageProps } from './$types';
import LoginCodeDialog from './login-code-dialog.svelte';
@@ -59,6 +60,7 @@
let shownCount = $state(0);
let selectedIds = $state<string[]>([]);
let createOpen = $state(false);
openOnCreateParam(() => (createOpen = true));
let tokensOpen = $state(false);
let tokensStartWithCreate = $state(false);
let groupPickerOpen = $state(false);