@k2b/cloud 0.39.0 → 0.40.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 +8 -0
- package/package.json +8 -4
- package/src/_internal/app-favicon.ts +4 -2
- package/src/_internal/capabilities.ts +72 -48
- package/src/_internal/capability-reviews.ts +81 -0
- package/src/_internal/capability-schemas.ts +29 -0
- package/src/_internal/capability-sentences.ts +216 -1
- package/src/_internal/capability-streams.ts +7 -0
- package/src/_internal/define-app.ts +36 -15
- package/src/_internal/font-preloads.ts +2 -1
- package/src/_internal/runtime-watcher.ts +6 -0
- package/src/_internal/static-assets.ts +52 -3
- package/src/_internal/status-preserving-ssr.ts +2 -0
- package/src/access/PermissionEditor.tsx +2 -2
- package/src/account/EntitySearch.tsx +1 -1
- package/src/ai/action-review.ts +101 -0
- package/src/ai/background-review.ts +72 -0
- package/src/ai/capabilities.ts +29 -11
- package/src/ai/capability-execution.ts +76 -43
- package/src/ai/capability-result.ts +95 -0
- package/src/ai/chat/action-card.ts +398 -0
- package/src/ai/chat/action-messages.ts +129 -0
- package/src/ai/chat/action-question.browser-harness.tsx +245 -0
- package/src/ai/chat/action-question.fixtures.ts +733 -0
- package/src/ai/chat/action-question.tsx +669 -0
- package/src/ai/chat/blocks.tsx +19 -25
- package/src/ai/chat/message-actions.tsx +6 -0
- package/src/ai/chat/tool-action.tsx +481 -0
- package/src/ai/chat/turn-view.tsx +10 -160
- package/src/ai/chat/view-block.tsx +15 -5
- package/src/ai/chat-task-contracts.ts +4 -1
- package/src/ai/chat-task-reasons-migrate.ts +7 -0
- package/src/ai/chat-task-reasons.ts +1 -1
- package/src/ai/chat-tasks.ts +55 -25
- package/src/ai/client/controller.ts +59 -0
- package/src/ai/default-tools.ts +9 -1
- package/src/ai/executor.ts +190 -39
- package/src/ai/index.ts +2 -0
- package/src/ai/migrate.ts +11 -2
- package/src/ai/projects-routes.ts +3 -0
- package/src/ai/protocol.ts +14 -2
- package/src/ai/recent-resources.ts +240 -0
- package/src/ai/recent-sent-refs.ts +38 -0
- package/src/ai/routes.ts +145 -1
- package/src/ai/runtime.ts +1 -0
- package/src/ai/skills-routes.ts +3 -0
- package/src/ai/skills.ts +10 -1
- package/src/ai/store.ts +227 -24
- package/src/ai/tool-audit.ts +8 -0
- package/src/ai/tool-wording.ts +24 -2
- package/src/ai/types.ts +28 -3
- package/src/api/accounts-entities.ts +2 -1
- package/src/api/admin-ai-projects.ts +2 -0
- package/src/api/admin-ai-skills.ts +2 -0
- package/src/api/admin-ai-usage.ts +2 -0
- package/src/api/admin-geoip.ts +80 -0
- package/src/api/admin-identity.ts +3 -0
- package/src/api/admin-lifecycle.ts +3 -0
- package/src/api/admin-outgoing-mail.ts +2 -0
- package/src/api/app-approval.ts +46 -5
- package/src/api/approvals-loader.ts +23 -1
- package/src/api/approvals.ts +2 -0
- package/src/api/auth.ts +25 -6
- package/src/api/capabilities.ts +44 -11
- package/src/api/capability-streams.ts +12 -0
- package/src/api/help.ts +4 -28
- package/src/api/index.ts +6 -1
- package/src/api/lasting-access-messages.ts +17 -0
- package/src/api/mcp.ts +3 -0
- package/src/api/me-panel-consent.ts +71 -0
- package/src/api/me.ts +80 -33
- package/src/api/search.ts +3 -1
- package/src/api/sync-ops.ts +2 -0
- package/src/api/widgets.ts +3 -0
- package/src/approvals/Approvals.tsx +180 -238
- package/src/approvals/messages.ts +12 -24
- package/src/browser/CloudResourceSearch.browser-harness.tsx +10 -2
- package/src/browser/app-approval.ts +54 -5
- package/src/browser/commands.ts +22 -6
- package/src/browser/composer-catalog.ts +45 -3
- package/src/browser/composer-insert.ts +10 -0
- package/src/browser/composer-open-messages.ts +17 -0
- package/src/browser/composer-open.ts +110 -0
- package/src/browser/discoverable-command.ts +6 -0
- package/src/browser/hints.ts +6 -0
- package/src/browser/live-connection.ts +26 -2
- package/src/browser/navigation-apps.ts +7 -0
- package/src/browser/panel-context-bridge.ts +30 -0
- package/src/browser/panel-context-receiver.ts +53 -0
- package/src/browser/panel-menu.tsx +56 -0
- package/src/browser/panel-messages.ts +11 -0
- package/src/browser/panel-shortcuts.ts +31 -0
- package/src/browser/panel-slot.ts +412 -10
- package/src/browser/panel.ts +37 -0
- package/src/browser/search-commands.ts +6 -3
- package/src/capabilities/client.ts +8 -2
- package/src/capabilities/reviews.ts +205 -0
- package/src/capabilities/server.ts +10 -3
- package/src/capabilities/testing.ts +47 -0
- package/src/capabilities/types.ts +2 -2
- package/src/cli/account.ts +13 -3
- package/src/cli/admin/geoip.ts +115 -0
- package/src/cli/admin/index.ts +3 -0
- package/src/contracts/app-approval.ts +120 -13
- package/src/contracts/approvals.ts +2 -2
- package/src/contracts/capabilities.ts +410 -18
- package/src/contracts/geoip.ts +42 -0
- package/src/contracts/index.ts +3 -0
- package/src/contracts/panel-context-plain.ts +99 -0
- package/src/contracts/panel-context.ts +93 -0
- package/src/contracts/seen-hints.ts +35 -0
- package/src/server/client-ip.ts +5 -0
- package/src/server/help-payload.ts +38 -0
- package/src/server/middleware/auth.ts +11 -1
- package/src/server/middleware/middleware.ts +9 -0
- package/src/server/middleware/no-transform.ts +19 -0
- package/src/server/middleware/rate-limit.ts +4 -6
- package/src/server/request-context/address.ts +45 -0
- package/src/server/request-context/geoip.ts +380 -0
- package/src/server/request-context/index.ts +33 -0
- package/src/server/request-context/user-agent.ts +95 -0
- package/src/services/app-approval.ts +382 -59
- package/src/services/approvals/device.ts +7 -5
- package/src/services/approvals/index.ts +6 -5
- package/src/services/approvals/list.ts +26 -5
- package/src/services/auth-flows/notification-sender.ts +3 -0
- package/src/services/geoip/index.ts +164 -0
- package/src/services/geoip/store.ts +30 -0
- package/src/services/geoip/upload.ts +150 -0
- package/src/services/identity/routes.ts +5 -1
- package/src/services/index.ts +11 -0
- package/src/services/known-browsers.ts +312 -0
- package/src/services/login-address-store.ts +51 -0
- package/src/services/login-context.ts +118 -0
- package/src/services/mandates/index.ts +3 -0
- package/src/services/mandates/policy.ts +15 -3
- package/src/services/notifications/email-frame.ts +1 -1
- package/src/services/oauth-tokens.ts +16 -2
- package/src/services/panel-context-consent.ts +58 -0
- package/src/services/service-account-credentials.ts +21 -0
- package/src/services/session/index.ts +97 -16
- package/src/services/session/recent.ts +13 -0
- package/src/services/webauthn.ts +12 -0
- package/src/shared/email-html.ts +2 -0
- package/src/shared/help.ts +3 -0
- package/src/shared-text/cursor.ts +22 -0
- package/src/shared-text/documents.ts +142 -0
- package/src/shared-text/events.ts +43 -0
- package/src/shared-text/index.ts +12 -0
- package/src/shared-text/log.ts +48 -0
- package/src/shared-text/text-diff.ts +23 -0
- package/src/shared-text/types.ts +41 -0
- package/src/ssr/AdminLayout.tsx +2 -0
- package/src/ssr/AppLaunchpadPanel.tsx +2 -2
- package/src/ssr/CloudShell.island.tsx +10 -2
- package/src/ssr/Layout.tsx +25 -2
- package/src/ssr/LayoutHelp.browser-harness.tsx +2 -0
- package/src/ssr/LayoutHelp.tsx +435 -274
- package/src/ssr/LayoutHelpPanel.browser-harness.tsx +136 -0
- package/src/ssr/LayoutHelpTrigger.tsx +27 -2
- package/src/ssr/MobileNavigation.tsx +2 -2
- package/src/ssr/PanelLayout.tsx +9 -1
- package/src/ssr/PanelRuntime.island.tsx +7 -0
- package/src/ssr/PanelSlot.tsx +53 -3
- package/src/ssr/ShellRuntime.island.tsx +11 -2
- package/src/ssr/app-color.ts +14 -9
- package/src/ssr/app-navigation.ts +16 -3
- package/src/ssr/compact-shell.browser-harness.ts +30 -22
- package/src/ssr/dialog-app-colors.browser-harness.tsx +43 -0
- package/src/ssr/help-messages.ts +4 -0
- package/src/ssr/help.ts +20 -0
- package/src/ssr/hints.ts +25 -0
- package/src/ssr/index.ts +2 -0
- package/src/ssr/islands/index.ts +4 -3
- package/src/ssr/layout-context.ts +2 -0
- package/src/ssr/layout-help-article.server.ts +50 -0
- package/src/ssr/layout-help-start.ts +33 -7
- package/src/ssr/panel-consent.ts +29 -0
- package/src/ssr/panel-context.ts +10 -0
- package/src/ssr/panel-slot.browser-harness.tsx +53 -4
- package/src/ssr/platform-messages.ts +18 -0
- package/src/ssr/shell-data.tsx +2 -1
- package/src/styles/effects.css +198 -9
- package/src/styles/file-chooser.css +2 -1
- package/src/styles/global.css +49 -12
- package/src/styles/panel.css +47 -4
- package/src/styles/resource-search.css +6 -2
- package/src/styles/tokens.css +17 -11
- package/src/styles/utilities-buttons.css +3 -2
- package/src/styles/utilities-data.css +2 -1
- package/src/styles/utilities-feedback.css +2 -2
- package/src/styles/utilities-layout.css +3 -13
- package/src/styles/utilities-markdown-editor.css +6 -6
- package/src/styles/utilities-navigation.css +17 -22
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { keyboardPlatform } from "@k2b/ui";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { defineApp } from "../_internal/define-app";
|
|
4
|
+
import { person } from "../_internal/define-app.test-fixture";
|
|
5
|
+
import type { AppAppearance, CloudRuntime } from "../contracts/app";
|
|
6
|
+
import type { User } from "../contracts/shared";
|
|
7
|
+
import type { HelpDocumentManifest, HelpDocumentPayload } from "../shared/help";
|
|
8
|
+
import { appAppearanceStyle } from "./app-appearance";
|
|
9
|
+
import Layout from "./Layout";
|
|
10
|
+
import { LayoutHelpPage, type LayoutHelpPageProps, LayoutHelpPanel, type LayoutHelpPanelProps } from "./LayoutHelp";
|
|
11
|
+
import { helpArticlePartsServer } from "./layout-help-article.server";
|
|
12
|
+
import PanelLayout from "./PanelLayout";
|
|
13
|
+
import { readPanelContext } from "./panel-context";
|
|
14
|
+
|
|
15
|
+
export const documents: HelpDocumentManifest[] = [
|
|
16
|
+
{ id: "spaces-share", title: "Share a Space", kind: "task", icon: "ti ti-share" },
|
|
17
|
+
{ id: "spaces-start", title: "Start with Spaces", kind: "start", icon: "ti ti-rocket" },
|
|
18
|
+
].map((topic) => ({
|
|
19
|
+
...topic,
|
|
20
|
+
kind: topic.id === "spaces-share" ? "task" : "start",
|
|
21
|
+
order: 1,
|
|
22
|
+
url: `/api/help/${topic.id}`,
|
|
23
|
+
searchUrl: "/api/help/search",
|
|
24
|
+
}));
|
|
25
|
+
export const helpManifest = { documents, pageBase: "/help/apps/spaces", baseLocale: "en", locale: "en", manifestHash: "fixture" };
|
|
26
|
+
export const fixtureDocument: HelpDocumentPayload = {
|
|
27
|
+
id: "spaces-share",
|
|
28
|
+
title: "Share a Space",
|
|
29
|
+
locale: "en",
|
|
30
|
+
markdown: "Share",
|
|
31
|
+
kind: "task",
|
|
32
|
+
needs: "manage",
|
|
33
|
+
related: [],
|
|
34
|
+
// A lead, the facts chips, and a section: everything the article head reads from the article.
|
|
35
|
+
html: '<p>Share a Space so other people can see and edit its tasks.</p><h2 id="share">Share</h2><div class="help-steps"><ol><li>Open Space settings.</li><li>Add a person.</li></ol></div>',
|
|
36
|
+
};
|
|
37
|
+
const initialArticle = { payload: fixtureDocument, parts: helpArticlePartsServer(fixtureDocument.html) };
|
|
38
|
+
const appearance: AppAppearance = { accent: "#16a34a", background: { from: "#16a34a", to: "#0284c7" } };
|
|
39
|
+
const apps: CloudRuntime["apps"] = [
|
|
40
|
+
{
|
|
41
|
+
id: "spaces",
|
|
42
|
+
name: "Spaces",
|
|
43
|
+
icon: "ti ti-box",
|
|
44
|
+
description: "Help fixture",
|
|
45
|
+
routes: ["/app/spaces"],
|
|
46
|
+
nav: { href: "/app/spaces", section: "primary" },
|
|
47
|
+
appearance,
|
|
48
|
+
help: helpManifest,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "core",
|
|
52
|
+
name: "Core",
|
|
53
|
+
icon: "ti ti-cloud",
|
|
54
|
+
description: "Core fixture",
|
|
55
|
+
routes: ["/"],
|
|
56
|
+
panel: { href: "/panel", label: "Help", icon: "ti ti-help-circle" },
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
const app = defineApp({
|
|
60
|
+
id: "help-fixture",
|
|
61
|
+
name: "Help fixture",
|
|
62
|
+
description: "Help fixture",
|
|
63
|
+
icon: "ti ti-help-circle",
|
|
64
|
+
baseUrl: "http://fixture:3000",
|
|
65
|
+
routes: ["/"],
|
|
66
|
+
});
|
|
67
|
+
type Environment = { Variables: { runtime: CloudRuntime; user: User } };
|
|
68
|
+
const PropsSnapshot = (props: { value: LayoutHelpPanelProps | LayoutHelpPageProps }) => (
|
|
69
|
+
<script id="help-fixture-props" type="application/json" innerHTML={JSON.stringify(props.value).replace(/</g, "\\u003c")} />
|
|
70
|
+
);
|
|
71
|
+
export const server = new Hono<Environment>()
|
|
72
|
+
.use(async (c, next) => {
|
|
73
|
+
c.set("runtime", { apps });
|
|
74
|
+
c.set("user", person);
|
|
75
|
+
await next();
|
|
76
|
+
})
|
|
77
|
+
.get(
|
|
78
|
+
"/app/spaces",
|
|
79
|
+
...app.ssr<Environment>((c) => () => (
|
|
80
|
+
<Layout c={c} title="Spaces" help={["spaces-share"]}>
|
|
81
|
+
<p>App content</p>
|
|
82
|
+
</Layout>
|
|
83
|
+
)),
|
|
84
|
+
)
|
|
85
|
+
.get(
|
|
86
|
+
"/panel",
|
|
87
|
+
...app.ssr<Environment>((c) => {
|
|
88
|
+
const context = readPanelContext(c);
|
|
89
|
+
const props: LayoutHelpPanelProps = {
|
|
90
|
+
documents,
|
|
91
|
+
app: { id: "spaces", name: "Spaces", icon: "ti ti-box" },
|
|
92
|
+
appearance: appAppearanceStyle(appearance),
|
|
93
|
+
view: context?.app === "spaces" ? context.help?.filter((id) => documents.some((topic) => topic.id === id)) : [],
|
|
94
|
+
platform: keyboardPlatform(c.req.header("User-Agent") ?? ""),
|
|
95
|
+
pageBase: helpManifest.pageBase,
|
|
96
|
+
initialArticle: c.req.query("topic") === "spaces-share" ? initialArticle : undefined,
|
|
97
|
+
initialTopic: c.req.query("topic") === "spaces-share" ? "spaces-share" : undefined,
|
|
98
|
+
};
|
|
99
|
+
return () => (
|
|
100
|
+
<PanelLayout c={c} title="Help">
|
|
101
|
+
<div id="help-fixture-root" class="flex h-full min-h-0 flex-1 flex-col">
|
|
102
|
+
<LayoutHelpPanel {...props} />
|
|
103
|
+
</div>
|
|
104
|
+
<PropsSnapshot value={props} />
|
|
105
|
+
</PanelLayout>
|
|
106
|
+
);
|
|
107
|
+
}),
|
|
108
|
+
)
|
|
109
|
+
.get(
|
|
110
|
+
"/help/apps/spaces/spaces-share",
|
|
111
|
+
...app.ssr<Environment>((c) => () => (
|
|
112
|
+
<Layout c={c} title="Help">
|
|
113
|
+
<div id="help-fixture-root">
|
|
114
|
+
<LayoutHelpPage
|
|
115
|
+
documents={documents}
|
|
116
|
+
initialTopic="spaces-share"
|
|
117
|
+
initialArticle={initialArticle}
|
|
118
|
+
pageBase={helpManifest.pageBase}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<PropsSnapshot value={{ documents, initialTopic: "spaces-share", initialArticle, pageBase: helpManifest.pageBase }} />
|
|
122
|
+
</Layout>
|
|
123
|
+
)),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Capture during parsing and immediately after mounting; coordinates inside the provider are relative to its viewport.
|
|
128
|
+
* The article head keeps its position, width and height; the contents column keeps its position and width.
|
|
129
|
+
*/
|
|
130
|
+
export function helpPanelGeometry() {
|
|
131
|
+
return ["[data-help-view]", "[data-cloud-app-area]", ".help-article-head", ".help-article-aside"].map((selector, index) => {
|
|
132
|
+
const rect = document.querySelector(selector)?.getBoundingClientRect();
|
|
133
|
+
if (!rect) return null;
|
|
134
|
+
return index < 3 ? { x: rect.x, y: rect.y, width: rect.width, height: rect.height } : { x: rect.x, y: rect.y, width: rect.width };
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { IconButton, Tooltip, useLocale } from "@k2b/ui";
|
|
2
|
+
import { createSignal, onCleanup, onMount } from "solid-js";
|
|
3
|
+
import { isServer } from "solid-js/web";
|
|
4
|
+
import { closePanel, openPanel } from "../browser/panel";
|
|
2
5
|
import { importOnDemand } from "../browser/reload";
|
|
3
6
|
import { createBusyAction } from "./busy-action";
|
|
4
7
|
import { platformMessages } from "./platform-messages";
|
|
@@ -6,6 +9,8 @@ import { platformMessages } from "./platform-messages";
|
|
|
6
9
|
type LayoutHelpTriggerProps = {
|
|
7
10
|
variant: "header" | "rail";
|
|
8
11
|
class?: string;
|
|
12
|
+
panel?: boolean;
|
|
13
|
+
helpOpen?: boolean;
|
|
9
14
|
};
|
|
10
15
|
|
|
11
16
|
/** Opens end-user help from either the desktop rail or the compact header. */
|
|
@@ -13,17 +18,35 @@ export default function LayoutHelpTrigger(props: LayoutHelpTriggerProps) {
|
|
|
13
18
|
const locale = useLocale();
|
|
14
19
|
const t = () => platformMessages.resolve([locale()]).t;
|
|
15
20
|
const action = createBusyAction();
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
// The server knows the cookie; the browser also knows the mode: a phone starts with the sheet closed, whatever the
|
|
22
|
+
// cookie says, and the lazy controller may have changed the state before this island mounts.
|
|
23
|
+
const readOpen = () => {
|
|
24
|
+
const canvas = document.querySelector(".cloud-app-canvas");
|
|
25
|
+
const attribute = window.matchMedia("(min-width: 768px)").matches ? "data-panel" : "data-panel-sheet";
|
|
26
|
+
return canvas?.getAttribute(attribute) === "open" && document.getElementById("cloud-panel")?.dataset.provider === "core";
|
|
27
|
+
};
|
|
28
|
+
const [helpOpen, setHelpOpen] = createSignal(isServer ? !!props.helpOpen : !!props.panel && readOpen());
|
|
29
|
+
onMount(() => {
|
|
30
|
+
const changed = (event: WindowEventMap["cloud.panel.state"]) => setHelpOpen(event.detail.open && event.detail.provider === "core");
|
|
31
|
+
window.addEventListener("cloud.panel.state", changed);
|
|
32
|
+
onCleanup(() => window.removeEventListener("cloud.panel.state", changed));
|
|
33
|
+
});
|
|
34
|
+
const openHelp = () => {
|
|
35
|
+
if (helpOpen()) return closePanel();
|
|
36
|
+
if (openPanel("core")) return;
|
|
37
|
+
return action.run(
|
|
18
38
|
() => importOnDemand(() => import("./LayoutHelp")),
|
|
19
39
|
(help) => help.openLayoutHelpDialog(),
|
|
20
40
|
);
|
|
41
|
+
};
|
|
21
42
|
|
|
22
43
|
if (props.variant === "rail")
|
|
23
44
|
return (
|
|
24
45
|
<Tooltip.Trigger
|
|
25
46
|
type="button"
|
|
26
47
|
class={`rail-item text-blue-500 hover:bg-blue-500/10 hover:text-blue-600 dark:text-blue-400 dark:hover:bg-blue-500/15 dark:hover:text-blue-300 ${props.class ?? ""}`}
|
|
48
|
+
aria-controls={props.panel ? "cloud-panel" : undefined}
|
|
49
|
+
aria-expanded={props.panel ? helpOpen() : undefined}
|
|
27
50
|
onClick={openHelp}
|
|
28
51
|
aria-busy={action.busy() ? "true" : undefined}
|
|
29
52
|
aria-label={t().openHelp}
|
|
@@ -38,6 +61,8 @@ export default function LayoutHelpTrigger(props: LayoutHelpTriggerProps) {
|
|
|
38
61
|
return (
|
|
39
62
|
<IconButton
|
|
40
63
|
class={props.class}
|
|
64
|
+
aria-controls={props.panel ? "cloud-panel" : undefined}
|
|
65
|
+
aria-expanded={props.panel ? helpOpen() : undefined}
|
|
41
66
|
onClick={openHelp}
|
|
42
67
|
aria-busy={action.busy() ? "true" : undefined}
|
|
43
68
|
label={t().openHelp}
|
|
@@ -48,8 +48,8 @@ function MobileNavigation(props: {
|
|
|
48
48
|
value={tab}
|
|
49
49
|
onValueChange={setTab}
|
|
50
50
|
options={[
|
|
51
|
-
{ value: "app", label: workspace()?.label || t().appMenu },
|
|
52
|
-
{ value: "apps", label: t().allApps },
|
|
51
|
+
{ value: "app", label: <span class="block k2b-truncate-fade">{workspace()?.label || t().appMenu}</span> },
|
|
52
|
+
{ value: "apps", label: <span class="block k2b-truncate-fade">{t().allApps}</span> },
|
|
53
53
|
]}
|
|
54
54
|
/>
|
|
55
55
|
}
|
package/src/ssr/PanelLayout.tsx
CHANGED
|
@@ -5,12 +5,19 @@ import { getLocale } from "../server/locale";
|
|
|
5
5
|
import { resolveRuntimeRoute } from "./app-navigation";
|
|
6
6
|
import type { MinimalLayoutContext } from "./layout-context";
|
|
7
7
|
import PanelRuntime from "./PanelRuntime.island";
|
|
8
|
+
import { readPanelContext } from "./panel-context";
|
|
8
9
|
import { getRuntimeContext } from "./runtime";
|
|
9
10
|
|
|
10
|
-
export type PanelLayoutProps = {
|
|
11
|
+
export type PanelLayoutProps = {
|
|
12
|
+
c: MinimalLayoutContext & { header(name: string, value: string): void };
|
|
13
|
+
title: string;
|
|
14
|
+
children: JSX.Element;
|
|
15
|
+
};
|
|
11
16
|
|
|
12
17
|
/** A provider page embedded in the shell's same-origin panel. */
|
|
13
18
|
export default function PanelLayout(props: PanelLayoutProps) {
|
|
19
|
+
props.c.header("Referrer-Policy", "same-origin");
|
|
20
|
+
const context = readPanelContext(props.c);
|
|
14
21
|
const runtime = getRuntimeContext(props.c);
|
|
15
22
|
// The provider owns this path by the canonical prefixes its panel declaration was checked against.
|
|
16
23
|
const apps = runtime.apps.map((entry) => ({ ...entry, routes: entry.routes.map(canonicalRoute) }));
|
|
@@ -19,6 +26,7 @@ export default function PanelLayout(props: PanelLayoutProps) {
|
|
|
19
26
|
return (
|
|
20
27
|
<LocaleProvider locale={getLocale(props.c)}>
|
|
21
28
|
<div class="cloud-panel-page" data-cloud-panel-page="" data-panel-provider={app?.id}>
|
|
29
|
+
<script id="cloud-panel-context" type="application/json" innerHTML={JSON.stringify(context).replace(/</g, "\\u003c")} />
|
|
22
30
|
<PanelRuntime provider={app?.id} scope={app?.panel ? new URL(app.panel.href, props.c.req.raw.url).pathname : undefined} />
|
|
23
31
|
{props.children}
|
|
24
32
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { installAppWorkspaceController, installInputModeController, keyboardPlatform } from "@k2b/ui";
|
|
2
2
|
import { onCleanup, onMount } from "solid-js";
|
|
3
|
+
import { installPanelContextReceiver } from "../browser/panel-context-receiver";
|
|
3
4
|
import { syncTimezoneCookie } from "./timezone-cookie";
|
|
4
5
|
|
|
5
6
|
export default function PanelRuntime(props: { provider?: string; scope?: string }) {
|
|
@@ -12,6 +13,7 @@ export default function PanelRuntime(props: { provider?: string; scope?: string
|
|
|
12
13
|
const deferred = new Set<ReturnType<typeof setTimeout>>();
|
|
13
14
|
const embedded = window.parent !== window;
|
|
14
15
|
let disposeTheme = () => {};
|
|
16
|
+
let disposeContext = () => {};
|
|
15
17
|
let resumeTheme = () => {};
|
|
16
18
|
// A page kept in the back/forward cache keeps following the host; only a removed iframe lets go.
|
|
17
19
|
const pageHide = (event: PageTransitionEvent) => {
|
|
@@ -38,6 +40,9 @@ export default function PanelRuntime(props: { provider?: string; scope?: string
|
|
|
38
40
|
if (event.key.toLowerCase() === "j" && event.metaKey === apple && event.ctrlKey === !apple) {
|
|
39
41
|
event.preventDefault();
|
|
40
42
|
if (!event.repeat) close();
|
|
43
|
+
} else if (event.key.toLowerCase() === "k" && event.metaKey === apple && event.ctrlKey === !apple) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
if (!event.repeat) window.parent.postMessage({ type: "cloud.panel.search", v: 1 }, location.origin);
|
|
41
46
|
} else if (event.key === "Escape" && !event.metaKey && !event.ctrlKey) {
|
|
42
47
|
// An open modal dialog takes Escape through its cancel event, without preventing this keydown.
|
|
43
48
|
if (document.querySelector("dialog[open], [aria-modal='true']")) return;
|
|
@@ -49,6 +54,7 @@ export default function PanelRuntime(props: { provider?: string; scope?: string
|
|
|
49
54
|
}
|
|
50
55
|
};
|
|
51
56
|
if (embedded) {
|
|
57
|
+
disposeContext = installPanelContextReceiver();
|
|
52
58
|
try {
|
|
53
59
|
const parent = window.parent;
|
|
54
60
|
const parentRoot = parent.document.documentElement;
|
|
@@ -73,6 +79,7 @@ export default function PanelRuntime(props: { provider?: string; scope?: string
|
|
|
73
79
|
window.addEventListener("click", click);
|
|
74
80
|
}
|
|
75
81
|
onCleanup(() => {
|
|
82
|
+
disposeContext();
|
|
76
83
|
disposeInput();
|
|
77
84
|
disposeWorkspace();
|
|
78
85
|
disposeTheme();
|
package/src/ssr/PanelSlot.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Button, IconButton, type KeyboardPlatform, Placeholder, ResizeSeparator, useLocale } from "@k2b/ui";
|
|
1
|
+
import { Button, IconButton, type KeyboardPlatform, NoticeCard, Placeholder, ResizeSeparator, useLocale } from "@k2b/ui";
|
|
2
2
|
import { PANEL_MAX_WIDTH_REM, PANEL_MIN_WIDTH_REM, type PanelCookieState } from "../_internal/panel-cookie";
|
|
3
|
+
import { withPanelContext } from "../contracts/panel-context-plain";
|
|
3
4
|
import { platformMessages } from "./platform-messages";
|
|
4
5
|
import type { ShellData } from "./shell-data";
|
|
5
6
|
|
|
@@ -9,6 +10,10 @@ export default function PanelSlot(props: { panel: NonNullable<ShellData["panel"]
|
|
|
9
10
|
const t = platformMessages.resolve([locale()]).t;
|
|
10
11
|
const provider = props.panel.providers.find((provider) => provider.id === props.panel.provider) ?? props.panel.providers[0];
|
|
11
12
|
if (!provider) return null;
|
|
13
|
+
const answer = props.panel.consent?.provider === provider.id ? props.panel.consent.allowed : null;
|
|
14
|
+
const ask = props.state.open && provider.consent && answer === null;
|
|
15
|
+
const allowed = !provider.consent || answer === true;
|
|
16
|
+
const hasConsent = props.panel.providers.some((provider) => provider.consent);
|
|
12
17
|
const tabs = props.panel.providers.length > 1;
|
|
13
18
|
return (
|
|
14
19
|
<aside
|
|
@@ -18,6 +23,10 @@ export default function PanelSlot(props: { panel: NonNullable<ShellData["panel"]
|
|
|
18
23
|
aria-label={t.panel}
|
|
19
24
|
data-state={props.state.open ? "loading" : "closed"}
|
|
20
25
|
data-provider={provider.id}
|
|
26
|
+
data-app-name={provider.appName}
|
|
27
|
+
data-provider-consent={String(provider.consent)}
|
|
28
|
+
data-context-allowed={String(allowed)}
|
|
29
|
+
data-consent={ask ? "ask" : undefined}
|
|
21
30
|
>
|
|
22
31
|
<ResizeSeparator
|
|
23
32
|
class="cloud-panel__resize"
|
|
@@ -48,7 +57,7 @@ export default function PanelSlot(props: { panel: NonNullable<ShellData["panel"]
|
|
|
48
57
|
tabindex={item.id === provider.id ? 0 : -1}
|
|
49
58
|
>
|
|
50
59
|
<i class={item.icon} aria-hidden="true" />
|
|
51
|
-
<span class="cloud-panel__tab-label">{item.label}</span>
|
|
60
|
+
<span class="cloud-panel__tab-label k2b-truncate-fade">{item.label}</span>
|
|
52
61
|
</button>
|
|
53
62
|
))}
|
|
54
63
|
</div>
|
|
@@ -58,6 +67,19 @@ export default function PanelSlot(props: { panel: NonNullable<ShellData["panel"]
|
|
|
58
67
|
<span>{provider.label}</span>
|
|
59
68
|
</h2>
|
|
60
69
|
)}
|
|
70
|
+
{/* The controller mounts the ⋯ menu over this button; the place stays reserved for Core providers. */}
|
|
71
|
+
{hasConsent && (
|
|
72
|
+
<span
|
|
73
|
+
class="cloud-panel__menu"
|
|
74
|
+
data-cloud-panel-menu=""
|
|
75
|
+
style={{ visibility: provider.consent ? "visible" : "hidden" }}
|
|
76
|
+
inert={!provider.consent}
|
|
77
|
+
>
|
|
78
|
+
<IconButton label={t.panelMenu} title={t.panelMenu} aria-haspopup="menu" aria-expanded="false">
|
|
79
|
+
<i class="ti ti-dots" aria-hidden="true" />
|
|
80
|
+
</IconButton>
|
|
81
|
+
</span>
|
|
82
|
+
)}
|
|
61
83
|
<IconButton
|
|
62
84
|
label={t.closePanel}
|
|
63
85
|
title={`${t.closePanel} (${props.platform === "apple" ? "⌘J" : "Ctrl+J"})`}
|
|
@@ -67,6 +89,34 @@ export default function PanelSlot(props: { panel: NonNullable<ShellData["panel"]
|
|
|
67
89
|
</IconButton>
|
|
68
90
|
</div>
|
|
69
91
|
<div class="cloud-panel__body" id="cloud-panel-body" role={tabs ? "tabpanel" : undefined}>
|
|
92
|
+
{hasConsent && (
|
|
93
|
+
// The shell asks, not the provider: the question sits above the provider page, which loads without context.
|
|
94
|
+
<NoticeCard
|
|
95
|
+
class="cloud-panel__consent"
|
|
96
|
+
tone="neutral"
|
|
97
|
+
data-cloud-panel-consent=""
|
|
98
|
+
role="region"
|
|
99
|
+
tabindex="-1"
|
|
100
|
+
aria-labelledby="cloud-panel-consent-title"
|
|
101
|
+
title={
|
|
102
|
+
<span id="cloud-panel-consent-title" data-cloud-panel-consent-title="">
|
|
103
|
+
{t.panelConsentTitle({ app: provider.appName })}
|
|
104
|
+
</span>
|
|
105
|
+
}
|
|
106
|
+
detail={<span data-cloud-panel-consent-body="">{t.panelConsentBody({ app: provider.appName })}</span>}
|
|
107
|
+
>
|
|
108
|
+
<div class="cloud-panel__consent-actions">
|
|
109
|
+
<Button size="sm" variant="primary" data-cloud-panel-consent-answer="allow">
|
|
110
|
+
{t.panelConsentAllow}
|
|
111
|
+
</Button>
|
|
112
|
+
<Button size="sm" variant="secondary" data-cloud-panel-consent-answer="deny">
|
|
113
|
+
{t.panelConsentDeny}
|
|
114
|
+
</Button>
|
|
115
|
+
</div>
|
|
116
|
+
{/* A failed save reports in this reserved line, so the question never grows. */}
|
|
117
|
+
<p class="cloud-panel__consent-error" data-cloud-panel-consent-error="" role="status" />
|
|
118
|
+
</NoticeCard>
|
|
119
|
+
)}
|
|
70
120
|
<div class="cloud-panel__skeleton" aria-hidden="true">
|
|
71
121
|
<span />
|
|
72
122
|
<span />
|
|
@@ -76,7 +126,7 @@ export default function PanelSlot(props: { panel: NonNullable<ShellData["panel"]
|
|
|
76
126
|
{props.state.open && (
|
|
77
127
|
<iframe
|
|
78
128
|
class="cloud-panel__frame"
|
|
79
|
-
src={provider.href}
|
|
129
|
+
src={withPanelContext(provider.href, allowed ? props.panel.context : null)}
|
|
80
130
|
title={provider.label}
|
|
81
131
|
loading="lazy"
|
|
82
132
|
data-cloud-panel-frame={provider.id}
|
|
@@ -13,7 +13,7 @@ import { appWorkspaceCookieName, readAppWorkspaceLayoutCookie } from "../_intern
|
|
|
13
13
|
import { registerContextAwareCommand } from "../browser/command-bridge";
|
|
14
14
|
import { attachCommandShortcuts } from "../browser/command-shortcuts";
|
|
15
15
|
import { navigationSearchItems } from "../browser/navigation-search";
|
|
16
|
-
import type
|
|
16
|
+
import { openPanel, type PanelRequest } from "../browser/panel";
|
|
17
17
|
import { importOnDemand } from "../browser/reload";
|
|
18
18
|
import { registerGlobalSearchHost } from "../browser/search-bridge";
|
|
19
19
|
import { attachShellFocusControls, readShellFocus, SHELL_FOCUS_CHANGE_EVENT, toggleShellFocus } from "../browser/shell-focus";
|
|
@@ -111,12 +111,18 @@ export default function ShellRuntime(props: ShellRuntimeProps) {
|
|
|
111
111
|
event.preventDefault();
|
|
112
112
|
togglePanel();
|
|
113
113
|
};
|
|
114
|
+
// The panel controller loads lazily; it reads this mark to recheck consent after a restore it did not see.
|
|
115
|
+
const restored = (event: PageTransitionEvent) => {
|
|
116
|
+
if (event.persisted) document.documentElement.setAttribute("data-cloud-page-restored", "");
|
|
117
|
+
};
|
|
118
|
+
window.addEventListener("pageshow", restored);
|
|
114
119
|
window.addEventListener("cloud.panel.request", request);
|
|
115
120
|
if (!props.commands) window.addEventListener("keydown", key);
|
|
116
121
|
if (document.querySelector('.cloud-app-canvas[data-panel="open"]')) void load();
|
|
117
122
|
onCleanup(() => {
|
|
118
123
|
stopped = true;
|
|
119
124
|
dispose?.();
|
|
125
|
+
window.removeEventListener("pageshow", restored);
|
|
120
126
|
window.removeEventListener("cloud.panel.request", request);
|
|
121
127
|
window.removeEventListener("keydown", key);
|
|
122
128
|
});
|
|
@@ -209,7 +215,10 @@ export default function ShellRuntime(props: ShellRuntimeProps) {
|
|
|
209
215
|
: command.id === "cloud.search"
|
|
210
216
|
? { search: {} }
|
|
211
217
|
: // Returning the load keeps the Command busy until the dialog is open; a second press opens no second dialog.
|
|
212
|
-
() =>
|
|
218
|
+
() => {
|
|
219
|
+
if (openPanel("core")) return;
|
|
220
|
+
return importOnDemand(() => import("./LayoutHelp")).then((help) => help?.openLayoutHelpDialog());
|
|
221
|
+
},
|
|
213
222
|
}),
|
|
214
223
|
);
|
|
215
224
|
}
|
package/src/ssr/app-color.ts
CHANGED
|
@@ -8,8 +8,9 @@ const fillShares = {
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Opaque theme backgrounds checked for readable app ink; a test pins them to
|
|
11
|
-
* tokens.css and @k2b/ui. `sidebar` is the workspace sidebar
|
|
12
|
-
* (`--k2b-surface
|
|
11
|
+
* tokens.css and @k2b/ui. `sidebar` is the workspace sidebar's own background
|
|
12
|
+
* (`--k2b-surface` since the one-surface workspace); a test reads that
|
|
13
|
+
* declaration from @k2b/ui. `sidebarHover` is its row hover over it; `subtle`,
|
|
13
14
|
* `muted`, `hover`, and `platformSelected` are the `--ui-surface-subtle`,
|
|
14
15
|
* `--ui-surface-muted`, `--ui-hover`, and blue `--ui-selected` fills app ink
|
|
15
16
|
* also sits on.
|
|
@@ -18,8 +19,8 @@ export const appColorSurfaces = {
|
|
|
18
19
|
light: {
|
|
19
20
|
canvas: "#fafafa",
|
|
20
21
|
surface: "#ffffff",
|
|
21
|
-
sidebar: "#
|
|
22
|
-
sidebarHover: "#
|
|
22
|
+
sidebar: "#ffffff",
|
|
23
|
+
sidebarHover: "#f3f3f3",
|
|
23
24
|
subtle: "#f8f8f8",
|
|
24
25
|
muted: "#f3f4f3",
|
|
25
26
|
raised: "#ffffff",
|
|
@@ -29,8 +30,8 @@ export const appColorSurfaces = {
|
|
|
29
30
|
dark: {
|
|
30
31
|
canvas: "#090d12",
|
|
31
32
|
surface: "#11151b",
|
|
32
|
-
sidebar: "#
|
|
33
|
-
sidebarHover: "#
|
|
33
|
+
sidebar: "#11151b",
|
|
34
|
+
sidebarHover: "#1d2026",
|
|
34
35
|
subtle: "#161a20",
|
|
35
36
|
muted: "#20242a",
|
|
36
37
|
raised: "#181d24",
|
|
@@ -72,13 +73,17 @@ export const mixHex = (a: HexColor, b: HexColor, share: number): HexColor => {
|
|
|
72
73
|
.join("")}`;
|
|
73
74
|
};
|
|
74
75
|
|
|
75
|
-
/** Derive selection fills and the nearest AA-readable app ink in each theme. */
|
|
76
|
+
/** Derive selection fills, the shell's open-app fill, and the nearest AA-readable app ink in each theme. */
|
|
76
77
|
export const appColors = (accent: HexColor) => {
|
|
77
78
|
const forTheme = (theme: keyof typeof appColorSurfaces) => {
|
|
78
79
|
const surfaces = appColorSurfaces[theme];
|
|
79
80
|
const selected = mixHex(accent, surfaces.surface, fillShares[theme].selected);
|
|
80
81
|
const selectedNav = mixHex(accent, surfaces.sidebar, fillShares[theme].selectedNav);
|
|
81
|
-
|
|
82
|
+
// Cloud's shell marks the open app on its app-colored bar (global.css):
|
|
83
|
+
// with the surface in light, and in dark with the navigation share over the
|
|
84
|
+
// muted surface, which stays apart from the dark bar.
|
|
85
|
+
const shellSelected = theme === "light" ? surfaces.surface : mixHex(accent, surfaces.muted, fillShares.dark.selectedNav);
|
|
86
|
+
const backgrounds = [...Object.values(surfaces), selected, selectedNav, shellSelected];
|
|
82
87
|
const toward = theme === "light" ? "#000000" : "#ffffff";
|
|
83
88
|
let ink: HexColor = toward;
|
|
84
89
|
for (let i = 100; i >= 0; i--) {
|
|
@@ -88,7 +93,7 @@ export const appColors = (accent: HexColor) => {
|
|
|
88
93
|
break;
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
|
-
return { ink, selected, selectedNav };
|
|
96
|
+
return { ink, selected, selectedNav, shellSelected };
|
|
92
97
|
};
|
|
93
98
|
return { light: forTheme("light"), dark: forTheme("dark") };
|
|
94
99
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isOwnAppPath } from "../_internal/app-path";
|
|
2
2
|
import { hasAnyAppRole } from "../_internal/app-roles";
|
|
3
3
|
import type { RuntimeAppMeta } from "../contracts/app";
|
|
4
|
+
import { panelProviderNeedsConsent } from "../contracts/panel-context-plain";
|
|
4
5
|
import { isPwaPartId } from "../contracts/pwa-paths";
|
|
5
6
|
import type { User } from "../contracts/shared";
|
|
6
7
|
import { resolveAppPresentation } from "../shared/app-presentation";
|
|
@@ -54,7 +55,7 @@ export const visiblePwaParts = (apps: readonly RuntimeAppMeta[], user: User | un
|
|
|
54
55
|
.sort((a, b) => a.name.localeCompare(b.name, locale) || a.id.localeCompare(b.id));
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
export type PanelProvider = { id: string; label: string; icon: string; href: string };
|
|
58
|
+
export type PanelProvider = { id: string; label: string; icon: string; href: string; appName: string; consent: boolean };
|
|
58
59
|
|
|
59
60
|
/** Authorized presentation only: the panel route still authorizes every request. */
|
|
60
61
|
export const visiblePanelProviders = (apps: readonly RuntimeAppMeta[], user: User | undefined, locale: string): PanelProvider[] => {
|
|
@@ -62,8 +63,20 @@ export const visiblePanelProviders = (apps: readonly RuntimeAppMeta[], user: Use
|
|
|
62
63
|
return apps
|
|
63
64
|
.flatMap((app) => {
|
|
64
65
|
if (!app.panel || !isOwnAppPath(app.panel.href, app.routes) || !hasAnyAppRole(user, app.panel.requiresRoles)) return [];
|
|
65
|
-
const
|
|
66
|
-
|
|
66
|
+
const presentation = resolveAppPresentation(app, locale);
|
|
67
|
+
const panel = presentation.panel;
|
|
68
|
+
return panel
|
|
69
|
+
? [
|
|
70
|
+
{
|
|
71
|
+
id: app.id,
|
|
72
|
+
label: panel.label,
|
|
73
|
+
icon: panel.icon,
|
|
74
|
+
href: panel.href,
|
|
75
|
+
appName: presentation.name,
|
|
76
|
+
consent: panelProviderNeedsConsent(app.id),
|
|
77
|
+
},
|
|
78
|
+
]
|
|
79
|
+
: [];
|
|
67
80
|
})
|
|
68
81
|
.sort((a, b) => a.label.localeCompare(b.label, locale) || a.id.localeCompare(b.id));
|
|
69
82
|
};
|
|
@@ -116,30 +116,38 @@ const workspace = (selected: string) =>
|
|
|
116
116
|
* A task-list page: a long list in Main and an open detail that the server renders with its own close
|
|
117
117
|
* control, as Contacts and Capabilities do. Invented content.
|
|
118
118
|
*/
|
|
119
|
-
const listWorkspace = () =>
|
|
119
|
+
const listWorkspace = (head = false) =>
|
|
120
120
|
createComponent(AppWorkspace, {
|
|
121
121
|
class: "min-h-0 flex-1",
|
|
122
122
|
get children() {
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
123
|
+
return [
|
|
124
|
+
head ? createComponent(AppWorkspace.Head, { primary: { label: "New task", icon: "ti ti-plus", href: "?new=task" } }) : null,
|
|
125
|
+
createComponent(AppWorkspace.Content, {
|
|
126
|
+
get children() {
|
|
127
|
+
return [
|
|
128
|
+
createComponent(AppWorkspace.Main, {
|
|
129
|
+
get children() {
|
|
130
|
+
return Array.from({ length: 80 }, (_, index) =>
|
|
131
|
+
createComponent(Button, {
|
|
132
|
+
variant: head ? "ghost" : undefined,
|
|
133
|
+
class: "list-row",
|
|
134
|
+
align: "start",
|
|
135
|
+
children: `Task ${index + 1}`,
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
},
|
|
139
|
+
}),
|
|
140
|
+
createComponent(AppWorkspace.Detail, {
|
|
141
|
+
id: "task-detail",
|
|
142
|
+
open: true,
|
|
143
|
+
get children() {
|
|
144
|
+
return createComponent(Button, { "data-workspace-detail-close": "", children: "Close task" });
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
];
|
|
148
|
+
},
|
|
149
|
+
}),
|
|
150
|
+
];
|
|
143
151
|
},
|
|
144
152
|
});
|
|
145
153
|
|
|
@@ -270,7 +278,7 @@ const server = new Hono()
|
|
|
270
278
|
fullPage: list === "page",
|
|
271
279
|
title: [{ title: "Home", href: "/" }, { title: "Tasks" }],
|
|
272
280
|
get children() {
|
|
273
|
-
return listWorkspace();
|
|
281
|
+
return listWorkspace(c.req.query("head") === "1");
|
|
274
282
|
},
|
|
275
283
|
});
|
|
276
284
|
return () =>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DataTable, prompts, SettingsModal } from "@k2b/ui";
|
|
2
|
+
import { render } from "solid-js/web";
|
|
3
|
+
|
|
4
|
+
const host = document.createElement("div");
|
|
5
|
+
document.querySelector(".cloud-app-canvas")!.append(host);
|
|
6
|
+
|
|
7
|
+
const SelectedTable = () => (
|
|
8
|
+
<DataTable
|
|
9
|
+
rows={[{ id: "selected", name: "Selected file" }]}
|
|
10
|
+
columns={[{ id: "name", header: "Name", value: "name" }]}
|
|
11
|
+
getRowId={(row) => row.id}
|
|
12
|
+
selectedRowId="selected"
|
|
13
|
+
surface="plain"
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
render(
|
|
18
|
+
() => (
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
onClick={() =>
|
|
22
|
+
void prompts.dialog<void>(
|
|
23
|
+
(close) => (
|
|
24
|
+
<div class="dialog-fixed-frame flex min-h-0 flex-col overflow-hidden">
|
|
25
|
+
<SettingsModal title="App settings" onClose={() => close()}>
|
|
26
|
+
<SettingsModal.Tab id="general" title="General">
|
|
27
|
+
<SelectedTable />
|
|
28
|
+
</SettingsModal.Tab>
|
|
29
|
+
<SettingsModal.Tab id="files" title="Files">
|
|
30
|
+
<SelectedTable />
|
|
31
|
+
</SettingsModal.Tab>
|
|
32
|
+
</SettingsModal>
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
{ surface: "bare", header: false, size: "large" },
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
Open app settings
|
|
40
|
+
</button>
|
|
41
|
+
),
|
|
42
|
+
host,
|
|
43
|
+
);
|
package/src/ssr/help-messages.ts
CHANGED
|
@@ -20,6 +20,8 @@ export const helpMessages = i18n.define({
|
|
|
20
20
|
shortcutsInApp: ({ app }: { app: string }) => `In ${app}`,
|
|
21
21
|
shortcutsInThisApp: "In this app",
|
|
22
22
|
shortcutsWithSelection: "With a selection",
|
|
23
|
+
helpNotAvailable: "This Help is not available",
|
|
24
|
+
helpNotAvailableDetail: "You may not have access to it, or it no longer exists.",
|
|
23
25
|
noArticles: "No Help articles here yet",
|
|
24
26
|
reload: "Reload",
|
|
25
27
|
noArticlesDetail: "This app has not published Help yet.",
|
|
@@ -79,6 +81,8 @@ export const helpMessages = i18n.define({
|
|
|
79
81
|
shortcutsInApp: ({ app }) => `In ${app}`,
|
|
80
82
|
shortcutsInThisApp: "In dieser App",
|
|
81
83
|
shortcutsWithSelection: "Mit Auswahl",
|
|
84
|
+
helpNotAvailable: "Diese Hilfe ist nicht verfügbar",
|
|
85
|
+
helpNotAvailableDetail: "Vielleicht hast du keinen Zugriff, oder es gibt sie nicht mehr.",
|
|
82
86
|
noArticles: "Hier gibt es noch keine Hilfeartikel",
|
|
83
87
|
reload: "Neu laden",
|
|
84
88
|
noArticlesDetail: "Diese App hat noch keine Hilfe veröffentlicht.",
|