@k2b/cloud 0.6.0-rc.0 → 0.7.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 +30 -17
- package/scripts/browser-performance.ts +14 -0
- package/scripts/build-pdf-renderer.ts +39 -0
- package/scripts/build.ts +15 -35
- package/scripts/preload.ts +9 -12
- package/scripts/sync-recovery-smoke.ts +2 -1
- package/src/_internal/app-presentation.ts +14 -2
- package/src/_internal/capabilities.ts +108 -7
- package/src/_internal/capability-streams.ts +81 -0
- package/src/_internal/define-app.ts +62 -36
- 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/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 +14 -153
- package/src/access/PrincipalPicker.tsx +104 -0
- package/src/access/ui.ts +2 -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 +79 -0
- package/src/ai/browser.ts +2 -0
- package/src/ai/capabilities.ts +55 -36
- package/src/ai/capability-execution.ts +47 -12
- package/src/ai/chat/blocks.tsx +157 -95
- 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 +48 -3
- package/src/ai/chat/message-actions.tsx +8 -7
- package/src/ai/chat/message-utils.ts +12 -1
- package/src/ai/chat/messages.ts +2 -0
- package/src/ai/chat/presentation.tsx +19 -48
- package/src/ai/chat/primitives.tsx +43 -1
- package/src/ai/chat/tool-disclosure.tsx +17 -2
- package/src/ai/chat/tool-groups.ts +52 -0
- package/src/ai/chat-quotas.ts +25 -0
- package/src/ai/chat-task-contracts.ts +9 -1
- package/src/ai/chat-tasks.ts +276 -43
- package/src/ai/client/controller.ts +110 -33
- package/src/ai/client/file-source.ts +12 -6
- package/src/ai/client/live-connection.ts +1 -0
- package/src/ai/client/projection.ts +22 -11
- package/src/ai/client/transport.ts +4 -0
- package/src/ai/code-capability-routes.ts +110 -0
- package/src/ai/code-capability-transport.ts +38 -0
- package/src/ai/code-mode-skill.ts +114 -0
- package/src/ai/code-runtime-tools.ts +166 -0
- package/src/ai/code-source-contracts.ts +202 -0
- package/src/ai/code-source-tools.ts +145 -0
- package/src/ai/compaction.ts +27 -6
- package/src/ai/data-analysis-skill.ts +10 -0
- package/src/ai/default-tools.ts +25 -2
- package/src/ai/dictation-runtime.ts +171 -0
- package/src/ai/dictations.ts +188 -0
- package/src/ai/draft-content.ts +34 -0
- package/src/ai/enrich.ts +6 -0
- package/src/ai/executor.ts +191 -31
- package/src/ai/file-content-version.ts +22 -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 +2 -1
- package/src/ai/files-store.ts +174 -71
- package/src/ai/fixtures/vision-encrypted.pdf +0 -0
- package/src/ai/grids-skill.ts +40 -10
- package/src/ai/http.ts +15 -8
- package/src/ai/index.ts +26 -2
- package/src/ai/inference-calls.ts +198 -0
- package/src/ai/live-events.ts +5 -1
- package/src/ai/memory-learning.ts +60 -28
- package/src/ai/message-queue.ts +167 -0
- package/src/ai/migrate.ts +187 -11
- package/src/ai/model-pricing.ts +43 -0
- package/src/ai/pdf-render-worker.ts +74 -0
- package/src/ai/pdf-render.fixture.ts +28 -0
- package/src/ai/pdf-render.ts +77 -0
- package/src/ai/projects-routes.ts +3 -3
- package/src/ai/projects.ts +20 -6
- package/src/ai/protocol.ts +3 -0
- package/src/ai/provider.ts +1 -8
- package/src/ai/quota-provider.ts +191 -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 +215 -11
- package/src/ai/run-timeout.ts +10 -0
- package/src/ai/runtime-tools.ts +3 -0
- package/src/ai/runtime.ts +55 -16
- package/src/ai/settings.ts +36 -8
- package/src/ai/short-id.ts +2 -2
- package/src/ai/skill-catalog.ts +0 -10
- package/src/ai/skill-search.ts +47 -0
- package/src/ai/skill-seeds.ts +105 -20
- package/src/ai/skill-tool.ts +16 -5
- package/src/ai/skills-routes.ts +32 -4
- package/src/ai/skills.ts +324 -155
- package/src/ai/store.ts +373 -191
- package/src/ai/stream.ts +5 -0
- package/src/ai/structured-runs.ts +4 -3
- package/src/ai/structured.ts +8 -1
- package/src/ai/system-prompt.ts +12 -2
- package/src/ai/timeline.ts +2 -5
- package/src/ai/todo-contracts.ts +20 -0
- package/src/ai/todo-tool.ts +16 -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 +2 -0
- package/src/ai/usage.ts +118 -118
- package/src/ai/vision-tool.ts +39 -10
- package/src/api/admin-ai-projects.ts +2 -2
- package/src/api/admin-ai-quotas.ts +74 -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-rail.ts +58 -0
- package/src/api/announcements.ts +14 -0
- package/src/api/capabilities.ts +292 -43
- package/src/api/capability-streams.ts +262 -0
- package/src/api/help.ts +8 -44
- package/src/api/index.ts +6 -0
- package/src/api/mcp.ts +44 -26
- 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 +66 -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 +5 -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 +114 -34
- 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 +79 -0
- package/src/cli/admin/data.ts +17 -0
- package/src/cli/admin/index.ts +9 -0
- package/src/cli/capabilities.ts +29 -0
- package/src/cli/capability-streams.ts +25 -0
- package/src/cli/index.ts +3 -2
- package/src/config/env.ts +11 -8
- package/src/contracts/app.ts +23 -3
- package/src/contracts/capabilities.ts +68 -5
- package/src/contracts/capability-streams.ts +46 -0
- package/src/contracts/commands.ts +49 -0
- package/src/contracts/index.ts +6 -1
- package/src/contracts/rail-admin.ts +38 -0
- package/src/contracts/rail-preferences.ts +16 -0
- package/src/contracts/registry.ts +3 -27
- package/src/contracts/settings-types.ts +1 -1
- package/src/contracts/shared.ts +9 -5
- package/src/contracts/web-vitals.ts +27 -0
- package/src/desktop/solid.tsx +3 -3
- package/src/index.ts +0 -4
- 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/services/access-revision.ts +10 -0
- package/src/server/services/access.ts +1 -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/posix.ts +45 -29
- package/src/services/accounts/users.ts +2 -2
- package/src/services/announcements/index.ts +69 -8
- 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 +2 -1
- package/src/services/identity/key-ring.ts +1 -1
- package/src/services/index.ts +15 -0
- 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 +18 -4
- package/src/services/mandates/policy.ts +65 -2
- package/src/services/notifications/batches.ts +124 -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 +107 -84
- package/src/services/pdf/index.ts +3 -0
- 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 +28 -0
- package/src/services/session/index.ts +15 -2
- package/src/services/session/user.ts +62 -37
- package/src/services/settings/core-settings.ts +21 -28
- package/src/services/settings/defaults.ts +5 -0
- package/src/services/settings/store.ts +58 -84
- 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 +6 -0
- package/src/ssr/AdminLayout.tsx +7 -5
- package/src/ssr/AdminSidebar.tsx +29 -15
- package/src/ssr/AppLaunchpad.island.tsx +24 -137
- 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 +23 -22
- package/src/ssr/{HotkeysHelpRail.island.tsx → LayoutHelpTrigger.island.tsx} +18 -12
- package/src/ssr/LayoutRail.tsx +17 -9
- 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 +7 -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/runtime.ts +5 -1
- package/src/workflows/ai/store.ts +2 -0
- 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 +2 -0
- 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/scripts/app-favicon.test.ts +0 -49
- package/src/ai/chat/blocks.render.test.tsx +0 -1108
- package/src/ai/kit-skill.ts +0 -22
- package/src/ai/usage-migrate.ts +0 -81
- package/src/browser/CloudResourceSearch.behavior.test.tsx +0 -74
- package/src/browser/notification-target.ts +0 -27
- package/src/contracts/notification-live.ts +0 -66
- package/src/services/notifications/live.ts +0 -50
- package/src/ssr/BrowserNotifications.island.tsx +0 -135
- package/src/ssr/GlobalSearchDialog.behavior.test.tsx +0 -96
- package/src/ssr/Layout.render.test.tsx +0 -148
- 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
|
|
7
|
+
import LayoutHelpTrigger from "./LayoutHelpTrigger.island";
|
|
7
8
|
import LayoutBreadcrumbs from "./LayoutBreadcrumbs.island";
|
|
8
|
-
import type { LayoutBreadcrumb } from "./layout-runtime";
|
|
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,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { COMMANDS_CHANGED, contextCommandsWithShortcuts, shortcutLabel } from "../browser/command-shortcuts";
|
|
2
|
+
import { searchHelpTopics } from "./layout-help-search";
|
|
3
|
+
import { clipboard } from "@k2b/stdlib/solid";
|
|
2
4
|
import { Button, IconButton, IconButtonLink, MarkdownView, NoticeCard, Placeholder, prompts, ScrollArea, useLocale } from "@k2b/ui";
|
|
3
5
|
import type { HelpDocumentManifest, HelpDocumentPayload, HelpSearchPayload } from "@k2b/cloud/shared";
|
|
4
6
|
import { createEffect, createMemo, createSignal, For, type JSX, onCleanup, onMount, Show } from "solid-js";
|
|
@@ -106,7 +108,15 @@ 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(() =>
|
|
118
|
+
commands().filter((command) => command.shortcut),
|
|
119
|
+
);
|
|
110
120
|
return (
|
|
111
121
|
<div class="flex flex-col gap-3">
|
|
112
122
|
<p class="text-sm leading-relaxed text-dimmed">{t().shortcutsIntro}</p>
|
|
@@ -118,20 +128,14 @@ const Shortcuts = (props: { openSearchHelp: () => void }) => {
|
|
|
118
128
|
{(entry) => (
|
|
119
129
|
<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
130
|
<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.
|
|
131
|
+
<p class="text-sm font-medium text-primary">{entry.title}</p>
|
|
132
|
+
<Show when={entry.description}>
|
|
133
|
+
<p class="mt-0.5 text-xs text-dimmed">{entry.description}</p>
|
|
124
134
|
</Show>
|
|
125
135
|
</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>
|
|
136
|
+
<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">
|
|
137
|
+
{shortcutLabel(entry.shortcut!)}
|
|
138
|
+
</kbd>
|
|
135
139
|
</div>
|
|
136
140
|
)}
|
|
137
141
|
</For>
|
|
@@ -161,7 +165,7 @@ const HelpShell = (props: {
|
|
|
161
165
|
const [loading, setLoading] = createSignal(props.session.view === "article");
|
|
162
166
|
const [loadError, setLoadError] = createSignal<string | null>(null);
|
|
163
167
|
const [loadAttempt, setLoadAttempt] = createSignal(0);
|
|
164
|
-
const [
|
|
168
|
+
const [remoteIds, setRemoteIds] = createSignal<readonly string[] | null>(null);
|
|
165
169
|
const [searching, setSearching] = createSignal(false);
|
|
166
170
|
const [copyingAll, setCopyingAll] = createSignal(false);
|
|
167
171
|
const [copyAllError, setCopyAllError] = createSignal<string | null>(null);
|
|
@@ -234,10 +238,7 @@ const HelpShell = (props: {
|
|
|
234
238
|
const results = createMemo(() => {
|
|
235
239
|
const value = normalizedQuery();
|
|
236
240
|
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
|
-
);
|
|
241
|
+
return searchHelpTopics(topics(), value, remoteIds());
|
|
241
242
|
});
|
|
242
243
|
|
|
243
244
|
createEffect(() => {
|
|
@@ -250,7 +251,7 @@ const HelpShell = (props: {
|
|
|
250
251
|
),
|
|
251
252
|
];
|
|
252
253
|
const version = ++searchVersion;
|
|
253
|
-
|
|
254
|
+
setRemoteIds(null);
|
|
254
255
|
setSearching(false);
|
|
255
256
|
if (!value || urls.length === 0) return;
|
|
256
257
|
|
|
@@ -269,7 +270,7 @@ const HelpShell = (props: {
|
|
|
269
270
|
}),
|
|
270
271
|
)
|
|
271
272
|
.then((groups) => {
|
|
272
|
-
if (version === searchVersion)
|
|
273
|
+
if (version === searchVersion) setRemoteIds([...new Set(groups.flat())]);
|
|
273
274
|
})
|
|
274
275
|
.catch((error) => {
|
|
275
276
|
if (error instanceof DOMException && error.name === "AbortError") return;
|
|
@@ -445,7 +446,7 @@ const HelpShell = (props: {
|
|
|
445
446
|
};
|
|
446
447
|
const showHub = () => {
|
|
447
448
|
setQuery("");
|
|
448
|
-
|
|
449
|
+
setRemoteIds(null);
|
|
449
450
|
setView("hub");
|
|
450
451
|
};
|
|
451
452
|
const modalTitle = createMemo(() => {
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { registerContextAwareCommand } from "../browser/commands";
|
|
2
|
+
import { createEffect, onCleanup } from "solid-js";
|
|
2
3
|
import { IconButton, Tooltip, useLocale } from "@k2b/ui";
|
|
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>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BottomSheet, Navigation, SegmentedControl, useLocale } from "@k2b/ui";
|
|
2
|
+
import { createSignal, onCleanup, Show } from "solid-js";
|
|
3
|
+
import { AppLaunchpadPanel, type AppLaunchpadContext } from "./AppLaunchpadPanel";
|
|
4
|
+
import { openMobileMenu } from "./mobile-menu-history";
|
|
5
|
+
import { observeWorkspaceNavigation, readWorkspaceNavigation } from "./workspace-navigation";
|
|
6
|
+
import { platformMessages } from "./platform-messages";
|
|
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 {
|
|
2
|
-
import {
|
|
3
|
-
import { browserNotificationClient } from "../browser/notifications";
|
|
4
|
-
import { apiClient } from "../clients/core";
|
|
1
|
+
import { createProfileActions } from "./profile-actions";
|
|
2
|
+
import { Avatar, Dropdown } from "@k2b/ui";
|
|
5
3
|
import type { CloudTheme } from "../shared/theme";
|
|
6
|
-
import { createPreferenceController } from "./preference-controller";
|
|
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,8 @@ 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(...contributedApps.flatMap(({groups}) => groups.filter(group => group.section === "ai").flatMap(group => group.links)));
|
|
49
|
+
|
|
46
50
|
const appsWithGroups = new Set(contributedApps.filter(({ groups }) => groups.length > 0).map(({ app }) => app.id));
|
|
47
51
|
const appLinks = apps
|
|
48
52
|
.filter((app) => !appsWithGroups.has(app.id))
|
|
@@ -57,10 +61,12 @@ export const buildAdminGroups = (apps: readonly RuntimeContext["apps"][number][]
|
|
|
57
61
|
label: t.general,
|
|
58
62
|
links: [
|
|
59
63
|
{ href: "/admin", icon: "ti-dashboard", label: t.overview },
|
|
64
|
+
{ href: "/admin/app-credentials", icon: "ti-key", label: t.appCredentials },
|
|
65
|
+
{ href: "/admin/rail", icon: "ti-layout-sidebar", label: t.rail },
|
|
60
66
|
{ href: "/admin/announcements", icon: "ti-speakerphone", label: t.announcements },
|
|
61
67
|
],
|
|
62
68
|
},
|
|
63
|
-
...contributedApps.flatMap(({ groups }) => groups),
|
|
69
|
+
...contributedApps.flatMap(({ groups }) => groups.filter(group => group.section !== "ai").map(({label,links}) => ({label,links}))),
|
|
64
70
|
{
|
|
65
71
|
label: t.userManagement,
|
|
66
72
|
links: [
|