@k2b/cloud 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +22 -11
- package/scripts/README.md +10 -0
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +20 -35
- package/scripts/preload.ts +16 -12
- package/scripts/sync-recovery-smoke.ts +22 -6
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/build-metadata.ts +8 -1
- package/src/_internal/capabilities.ts +131 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +80 -37
- package/src/_internal/heartbeat.ts +4 -0
- package/src/_internal/help-catalog.ts +27 -172
- package/src/_internal/help.ts +13 -76
- package/src/_internal/page-responses.ts +2 -0
- package/src/_internal/postgres-application-name.ts +30 -0
- package/src/_internal/process-identity.ts +15 -0
- package/src/_internal/process-sync.ts +1 -1
- package/src/_internal/readiness.ts +4 -1
- package/src/_internal/registry-validation.ts +29 -25
- package/src/_internal/registry.ts +2 -53
- package/src/_internal/runtime-context.ts +5 -16
- package/src/_internal/server-timing.ts +13 -0
- package/src/_internal/status-preserving-ssr.ts +18 -6
- package/src/_internal/web-vitals-asset.ts +10 -0
- package/src/access/PermissionEditor.tsx +18 -151
- package/src/access/PrincipalPicker.tsx +110 -0
- package/src/access/ResourceApiKeys.tsx +1 -1
- package/src/access/ui.ts +1 -0
- package/src/ai/admin.ts +14 -13
- package/src/ai/assistant-models.ts +12 -1
- package/src/ai/audio-format.ts +34 -0
- package/src/ai/audio-tool.ts +90 -0
- package/src/ai/browser-code-contracts.ts +201 -0
- package/src/ai/browser.ts +4 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +229 -90
- package/src/ai/chat/builtin-tools.tsx +2 -4
- package/src/ai/chat/capability-result.ts +15 -0
- package/src/ai/chat/capability-table.tsx +2 -10
- package/src/ai/chat/composer-adapter.ts +83 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +25 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +31 -47
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +21 -2
- package/src/ai/chat/tool-groups.ts +58 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +14 -10
- package/src/ai/chat-tasks.ts +297 -43
- package/src/ai/client/controller.ts +165 -39
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +29 -11
- package/src/ai/client/transport.ts +6 -0
- package/src/ai/code-capability-routes.ts +105 -0
- package/src/ai/code-capability-transport.ts +39 -0
- package/src/ai/code-execution.ts +53 -0
- package/src/ai/code-mode-skill.ts +118 -0
- package/src/ai/code-runtime-tools.ts +179 -0
- package/src/ai/code-source-contracts.ts +270 -0
- package/src/ai/code-source-tools.ts +188 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +99 -3
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +49 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +189 -33
- package/src/ai/fetch-file-tool.ts +2 -2
- package/src/ai/file-content-version.ts +27 -0
- package/src/ai/file-context.ts +2 -1
- package/src/ai/file-media-type.ts +12 -0
- package/src/ai/file-reference-contracts.ts +15 -0
- package/src/ai/file-tools.ts +8 -1
- package/src/ai/files-store.ts +212 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +42 -10
- package/src/ai/http.ts +26 -8
- package/src/ai/index.ts +26 -3
- package/src/ai/inference-calls.ts +197 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/live.ts +1 -1
- package/src/ai/markdown-pdf-tool.ts +2 -2
- package/src/ai/memories.ts +30 -27
- package/src/ai/memory-learning-runs.ts +1 -1
- package/src/ai/memory-learning.ts +64 -32
- package/src/ai/memory-tool.ts +1 -5
- package/src/ai/memory-workflow-evidence.ts +9 -4
- package/src/ai/message-queue.ts +178 -0
- package/src/ai/migrate.ts +189 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +75 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +18 -43
- package/src/ai/projects.ts +33 -14
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +192 -0
- package/src/ai/quota-report.ts +152 -0
- package/src/ai/quotas-migrate.ts +45 -0
- package/src/ai/quotas.ts +168 -0
- package/src/ai/routes.ts +241 -11
- package/src/ai/run-timeout.ts +12 -0
- package/src/ai/runtime-tools.ts +7 -5
- package/src/ai/runtime.ts +74 -17
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +4 -5
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +58 -0
- package/src/ai/skill-seeds.ts +114 -20
- package/src/ai/skill-tool.ts +21 -5
- package/src/ai/skills-routes.ts +49 -4
- package/src/ai/skills.ts +358 -163
- package/src/ai/store.ts +416 -201
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +12 -1
- package/src/ai/system-prompt.ts +14 -2
- package/src/ai/task-contracts.ts +84 -0
- package/src/ai/task-execution.ts +92 -0
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +32 -0
- package/src/ai/todo-tool.ts +19 -0
- package/src/ai/tool-audit.ts +30 -40
- package/src/ai/tools.ts +6 -0
- package/src/ai/transcription.ts +202 -0
- package/src/ai/turn-timing.ts +114 -0
- package/src/ai/types.ts +58 -10
- package/src/ai/ui.tsx +3 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +60 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +80 -0
- package/src/api/admin-ai-skills.ts +48 -5
- package/src/api/admin-ai-usage.ts +2 -2
- package/src/api/admin-core-settings.ts +25 -0
- package/src/api/admin-identity.ts +91 -51
- package/src/api/admin-linux-identities.ts +10 -4
- package/src/api/admin-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/app-approval.ts +2 -2
- package/src/api/auth.ts +1 -1
- package/src/api/capabilities.ts +324 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +8 -2
- package/src/api/mcp.ts +45 -34
- package/src/api/me-web-vitals.ts +35 -0
- package/src/api/me.ts +2 -0
- package/src/api/search/schemas.ts +26 -15
- package/src/api/search.bench.ts +1 -1
- package/src/api/search.ts +54 -17
- package/src/browser/CloudResourceSearch.tsx +640 -702
- package/src/browser/app-approval-vault.ts +19 -7
- package/src/browser/command-bridge.ts +109 -0
- package/src/browser/command-shortcuts.ts +76 -0
- package/src/browser/commands.ts +67 -0
- package/src/browser/navigation-search.ts +48 -0
- package/src/browser/notifications.ts +1 -1
- package/src/browser/resource-picker.tsx +5 -18
- package/src/browser/resource-search-dialog.ts +9 -0
- package/src/browser/resource-search-input.ts +46 -0
- package/src/browser/resource-search-messages.ts +76 -40
- package/src/browser/resource-search.ts +7 -0
- package/src/browser/search-bridge.ts +102 -0
- package/src/browser/search-commands.ts +59 -0
- package/src/browser/search.ts +13 -0
- package/src/browser/spotlight-position.ts +208 -0
- package/src/browser/testing.ts +6 -0
- package/src/browser/web-vitals.ts +25 -0
- package/src/capabilities/claims.ts +179 -0
- package/src/capabilities/client.ts +34 -0
- package/src/capabilities/command-link.ts +16 -0
- package/src/capabilities/executions.ts +298 -0
- package/src/capabilities/migrate.ts +81 -0
- package/src/capabilities/server.ts +116 -35
- package/src/capabilities/stream-body.ts +16 -0
- package/src/capabilities/streams.ts +28 -0
- package/src/cli/admin/ai-quotas.ts +161 -0
- package/src/cli/admin/ai-skills.ts +64 -0
- package/src/cli/admin/ai-usage.ts +53 -30
- package/src/cli/admin/app-credentials.ts +77 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +10 -1
- package/src/cli/admin/instance.ts +2 -2
- package/src/cli/admin/linux.ts +4 -2
- package/src/cli/admin/sync.ts +1 -1
- package/src/cli/capabilities.ts +42 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/define-env.ts +97 -0
- package/src/config/env.ts +147 -51
- package/src/config/index.ts +3 -1
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +75 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +5 -2
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +5 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +18 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/index.ts +1 -0
- package/src/desktop/solid.tsx +1 -1
- package/src/index.ts +8 -6
- package/src/server/index.ts +3 -0
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/middleware/route-template.ts +9 -3
- package/src/server/middleware/runtime.ts +2 -1
- package/src/server/middleware/settings.ts +17 -11
- package/src/server/ratelimit.ts +1 -1
- package/src/server/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +3 -1
- package/src/server/services/index.ts +1 -0
- package/src/services/account-category-policy.ts +9 -0
- package/src/services/account-lifecycle/audit.ts +1 -1
- package/src/services/accounts/app.ts +12 -2
- package/src/services/accounts/email-write.ts +23 -0
- package/src/services/accounts/groups.ts +15 -4
- package/src/services/accounts/identities.ts +249 -0
- package/src/services/accounts/identity-reconciliation.ts +87 -0
- package/src/services/accounts/ipa-data.ts +2 -2
- package/src/services/accounts/local-groups.ts +2 -2
- package/src/services/accounts/notification-sender.ts +12 -2
- package/src/services/accounts/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- package/src/services/app-approval.ts +11 -10
- package/src/services/audit/index.ts +1 -1
- package/src/services/cache-fill.ts +35 -0
- package/src/services/help/index.ts +109 -0
- package/src/services/help/maintenance.ts +25 -0
- package/src/services/help/store.ts +180 -0
- package/src/services/help/types.ts +46 -0
- package/src/services/identity/invocation-actor.ts +2 -2
- package/src/services/identity/key-config.ts +8 -5
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/identity/runtime-config.ts +6 -5
- package/src/services/index.ts +15 -0
- package/src/services/ipa/posix.ts +1 -1
- package/src/services/ipa/sync.ts +1 -1
- package/src/services/ipa/users.ts +268 -221
- package/src/services/legal-consent.ts +1 -1
- package/src/services/logging/index.ts +15 -13
- package/src/services/logging/trace.ts +2 -1
- package/src/services/logging/web-vitals.ts +81 -0
- package/src/services/mandates/index.ts +21 -4
- package/src/services/mandates/policy.ts +165 -2
- package/src/services/notifications/batches.ts +125 -57
- package/src/services/notifications/browser.ts +12 -0
- package/src/services/notifications/index.ts +0 -2
- package/src/services/notifications/platform.ts +0 -4
- package/src/services/oauth-tokens.ts +3 -3
- package/src/services/pdf/gotenberg.ts +131 -72
- package/src/services/pdf/index.ts +11 -8
- package/src/services/providers/local/users.ts +25 -16
- package/src/services/public-http.ts +101 -0
- package/src/services/rail-shortcuts.ts +95 -0
- package/src/services/rail-snapshot.ts +53 -0
- package/src/services/request-cache-redis.ts +29 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/app.ts +5 -1
- package/src/services/settings/core-settings.ts +26 -35
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/index.ts +3 -3
- package/src/services/settings/store.ts +58 -84
- package/src/services/webauthn.ts +1 -1
- package/src/shared/ai-costs.ts +42 -0
- package/src/shared/ai-platform-prompt.ts +1 -1
- package/src/shared/ai-quotas.ts +136 -0
- package/src/shared/ai-usage.ts +6 -16
- package/src/shared/app-presentation.ts +21 -15
- package/src/shared/capability-messages.ts +14 -0
- package/src/shared/help.ts +6 -42
- package/src/shared/index.ts +3 -0
- package/src/shared/markdown/extensions/info-blocks.ts +1 -1
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +30 -16
- package/src/ssr/AppLaunchpad.island.tsx +33 -136
- package/src/ssr/AppLaunchpadPanel.tsx +181 -0
- package/src/ssr/BrowserPushRegistration.island.tsx +12 -0
- package/src/ssr/GlobalSearchDialog.tsx +167 -20
- package/src/ssr/GlobalSearchTrigger.island.tsx +33 -22
- package/src/ssr/Layout.tsx +17 -6
- package/src/ssr/LayoutBreadcrumbs.island.tsx +3 -5
- package/src/ssr/LayoutFooter.tsx +1 -1
- package/src/ssr/LayoutHeader.tsx +46 -14
- package/src/ssr/LayoutHelp.tsx +22 -23
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- package/src/ssr/MinimalLayout.tsx +1 -1
- package/src/ssr/MobileNavigation.tsx +71 -0
- package/src/ssr/MobileProfileActions.tsx +49 -0
- package/src/ssr/ProfilePreferences.island.tsx +3 -36
- package/src/ssr/RailApps.island.tsx +2 -2
- package/src/ssr/RailEditor.tsx +17 -2
- package/src/ssr/WorkspaceNavigation.island.tsx +7 -0
- package/src/ssr/WorkspaceNavigationProvider.tsx +16 -0
- package/src/ssr/admin-navigation.ts +11 -1
- package/src/ssr/help.ts +29 -0
- package/src/ssr/islands/SearchBar.tsx +67 -0
- package/src/ssr/islands/index.ts +3 -1
- package/src/ssr/layout-context.ts +2 -2
- package/src/ssr/layout-help-search.ts +15 -0
- package/src/ssr/mobile-menu-history.ts +70 -0
- package/src/ssr/platform-messages.ts +16 -8
- package/src/ssr/profile-actions.ts +42 -0
- package/src/ssr/rail-context.ts +8 -4
- package/src/ssr/rail-messages.ts +2 -0
- package/src/ssr/rail-navigation.ts +3 -3
- package/src/ssr/workspace-navigation.ts +75 -0
- package/src/styles/effects.css +6 -6
- package/src/styles/global.css +54 -0
- package/src/styles/input.css +43 -41
- package/src/styles/resource-search.css +505 -0
- package/src/styles/utilities-feedback.css +13 -0
- package/src/styles/utilities-layout.css +1 -5
- package/src/styles/utilities-navigation.css +3 -39
- package/src/types/ambient.d.ts +5 -0
- package/src/workflows/ai/index.ts +2 -2
- package/src/workflows/ai/runtime.ts +19 -90
- package/src/workflows/ai/store.ts +2 -0
- package/src/workflows/ai/types.ts +6 -82
- package/src/workflows/ai-actions.ts +16 -13
- package/src/workflows/definition.ts +10 -5
- package/src/workflows/store/actions.ts +44 -20
- package/src/workflows/store/index.ts +4 -2
- package/src/workflows/store/runs.ts +36 -0
- package/src/workflows/store/worker-pool.ts +55 -0
- package/src/workflows/store/worker-runtime.ts +90 -0
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/server/services/freeipa/test-certificates.ts +0 -44
- package/src/services/notifications/live.ts +0 -50
- package/src/services/session/test-fixture.ts +0 -18
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/islands/SearchBar.island.tsx +0 -80
package/src/ssr/LayoutHeader.tsx
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { ButtonLink } from "@k2b/ui";
|
|
2
|
+
import type { NavigationSearchItem } from "../browser/navigation-search";
|
|
2
3
|
import type { CloudTheme } from "../shared/theme";
|
|
3
4
|
import AppLaunchpad, { type AppLaunchpadApp } from "./AppLaunchpad.island";
|
|
4
5
|
import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
|
|
5
6
|
import GlobalSearchTrigger from "./GlobalSearchTrigger.island";
|
|
6
|
-
import HotkeysHelpRail from "./HotkeysHelpRail.island";
|
|
7
7
|
import LayoutBreadcrumbs from "./LayoutBreadcrumbs.island";
|
|
8
|
-
import
|
|
8
|
+
import LayoutHelpTrigger from "./LayoutHelpTrigger.island";
|
|
9
9
|
import LayoutPreferences from "./LayoutPreferences.island";
|
|
10
|
+
import type { LayoutBreadcrumb } from "./layout-runtime";
|
|
10
11
|
import ProfilePreferences from "./ProfilePreferences.island";
|
|
11
12
|
|
|
12
13
|
type LayoutHeaderProps = {
|
|
13
14
|
accent?: string;
|
|
15
|
+
appLabel?: string;
|
|
14
16
|
authenticated: boolean;
|
|
15
17
|
breadcrumbs: LayoutBreadcrumb[];
|
|
16
18
|
homeLabel: string;
|
|
@@ -20,6 +22,7 @@ type LayoutHeaderProps = {
|
|
|
20
22
|
profileAvatarSrc?: string;
|
|
21
23
|
profileName: string;
|
|
22
24
|
searchHelpApps: GlobalSearchHelpApp[];
|
|
25
|
+
searchLinks?: NavigationSearchItem[];
|
|
23
26
|
signInLabel: string;
|
|
24
27
|
theme: CloudTheme;
|
|
25
28
|
};
|
|
@@ -27,7 +30,7 @@ type LayoutHeaderProps = {
|
|
|
27
30
|
export default function LayoutHeader(props: LayoutHeaderProps) {
|
|
28
31
|
return (
|
|
29
32
|
<header
|
|
30
|
-
class="layout-header paper flex min-h-[2.875rem] shrink-0 items-center justify-between px-2 py-1.5
|
|
33
|
+
class="layout-header paper flex min-h-[2.875rem] shrink-0 items-center justify-between px-2 py-1.5 lg:px-3 lg:py-2"
|
|
31
34
|
style="box-shadow: var(--ui-shadow-surface)"
|
|
32
35
|
>
|
|
33
36
|
<div class="flex min-w-0 items-center gap-2">
|
|
@@ -35,39 +38,68 @@ export default function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
35
38
|
href="/"
|
|
36
39
|
class={
|
|
37
40
|
props.authenticated
|
|
38
|
-
? "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-dimmed transition-colors hover:bg-zinc-100 hover:text-secondary
|
|
41
|
+
? "inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-lg text-dimmed transition-colors hover:bg-zinc-100 hover:text-secondary lg:hidden dark:hover:bg-zinc-800"
|
|
39
42
|
: "flex shrink-0 items-center"
|
|
40
43
|
}
|
|
41
44
|
aria-label={props.homeLabel}
|
|
42
45
|
>
|
|
43
46
|
<img src="/branding/logo" alt="" class={props.authenticated ? "h-4 w-4" : "h-6 w-6"} />
|
|
44
47
|
</a>
|
|
45
|
-
<div class="hidden min-w-0 items-center
|
|
48
|
+
<div class="hidden min-w-0 items-center lg:flex">
|
|
46
49
|
<LayoutBreadcrumbs breadcrumbs={props.breadcrumbs} />
|
|
47
50
|
</div>
|
|
48
|
-
<div class="flex min-w-0 items-center
|
|
49
|
-
<
|
|
51
|
+
<div class="flex min-w-0 items-center lg:hidden">
|
|
52
|
+
<span class="truncate font-semibold">{props.appLabel ?? props.breadcrumbs.at(-1)?.title}</span>
|
|
50
53
|
</div>
|
|
51
54
|
</div>
|
|
52
55
|
<div class="flex shrink-0 items-center gap-1">
|
|
53
|
-
<div class="flex items-center gap-1
|
|
54
|
-
<
|
|
56
|
+
<div class="flex items-center gap-1 lg:hidden">
|
|
57
|
+
<LayoutHelpTrigger
|
|
55
58
|
variant="header"
|
|
56
|
-
|
|
59
|
+
registerCommand={!props.authenticated}
|
|
57
60
|
searchHelpApps={props.searchHelpApps}
|
|
58
61
|
accent={props.accent}
|
|
59
62
|
/>
|
|
60
|
-
|
|
63
|
+
<GlobalSearchTrigger
|
|
64
|
+
variant={props.authenticated ? "header" : "host"}
|
|
65
|
+
registerCommand
|
|
66
|
+
searchLinks={props.searchLinks}
|
|
67
|
+
searchResources={props.authenticated}
|
|
68
|
+
/>
|
|
61
69
|
</div>
|
|
62
70
|
{props.authenticated ? (
|
|
63
71
|
<>
|
|
64
|
-
<div class="
|
|
65
|
-
<AppLaunchpad
|
|
72
|
+
<div class="lg:hidden">
|
|
73
|
+
<AppLaunchpad
|
|
74
|
+
profile={props.authenticated ? { name: props.profileName, theme: props.theme } : undefined}
|
|
75
|
+
apps={props.launchpadApps}
|
|
76
|
+
legalLinks={props.legalLinks}
|
|
77
|
+
variant="header"
|
|
78
|
+
label={props.openAppsLabel}
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="hidden lg:block">
|
|
82
|
+
<ProfilePreferences
|
|
83
|
+
avatarSrc={props.profileAvatarSrc}
|
|
84
|
+
initialTheme={props.theme}
|
|
85
|
+
name={props.profileName}
|
|
86
|
+
placement="header"
|
|
87
|
+
/>
|
|
66
88
|
</div>
|
|
67
|
-
<ProfilePreferences avatarSrc={props.profileAvatarSrc} initialTheme={props.theme} name={props.profileName} placement="header" />
|
|
68
89
|
</>
|
|
69
90
|
) : (
|
|
70
91
|
<>
|
|
92
|
+
{props.appLabel && (
|
|
93
|
+
<div class="lg:hidden">
|
|
94
|
+
<AppLaunchpad
|
|
95
|
+
profile={props.authenticated ? { name: props.profileName, theme: props.theme } : undefined}
|
|
96
|
+
apps={props.launchpadApps}
|
|
97
|
+
legalLinks={props.legalLinks}
|
|
98
|
+
variant="header"
|
|
99
|
+
label={props.openAppsLabel}
|
|
100
|
+
/>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
71
103
|
<LayoutPreferences initialTheme={props.theme} position="bottom-left" />
|
|
72
104
|
<ButtonLink href="/auth/login" size="sm" variant="primary">
|
|
73
105
|
<i class="ti ti-login" aria-hidden="true" />
|
package/src/ssr/LayoutHelp.tsx
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { clipboard, hotkeys } from "@k2b/stdlib/solid";
|
|
2
|
-
import { Button, IconButton, IconButtonLink, MarkdownView, NoticeCard, Placeholder, prompts, ScrollArea, useLocale } from "@k2b/ui";
|
|
3
1
|
import type { HelpDocumentManifest, HelpDocumentPayload, HelpSearchPayload } from "@k2b/cloud/shared";
|
|
2
|
+
import { clipboard } from "@k2b/stdlib/solid";
|
|
3
|
+
import { Button, IconButton, IconButtonLink, MarkdownView, NoticeCard, Placeholder, prompts, ScrollArea, useLocale } from "@k2b/ui";
|
|
4
4
|
import { createEffect, createMemo, createSignal, For, type JSX, onCleanup, onMount, Show } from "solid-js";
|
|
5
|
+
import { COMMANDS_CHANGED, contextCommandsWithShortcuts, shortcutLabel } from "../browser/command-shortcuts";
|
|
5
6
|
import { appAccentStyle } from "./app-appearance";
|
|
6
7
|
import { type GlobalSearchHelpApp, openGlobalSearchHelpDialog } from "./GlobalSearchHelpDialog";
|
|
7
8
|
import { helpMessages } from "./help-messages";
|
|
8
9
|
import { formatHelpBundleMarkdown, formatHelpDocumentMarkdown } from "./layout-help-markdown";
|
|
9
10
|
import { adjacentHelpDocuments, focusHelpArticleHeading, resetHelpArticleScroll } from "./layout-help-navigation";
|
|
11
|
+
import { searchHelpTopics } from "./layout-help-search";
|
|
10
12
|
import { layoutHelpTopicHref } from "./layout-help-url";
|
|
11
13
|
|
|
12
14
|
type HelpTopicBase = {
|
|
@@ -106,7 +108,13 @@ export function LayoutHelpDocuments(props: LayoutHelpDocumentsProps) {
|
|
|
106
108
|
const Shortcuts = (props: { openSearchHelp: () => void }) => {
|
|
107
109
|
const locale = useLocale();
|
|
108
110
|
const t = () => helpMessages.resolve([locale()]).t;
|
|
109
|
-
const
|
|
111
|
+
const [commands, setCommands] = createSignal(contextCommandsWithShortcuts());
|
|
112
|
+
onMount(() => {
|
|
113
|
+
const update = () => setCommands(contextCommandsWithShortcuts());
|
|
114
|
+
window.addEventListener(COMMANDS_CHANGED, update);
|
|
115
|
+
onCleanup(() => window.removeEventListener(COMMANDS_CHANGED, update));
|
|
116
|
+
});
|
|
117
|
+
const entries = createMemo(() => commands().filter((command) => command.shortcut));
|
|
110
118
|
return (
|
|
111
119
|
<div class="flex flex-col gap-3">
|
|
112
120
|
<p class="text-sm leading-relaxed text-dimmed">{t().shortcutsIntro}</p>
|
|
@@ -118,20 +126,14 @@ const Shortcuts = (props: { openSearchHelp: () => void }) => {
|
|
|
118
126
|
{(entry) => (
|
|
119
127
|
<div class="flex items-start justify-between gap-4 rounded-[var(--ui-radius-surface)] bg-[var(--ui-surface-subtle)] px-3 py-2.5">
|
|
120
128
|
<div class="min-w-0">
|
|
121
|
-
<p class="text-sm font-medium text-primary">{entry.
|
|
122
|
-
<Show when={entry.
|
|
123
|
-
<p class="mt-0.5 text-xs text-dimmed">{entry.
|
|
129
|
+
<p class="text-sm font-medium text-primary">{entry.title}</p>
|
|
130
|
+
<Show when={entry.description}>
|
|
131
|
+
<p class="mt-0.5 text-xs text-dimmed">{entry.description}</p>
|
|
124
132
|
</Show>
|
|
125
133
|
</div>
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<kbd class="rounded bg-[var(--ui-surface-raised)] px-1.5 py-1 text-[11px] ring-1 ring-black/10 dark:ring-white/10">
|
|
130
|
-
{part.key}
|
|
131
|
-
</kbd>
|
|
132
|
-
)}
|
|
133
|
-
</For>
|
|
134
|
-
</div>
|
|
134
|
+
<kbd class="shrink-0 rounded bg-[var(--ui-surface-raised)] px-1.5 py-1 text-[11px] ring-1 ring-black/10 dark:ring-white/10">
|
|
135
|
+
{shortcutLabel(entry.shortcut!)}
|
|
136
|
+
</kbd>
|
|
135
137
|
</div>
|
|
136
138
|
)}
|
|
137
139
|
</For>
|
|
@@ -161,7 +163,7 @@ const HelpShell = (props: {
|
|
|
161
163
|
const [loading, setLoading] = createSignal(props.session.view === "article");
|
|
162
164
|
const [loadError, setLoadError] = createSignal<string | null>(null);
|
|
163
165
|
const [loadAttempt, setLoadAttempt] = createSignal(0);
|
|
164
|
-
const [
|
|
166
|
+
const [remoteIds, setRemoteIds] = createSignal<readonly string[] | null>(null);
|
|
165
167
|
const [searching, setSearching] = createSignal(false);
|
|
166
168
|
const [copyingAll, setCopyingAll] = createSignal(false);
|
|
167
169
|
const [copyAllError, setCopyAllError] = createSignal<string | null>(null);
|
|
@@ -234,10 +236,7 @@ const HelpShell = (props: {
|
|
|
234
236
|
const results = createMemo(() => {
|
|
235
237
|
const value = normalizedQuery();
|
|
236
238
|
if (!value) return topics();
|
|
237
|
-
|
|
238
|
-
return topics().filter(
|
|
239
|
-
(topic) => [topic.title, topic.description].some((part) => part?.toLocaleLowerCase().includes(value)) || matches.has(topic.id),
|
|
240
|
-
);
|
|
239
|
+
return searchHelpTopics(topics(), value, remoteIds());
|
|
241
240
|
});
|
|
242
241
|
|
|
243
242
|
createEffect(() => {
|
|
@@ -250,7 +249,7 @@ const HelpShell = (props: {
|
|
|
250
249
|
),
|
|
251
250
|
];
|
|
252
251
|
const version = ++searchVersion;
|
|
253
|
-
|
|
252
|
+
setRemoteIds(null);
|
|
254
253
|
setSearching(false);
|
|
255
254
|
if (!value || urls.length === 0) return;
|
|
256
255
|
|
|
@@ -269,7 +268,7 @@ const HelpShell = (props: {
|
|
|
269
268
|
}),
|
|
270
269
|
)
|
|
271
270
|
.then((groups) => {
|
|
272
|
-
if (version === searchVersion)
|
|
271
|
+
if (version === searchVersion) setRemoteIds([...new Set(groups.flat())]);
|
|
273
272
|
})
|
|
274
273
|
.catch((error) => {
|
|
275
274
|
if (error instanceof DOMException && error.name === "AbortError") return;
|
|
@@ -445,7 +444,7 @@ const HelpShell = (props: {
|
|
|
445
444
|
};
|
|
446
445
|
const showHub = () => {
|
|
447
446
|
setQuery("");
|
|
448
|
-
|
|
447
|
+
setRemoteIds(null);
|
|
449
448
|
setView("hub");
|
|
450
449
|
};
|
|
451
450
|
const modalTitle = createMemo(() => {
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { hotkeys } from "@k2b/stdlib/solid";
|
|
2
1
|
import { IconButton, Tooltip, useLocale } from "@k2b/ui";
|
|
2
|
+
import { createEffect, onCleanup } from "solid-js";
|
|
3
|
+
import { registerContextAwareCommand } from "../browser/commands";
|
|
3
4
|
import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
|
|
4
5
|
import { openLayoutHelpDialog } from "./LayoutHelp";
|
|
5
6
|
import { platformMessages } from "./platform-messages";
|
|
6
7
|
|
|
7
|
-
type
|
|
8
|
+
type LayoutHelpTriggerProps = {
|
|
8
9
|
variant: "header" | "rail";
|
|
9
10
|
class?: string;
|
|
10
|
-
|
|
11
|
+
registerCommand?: boolean;
|
|
11
12
|
searchHelpApps?: GlobalSearchHelpApp[];
|
|
12
13
|
accent?: string;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
/** Opens end-user help from either the desktop rail or the compact header. */
|
|
16
|
-
export default function
|
|
17
|
+
export default function LayoutHelpTrigger(props: LayoutHelpTriggerProps) {
|
|
17
18
|
const locale = useLocale();
|
|
18
19
|
const t = () => platformMessages.resolve([locale()]).t;
|
|
19
20
|
const searchHelpApps = props.searchHelpApps ?? [];
|
|
@@ -22,14 +23,19 @@ export default function HotkeysHelpRail(props: HotkeysHelpTriggerProps) {
|
|
|
22
23
|
openLayoutHelpDialog(searchHelpApps, props.accent);
|
|
23
24
|
};
|
|
24
25
|
|
|
25
|
-
if (props.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
if (props.registerCommand) {
|
|
27
|
+
createEffect(() =>
|
|
28
|
+
onCleanup(
|
|
29
|
+
registerContextAwareCommand({
|
|
30
|
+
id: "cloud.help",
|
|
31
|
+
title: t().openShortcutHelp,
|
|
32
|
+
description: t().shortcutHelpDescription,
|
|
33
|
+
icon: "ti ti-help-circle",
|
|
34
|
+
shortcut: "shift+/",
|
|
35
|
+
action: openHelp,
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
if (props.variant === "rail")
|
package/src/ssr/LayoutRail.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { NavigationSearchItem } from "../browser/navigation-search";
|
|
2
|
+
import type { RailSnapshot } from "../contracts/rail-preferences";
|
|
2
3
|
import type { CloudTheme } from "../shared/theme";
|
|
3
4
|
import AppLaunchpad, { type AppLaunchpadApp } from "./AppLaunchpad.island";
|
|
4
5
|
import type { GlobalSearchHelpApp } from "./GlobalSearchHelpDialog";
|
|
5
6
|
import GlobalSearchTrigger from "./GlobalSearchTrigger.island";
|
|
6
|
-
import
|
|
7
|
+
import LayoutHelpTrigger from "./LayoutHelpTrigger.island";
|
|
7
8
|
import ProfilePreferences from "./ProfilePreferences.island";
|
|
8
9
|
import RailApps from "./RailApps.island";
|
|
9
10
|
import type { RailApp } from "./rail-navigation";
|
|
@@ -17,11 +18,12 @@ type LayoutRailProps = {
|
|
|
17
18
|
legalLinks: Array<{ label: string; href: string; icon?: string }>;
|
|
18
19
|
openAppsLabel: string;
|
|
19
20
|
apps: RailApp[];
|
|
20
|
-
railSettings:
|
|
21
|
+
railSettings: RailSnapshot;
|
|
21
22
|
currentUrl: string;
|
|
22
23
|
profileAvatarSrc?: string;
|
|
23
24
|
profileName: string;
|
|
24
25
|
searchHelpApps: GlobalSearchHelpApp[];
|
|
26
|
+
searchLinks?: NavigationSearchItem[];
|
|
25
27
|
theme: CloudTheme;
|
|
26
28
|
};
|
|
27
29
|
|
|
@@ -30,14 +32,14 @@ const jsonScript = (value: unknown): string => JSON.stringify(value).replace(/</
|
|
|
30
32
|
export default function LayoutRail(props: LayoutRailProps) {
|
|
31
33
|
return (
|
|
32
34
|
<>
|
|
33
|
-
<AppLaunchpad apps={props.launchpadApps} legalLinks={props.legalLinks} />
|
|
35
|
+
<AppLaunchpad profile={{ name: props.profileName, theme: props.theme }} apps={props.launchpadApps} legalLinks={props.legalLinks} />
|
|
34
36
|
<script id="cloud-app-launchpad-data" type="application/json">
|
|
35
|
-
{jsonScript({ apps: props.launchpadApps, legalLinks: props.legalLinks })}
|
|
37
|
+
{jsonScript({ apps: props.launchpadApps, legalLinks: props.legalLinks, profile: { name: props.profileName, theme: props.theme } })}
|
|
36
38
|
</script>
|
|
37
39
|
<script id="cloud-rail-data" type="application/json">
|
|
38
40
|
{jsonScript({ apps: props.apps, settings: props.railSettings })}
|
|
39
41
|
</script>
|
|
40
|
-
<aside class="layout-rail hidden w-10 shrink-0 flex-col
|
|
42
|
+
<aside class="layout-rail hidden w-10 shrink-0 flex-col lg:flex">
|
|
41
43
|
<div class="layout-rail-logo flex h-[2.875rem] shrink-0 items-center justify-center">
|
|
42
44
|
<a href="/" aria-label={props.homeLabel}>
|
|
43
45
|
<img src="/branding/logo" alt="Logo" class="h-5 w-5" />
|
|
@@ -45,10 +47,16 @@ export default function LayoutRail(props: LayoutRailProps) {
|
|
|
45
47
|
</div>
|
|
46
48
|
<nav class="layout-rail-navigation flex min-h-0 flex-1 flex-col items-center gap-1" aria-label={props.appsLabel}>
|
|
47
49
|
<RailApps apps={props.apps} settings={props.railSettings} currentUrl={props.currentUrl} />
|
|
48
|
-
<AppLaunchpad
|
|
50
|
+
<AppLaunchpad
|
|
51
|
+
profile={{ name: props.profileName, theme: props.theme }}
|
|
52
|
+
apps={props.launchpadApps}
|
|
53
|
+
legalLinks={props.legalLinks}
|
|
54
|
+
variant="rail"
|
|
55
|
+
label={props.openAppsLabel}
|
|
56
|
+
/>
|
|
49
57
|
<div class="mt-auto shrink-0 flex flex-col items-center gap-1">
|
|
50
|
-
<GlobalSearchTrigger variant="rail"
|
|
51
|
-
<
|
|
58
|
+
<GlobalSearchTrigger variant="rail" searchLinks={props.searchLinks} />
|
|
59
|
+
<LayoutHelpTrigger variant="rail" registerCommand searchHelpApps={props.searchHelpApps} accent={props.accent} />
|
|
52
60
|
<ProfilePreferences avatarSrc={props.profileAvatarSrc} initialTheme={props.theme} name={props.profileName} placement="rail" />
|
|
53
61
|
</div>
|
|
54
62
|
</nav>
|
|
@@ -2,8 +2,8 @@ import { LocaleProvider } from "@k2b/ui";
|
|
|
2
2
|
import type { JSX } from "solid-js/jsx-runtime";
|
|
3
3
|
import { getLocale } from "../server/locale";
|
|
4
4
|
import { readThemeFromCookieHeader } from "../shared/theme";
|
|
5
|
-
import type { MinimalLayoutContext } from "./layout-context";
|
|
6
5
|
import LayoutPreferences from "./LayoutPreferences.island";
|
|
6
|
+
import type { MinimalLayoutContext } from "./layout-context";
|
|
7
7
|
import TimezoneCookie from "./TimezoneCookie.island";
|
|
8
8
|
|
|
9
9
|
export type MinimalLayoutPreferencePosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BottomSheet, Navigation, SegmentedControl, useLocale } from "@k2b/ui";
|
|
2
|
+
import { createSignal, onCleanup, Show } from "solid-js";
|
|
3
|
+
import { type AppLaunchpadContext, AppLaunchpadPanel } from "./AppLaunchpadPanel";
|
|
4
|
+
import { openMobileMenu } from "./mobile-menu-history";
|
|
5
|
+
import { platformMessages } from "./platform-messages";
|
|
6
|
+
import { observeWorkspaceNavigation, readWorkspaceNavigation } from "./workspace-navigation";
|
|
7
|
+
|
|
8
|
+
export function openCloudMobileMenu(apps: AppLaunchpadContext) {
|
|
9
|
+
return openMobileMenu((beforeSelect) => (close, context) => (
|
|
10
|
+
<MobileNavigation apps={apps} beforeSelect={beforeSelect} close={close} dismiss={context.requestDismiss} />
|
|
11
|
+
));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function MobileNavigation(props: {
|
|
15
|
+
apps: AppLaunchpadContext;
|
|
16
|
+
beforeSelect: () => Promise<boolean>;
|
|
17
|
+
close: () => void;
|
|
18
|
+
dismiss: () => Promise<void>;
|
|
19
|
+
}) {
|
|
20
|
+
const locale = useLocale();
|
|
21
|
+
const t = () => platformMessages.resolve([locale()]).t;
|
|
22
|
+
const [workspace, setWorkspace] = createSignal(readWorkspaceNavigation());
|
|
23
|
+
const [tab, setTab] = createSignal<"app" | "apps">("app");
|
|
24
|
+
// A new wrapper signal version propagates in-place updates of the owner snapshot.
|
|
25
|
+
onCleanup(
|
|
26
|
+
observeWorkspaceNavigation(() =>
|
|
27
|
+
setWorkspace(() => {
|
|
28
|
+
const next = readWorkspaceNavigation();
|
|
29
|
+
return next ? { ...next } : undefined;
|
|
30
|
+
}),
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
return (
|
|
34
|
+
<BottomSheet onDismiss={props.dismiss}>
|
|
35
|
+
<BottomSheet.Header
|
|
36
|
+
title={workspace()?.label || t().apps}
|
|
37
|
+
close={() => void props.dismiss()}
|
|
38
|
+
actions={
|
|
39
|
+
<Show when={workspace()} fallback={<span class="font-semibold">{t().allApps}</span>}>
|
|
40
|
+
<SegmentedControl
|
|
41
|
+
class="cloud-mobile-menu__switch"
|
|
42
|
+
size="md"
|
|
43
|
+
ariaLabel={t().navigation}
|
|
44
|
+
value={tab}
|
|
45
|
+
onValueChange={setTab}
|
|
46
|
+
options={[
|
|
47
|
+
{ value: "app", label: workspace()?.label || t().appMenu },
|
|
48
|
+
{ value: "apps", label: t().allApps },
|
|
49
|
+
]}
|
|
50
|
+
/>
|
|
51
|
+
</Show>
|
|
52
|
+
}
|
|
53
|
+
/>
|
|
54
|
+
<BottomSheet.Body>
|
|
55
|
+
<Show
|
|
56
|
+
when={workspace() && tab() === "app"}
|
|
57
|
+
fallback={<AppLaunchpadPanel surface="sheet" {...props.apps} close={props.close} beforeSelect={props.beforeSelect} />}
|
|
58
|
+
>
|
|
59
|
+
<Navigation
|
|
60
|
+
navigation={workspace()!.navigation}
|
|
61
|
+
label={workspace()!.label || t().appMenu}
|
|
62
|
+
beforeSelect={async () => {
|
|
63
|
+
const owner = workspace()?.owner;
|
|
64
|
+
return (await props.beforeSelect()) && Boolean(owner?.isConnected && readWorkspaceNavigation()?.owner === owner);
|
|
65
|
+
}}
|
|
66
|
+
/>
|
|
67
|
+
</Show>
|
|
68
|
+
</BottomSheet.Body>
|
|
69
|
+
</BottomSheet>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { documentNavigate } from "@k2b/ssr/nav";
|
|
2
|
+
import { Button, ButtonLink } from "@k2b/ui";
|
|
3
|
+
import type { CloudTheme } from "../shared/theme";
|
|
4
|
+
import { createProfileActions } from "./profile-actions";
|
|
5
|
+
|
|
6
|
+
export function MobileProfileActions(props: { profile: { name: string; theme: CloudTheme }; beforeSelect: () => Promise<boolean> }) {
|
|
7
|
+
const { preferences, signingOut, signOut } = createProfileActions(props.profile.theme);
|
|
8
|
+
return (
|
|
9
|
+
<section class="cloud-mobile-profile" aria-label={preferences.messages().menuLabel}>
|
|
10
|
+
<ButtonLink
|
|
11
|
+
href="/me"
|
|
12
|
+
variant="ghost"
|
|
13
|
+
onClick={(event) => {
|
|
14
|
+
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
void props.beforeSelect().then((allowed) => {
|
|
17
|
+
if (allowed) documentNavigate("/me");
|
|
18
|
+
});
|
|
19
|
+
}}
|
|
20
|
+
>
|
|
21
|
+
<i class="ti ti-user-circle" aria-hidden="true" />
|
|
22
|
+
{preferences.messages().profileSettings}
|
|
23
|
+
</ButtonLink>
|
|
24
|
+
<Button variant="ghost" onClick={preferences.toggleTheme}>
|
|
25
|
+
<i class={`ti ${preferences.theme() === "light" ? "ti-moon" : "ti-sun-high"}`} aria-hidden="true" />
|
|
26
|
+
{preferences.themeLabel()}
|
|
27
|
+
</Button>
|
|
28
|
+
<Button
|
|
29
|
+
variant="ghost"
|
|
30
|
+
onClick={async () => {
|
|
31
|
+
if (await props.beforeSelect()) preferences.toggleLanguage();
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
<i class="ti ti-language" aria-hidden="true" />
|
|
35
|
+
{preferences.languageLabel()}
|
|
36
|
+
</Button>
|
|
37
|
+
<Button
|
|
38
|
+
variant="ghost"
|
|
39
|
+
disabled={signingOut()}
|
|
40
|
+
onClick={async () => {
|
|
41
|
+
if (await props.beforeSelect()) await signOut();
|
|
42
|
+
}}
|
|
43
|
+
>
|
|
44
|
+
<i class="ti ti-logout" aria-hidden="true" />
|
|
45
|
+
{preferences.messages().signOut}
|
|
46
|
+
</Button>
|
|
47
|
+
</section>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import { Avatar, Dropdown
|
|
2
|
-
import { createMemo, createSignal } from "solid-js";
|
|
3
|
-
import { browserNotificationClient } from "../browser/notifications";
|
|
4
|
-
import { apiClient } from "../clients/core";
|
|
1
|
+
import { Avatar, Dropdown } from "@k2b/ui";
|
|
5
2
|
import type { CloudTheme } from "../shared/theme";
|
|
6
|
-
import {
|
|
3
|
+
import { createProfileActions } from "./profile-actions";
|
|
7
4
|
|
|
8
5
|
type ProfilePreferencesProps = {
|
|
9
6
|
avatarSrc?: string;
|
|
@@ -13,37 +10,7 @@ type ProfilePreferencesProps = {
|
|
|
13
10
|
};
|
|
14
11
|
|
|
15
12
|
export default function ProfilePreferences(props: ProfilePreferencesProps) {
|
|
16
|
-
const
|
|
17
|
-
const preferences = createPreferenceController(props.initialTheme, locale);
|
|
18
|
-
const [signingOut, setSigningOut] = createSignal(false);
|
|
19
|
-
const signOut = async () => {
|
|
20
|
-
if (signingOut()) return;
|
|
21
|
-
setSigningOut(true);
|
|
22
|
-
try {
|
|
23
|
-
await browserNotificationClient.disable().catch(() => undefined);
|
|
24
|
-
const response = await apiClient.auth.logout.$post();
|
|
25
|
-
if (!response.ok) throw new Error(preferences.messages().signOutFailed);
|
|
26
|
-
window.location.href = "/auth/login";
|
|
27
|
-
} catch {
|
|
28
|
-
toast.error(preferences.messages().signOutFailed);
|
|
29
|
-
setSigningOut(false);
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const items = createMemo<DropdownItem[]>(() => [
|
|
34
|
-
...preferences.items(),
|
|
35
|
-
{
|
|
36
|
-
href: "/me",
|
|
37
|
-
icon: "ti ti-user-circle",
|
|
38
|
-
label: preferences.messages().profileSettings,
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
action: signOut,
|
|
42
|
-
icon: "ti ti-logout",
|
|
43
|
-
label: preferences.messages().signOut,
|
|
44
|
-
disabled: signingOut(),
|
|
45
|
-
},
|
|
46
|
-
]);
|
|
13
|
+
const { preferences, items, signOut, signingOut } = createProfileActions(props.initialTheme);
|
|
47
14
|
|
|
48
15
|
const avatar = () => <Avatar name={props.name} src={props.avatarSrc} size="xs" />;
|
|
49
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ScrollArea, Tooltip, useLocale } from "@k2b/ui";
|
|
2
2
|
import { createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { RailSnapshot } from "../contracts/rail-preferences";
|
|
4
4
|
import { appAccentStyle } from "./app-appearance";
|
|
5
5
|
import { RAIL_PREFERENCES_EVENT, readRailContext } from "./rail-context";
|
|
6
6
|
import { projectRailNavigation, type RailApp, type RailLink, railLinkActive } from "./rail-navigation";
|
|
@@ -26,7 +26,7 @@ function RailIcon(props: { link: RailLink; currentUrl: string }) {
|
|
|
26
26
|
);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export default function RailApps(props: { apps: RailApp[]; settings:
|
|
29
|
+
export default function RailApps(props: { apps: RailApp[]; settings: RailSnapshot; currentUrl: string }) {
|
|
30
30
|
const locale = useLocale();
|
|
31
31
|
const [settings, setSettings] = createSignal(props.settings);
|
|
32
32
|
const [currentUrl, setCurrentUrl] = createSignal(props.currentUrl);
|
package/src/ssr/RailEditor.tsx
CHANGED
|
@@ -19,7 +19,7 @@ import { coreClient } from "../clients/core";
|
|
|
19
19
|
import { type RailPreferences, RailPreferencesSchema, type RailShortcut } from "../contracts/rail-preferences";
|
|
20
20
|
import { publishRailPreferences, readRailContext } from "./rail-context";
|
|
21
21
|
import { railMessages } from "./rail-messages";
|
|
22
|
-
import { isRailAppVisible, type RailApp, sortRailApps } from "./rail-navigation";
|
|
22
|
+
import { isRailAppVisible, projectRailNavigation, type RailApp, sortRailApps } from "./rail-navigation";
|
|
23
23
|
|
|
24
24
|
export const readRailPreferences = async (signal: AbortSignal, message: string) => {
|
|
25
25
|
const response = await coreClient.me.rail.$get({}, { init: { signal } });
|
|
@@ -35,7 +35,10 @@ function RailEditorForm(props: RailDialogProps & { initial: RailPreferences }) {
|
|
|
35
35
|
const [settings, setSettings] = createSignal<RailPreferences>(structuredClone(props.initial));
|
|
36
36
|
const valid = createMemo(() => RailPreferencesSchema.safeParse(settings()).success);
|
|
37
37
|
const dirty = () => JSON.stringify(settings()) !== JSON.stringify(props.initial);
|
|
38
|
-
const
|
|
38
|
+
const managedShortcuts = readRailContext()?.settings.managedShortcuts ?? [];
|
|
39
|
+
const managedLinks = () => projectRailNavigation(props.apps, { ...settings(), shortcuts: [], managedShortcuts }, locale()).shortcuts;
|
|
40
|
+
const pinned = () =>
|
|
41
|
+
new Set([...managedShortcuts, ...settings().shortcuts].flatMap((entry) => (entry.kind === "app" ? [entry.appId] : [])));
|
|
39
42
|
const appOptions = () => props.apps.map((app) => ({ id: app.id, label: app.label, icon: app.iconClass }));
|
|
40
43
|
const nextApp = () => props.apps.find((app) => !pinned().has(app.id));
|
|
41
44
|
const updateShortcut = (index: number, next: RailShortcut) =>
|
|
@@ -77,6 +80,18 @@ function RailEditorForm(props: RailDialogProps & { initial: RailPreferences }) {
|
|
|
77
80
|
/>
|
|
78
81
|
<PanelDialog.Body>
|
|
79
82
|
<fieldset disabled={save.loading()} class="flex min-h-0 flex-col gap-4">
|
|
83
|
+
<Show when={managedLinks().length > 0}>
|
|
84
|
+
<PanelDialog.Section title={t().managed} icon="ti ti-building">
|
|
85
|
+
<For each={managedLinks()}>
|
|
86
|
+
{(link) => (
|
|
87
|
+
<div class="flex items-center gap-2 text-sm">
|
|
88
|
+
<i class={link.iconClass} aria-hidden="true" />
|
|
89
|
+
<span>{link.label}</span>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
</For>
|
|
93
|
+
</PanelDialog.Section>
|
|
94
|
+
</Show>
|
|
80
95
|
<PanelDialog.Section title={t().apps} icon="ti ti-apps">
|
|
81
96
|
<div
|
|
82
97
|
style={{ display: "grid", "grid-template-columns": "repeat(auto-fit, minmax(min(100%, 13rem), 1fr))", gap: "0.75rem 1.5rem" }}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createNavigation, type NavigationItem } from "@k2b/ui";
|
|
2
|
+
import WorkspaceNavigationProvider from "./WorkspaceNavigationProvider";
|
|
3
|
+
|
|
4
|
+
/** SSR-first link navigation. Use provideWorkspaceNavigation for island-owned actions. */
|
|
5
|
+
export default function WorkspaceNavigation(props: { items: readonly NavigationItem[]; label: string }) {
|
|
6
|
+
return <WorkspaceNavigationProvider navigation={createNavigation({ items: () => props.items })} label={props.label} />;
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NavigationController } from "@k2b/ui";
|
|
2
|
+
import { provideWorkspaceNavigation } from "./workspace-navigation";
|
|
3
|
+
|
|
4
|
+
/** Use within an existing app island when navigation includes local actions. */
|
|
5
|
+
export default function WorkspaceNavigationProvider(props: { navigation: NavigationController; label: string }) {
|
|
6
|
+
let owner!: HTMLScriptElement;
|
|
7
|
+
provideWorkspaceNavigation(props.navigation, { label: () => props.label, owner: () => owner });
|
|
8
|
+
return (
|
|
9
|
+
<script
|
|
10
|
+
ref={owner}
|
|
11
|
+
type="application/json"
|
|
12
|
+
data-cloud-workspace-navigation
|
|
13
|
+
innerHTML={JSON.stringify({ label: props.label, items: props.navigation.items() }).replace(/</g, "\\u003c")}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -23,6 +23,7 @@ export const buildAdminGroups = (apps: readonly RuntimeContext["apps"][number][]
|
|
|
23
23
|
const aiLinks: AdminLink[] = [
|
|
24
24
|
{ href: "/admin/settings?tab=ai-general", icon: "ti-adjustments", label: t.general },
|
|
25
25
|
{ href: "/admin/settings?tab=ai-providers", icon: "ti-sparkles", label: t.providers },
|
|
26
|
+
{ href: "/admin/settings?tab=ai-quotas", icon: "ti-gauge", label: t.aiQuotas },
|
|
26
27
|
{ href: "/admin/settings?tab=ai-usage", icon: "ti-chart-histogram", label: t.aiUsage },
|
|
27
28
|
{ href: "/admin/settings?tab=ai-skills", icon: "ti-wand", label: t.skills },
|
|
28
29
|
{ href: "/admin/settings?tab=ai-projects", icon: "ti-folders", label: t.projects },
|
|
@@ -33,6 +34,7 @@ export const buildAdminGroups = (apps: readonly RuntimeContext["apps"][number][]
|
|
|
33
34
|
app,
|
|
34
35
|
groups: (app.adminNav ?? [])
|
|
35
36
|
.map((group) => ({
|
|
37
|
+
section: group.section,
|
|
36
38
|
label: group.label,
|
|
37
39
|
links: group.links.flatMap((link) => {
|
|
38
40
|
const href = normalizeAdminHref(link.href);
|
|
@@ -43,6 +45,10 @@ export const buildAdminGroups = (apps: readonly RuntimeContext["apps"][number][]
|
|
|
43
45
|
}))
|
|
44
46
|
.sort((a, b) => a.app.name.localeCompare(b.app.name));
|
|
45
47
|
|
|
48
|
+
aiLinks.push(
|
|
49
|
+
...contributedApps.flatMap(({ groups }) => groups.filter((group) => group.section === "ai").flatMap((group) => group.links)),
|
|
50
|
+
);
|
|
51
|
+
|
|
46
52
|
const appsWithGroups = new Set(contributedApps.filter(({ groups }) => groups.length > 0).map(({ app }) => app.id));
|
|
47
53
|
const appLinks = apps
|
|
48
54
|
.filter((app) => !appsWithGroups.has(app.id))
|
|
@@ -57,10 +63,14 @@ export const buildAdminGroups = (apps: readonly RuntimeContext["apps"][number][]
|
|
|
57
63
|
label: t.general,
|
|
58
64
|
links: [
|
|
59
65
|
{ href: "/admin", icon: "ti-dashboard", label: t.overview },
|
|
66
|
+
{ href: "/admin/app-credentials", icon: "ti-key", label: t.appCredentials },
|
|
67
|
+
{ href: "/admin/rail", icon: "ti-layout-sidebar", label: t.rail },
|
|
60
68
|
{ href: "/admin/announcements", icon: "ti-speakerphone", label: t.announcements },
|
|
61
69
|
],
|
|
62
70
|
},
|
|
63
|
-
...contributedApps.flatMap(({ groups }) =>
|
|
71
|
+
...contributedApps.flatMap(({ groups }) =>
|
|
72
|
+
groups.filter((group) => group.section !== "ai").map(({ label, links }) => ({ label, links })),
|
|
73
|
+
),
|
|
64
74
|
{
|
|
65
75
|
label: t.userManagement,
|
|
66
76
|
links: [
|