@skillcdn/console 0.4.1 → 0.4.2
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 +19 -2
- package/SKILLCDN.md +7 -5
- package/dist/api.d.ts +4 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -3
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +543 -34
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +34 -3
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +277 -6
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +35 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +21 -0
- package/dist/cli/plugin-check.d.ts.map +1 -0
- package/dist/cli/plugin-check.js +374 -0
- package/dist/cli/plugin-check.js.map +1 -0
- package/dist/cli/plugin-cli.d.ts +20 -0
- package/dist/cli/plugin-cli.d.ts.map +1 -0
- package/dist/cli/plugin-cli.js +123 -0
- package/dist/cli/plugin-cli.js.map +1 -0
- package/dist/cli/plugin-init.d.ts +20 -0
- package/dist/cli/plugin-init.d.ts.map +1 -0
- package/dist/cli/plugin-init.js +74 -0
- package/dist/cli/plugin-init.js.map +1 -0
- package/dist/cli/plugin-main.d.ts +3 -0
- package/dist/cli/plugin-main.d.ts.map +1 -0
- package/dist/cli/plugin-main.js +28 -0
- package/dist/cli/plugin-main.js.map +1 -0
- package/dist/client.d.ts +97 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +63 -2
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +2 -2
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +59 -15
- package/dist/components/board.js.map +1 -1
- package/dist/components/event-feed.d.ts +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +33 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +52 -0
- package/dist/components/plugin-frame.d.ts.map +1 -0
- package/dist/components/plugin-frame.js +314 -0
- package/dist/components/plugin-frame.js.map +1 -0
- package/dist/components/plugin-settings.d.ts +38 -0
- package/dist/components/plugin-settings.d.ts.map +1 -0
- package/dist/components/plugin-settings.js +348 -0
- package/dist/components/plugin-settings.js.map +1 -0
- package/dist/components/shell.d.ts +2 -0
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +26 -1
- package/dist/components/shell.js.map +1 -1
- package/dist/components/workspace.d.ts +7 -1
- package/dist/components/workspace.d.ts.map +1 -1
- package/dist/components/workspace.js +1 -1
- package/dist/components/workspace.js.map +1 -1
- package/dist/console.css +327 -0
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +136 -26
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +7 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +19 -2
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +149 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +160 -0
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +144 -0
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin/args.d.ts +148 -0
- package/dist/plugin/args.d.ts.map +1 -0
- package/dist/plugin/args.js +369 -0
- package/dist/plugin/args.js.map +1 -0
- package/dist/plugin/bridge.d.ts +146 -0
- package/dist/plugin/bridge.d.ts.map +1 -0
- package/dist/plugin/bridge.js +125 -0
- package/dist/plugin/bridge.js.map +1 -0
- package/dist/plugin/capabilities.d.ts +20 -0
- package/dist/plugin/capabilities.d.ts.map +1 -0
- package/dist/plugin/capabilities.js +133 -0
- package/dist/plugin/capabilities.js.map +1 -0
- package/dist/plugin/context.d.ts +374 -0
- package/dist/plugin/context.d.ts.map +1 -0
- package/dist/plugin/context.js +2 -0
- package/dist/plugin/context.js.map +1 -0
- package/dist/plugin/define.d.ts +208 -0
- package/dist/plugin/define.d.ts.map +1 -0
- package/dist/plugin/define.js +482 -0
- package/dist/plugin/define.js.map +1 -0
- package/dist/plugin/definition-error.d.ts +11 -0
- package/dist/plugin/definition-error.d.ts.map +1 -0
- package/dist/plugin/definition-error.js +17 -0
- package/dist/plugin/definition-error.js.map +1 -0
- package/dist/plugin/limits.d.ts +67 -0
- package/dist/plugin/limits.d.ts.map +1 -0
- package/dist/plugin/limits.js +70 -0
- package/dist/plugin/limits.js.map +1 -0
- package/dist/plugin/load-worker.d.ts +7 -0
- package/dist/plugin/load-worker.d.ts.map +1 -0
- package/dist/plugin/load-worker.js +41 -0
- package/dist/plugin/load-worker.js.map +1 -0
- package/dist/plugin/load.d.ts +18 -0
- package/dist/plugin/load.d.ts.map +1 -0
- package/dist/plugin/load.js +62 -0
- package/dist/plugin/load.js.map +1 -0
- package/dist/plugin/manifest.d.ts +237 -0
- package/dist/plugin/manifest.d.ts.map +1 -0
- package/dist/plugin/manifest.js +411 -0
- package/dist/plugin/manifest.js.map +1 -0
- package/dist/plugin/refusals.d.ts +49 -0
- package/dist/plugin/refusals.d.ts.map +1 -0
- package/dist/plugin/refusals.js +79 -0
- package/dist/plugin/refusals.js.map +1 -0
- package/dist/plugin/rest-schemas.d.ts +556 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -0
- package/dist/plugin/rest-schemas.js +262 -0
- package/dist/plugin/rest-schemas.js.map +1 -0
- package/dist/plugin/sandbox.d.ts +23 -0
- package/dist/plugin/sandbox.d.ts.map +1 -0
- package/dist/plugin/sandbox.js +99 -0
- package/dist/plugin/sandbox.js.map +1 -0
- package/dist/plugin/testing.d.ts +45 -0
- package/dist/plugin/testing.d.ts.map +1 -0
- package/dist/plugin/testing.js +420 -0
- package/dist/plugin/testing.js.map +1 -0
- package/dist/plugin/web-react.d.ts +11 -0
- package/dist/plugin/web-react.d.ts.map +1 -0
- package/dist/plugin/web-react.js +34 -0
- package/dist/plugin/web-react.js.map +1 -0
- package/dist/plugin/web.d.ts +95 -0
- package/dist/plugin/web.d.ts.map +1 -0
- package/dist/plugin/web.js +245 -0
- package/dist/plugin/web.js.map +1 -0
- package/dist/plugin-nav.d.ts +29 -0
- package/dist/plugin-nav.d.ts.map +1 -0
- package/dist/plugin-nav.js +92 -0
- package/dist/plugin-nav.js.map +1 -0
- package/dist/plugin-testing.d.ts +2 -0
- package/dist/plugin-testing.d.ts.map +1 -0
- package/dist/plugin-testing.js +4 -0
- package/dist/plugin-testing.js.map +1 -0
- package/dist/plugin-web-react.d.ts +3 -0
- package/dist/plugin-web-react.d.ts.map +1 -0
- package/dist/plugin-web-react.js +3 -0
- package/dist/plugin-web-react.js.map +1 -0
- package/dist/plugin-web.d.ts +4 -0
- package/dist/plugin-web.d.ts.map +1 -0
- package/dist/plugin-web.js +5 -0
- package/dist/plugin-web.js.map +1 -0
- package/dist/plugin.d.ts +11 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +15 -0
- package/dist/plugin.js.map +1 -0
- package/dist/router.d.ts +28 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +47 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +4 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +6 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +117 -0
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +40 -1
- package/dist/schemas.js.map +1 -1
- package/dist/tokens.d.ts +2 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +101 -0
- package/dist/tokens.js.map +1 -0
- package/dist/vocabulary.d.ts +11 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +24 -0
- package/dist/vocabulary.js.map +1 -1
- package/dist/web.d.ts.map +1 -1
- package/dist/web.js +2 -0
- package/dist/web.js.map +1 -1
- package/docs/specs/cli.md +11 -4
- package/docs/specs/plugins.md +487 -0
- package/docs/specs/rest.md +3 -2
- package/package.json +20 -2
- package/skills/building-a-console/SKILL.md +25 -26
- package/skills/building-a-console/references/configuring.md +40 -0
- package/skills/building-a-console/references/the-default-console.md +1 -1
- package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
- package/skills/building-a-plugin/SKILL.md +137 -0
- package/skills/deploying-a-console/SKILL.md +24 -26
- package/skills/deploying-a-console/references/access.md +13 -9
- package/skills/deploying-a-console/references/resources.md +25 -10
- package/skills/deploying-a-console/references/running-it-again.md +15 -0
- package/skills/deploying-a-console/references/sizing.md +2 -0
- package/skills/planning-the-board/SKILL.md +96 -0
- package/skills/planning-the-board/references/a-task.md +36 -0
- package/skills/planning-the-board/references/setting-up.md +44 -0
- package/skills/working-the-board/SKILL.md +43 -34
- package/skills/working-the-board/references/the-pack.md +19 -0
- package/src/api.ts +8 -0
- package/src/cli/cli.ts +697 -33
- package/src/cli/format.ts +356 -9
- package/src/cli/help.ts +41 -2
- package/src/cli/plugin-check.ts +470 -0
- package/src/cli/plugin-cli.ts +148 -0
- package/src/cli/plugin-init.ts +90 -0
- package/src/cli/plugin-main.ts +30 -0
- package/src/client.ts +294 -0
- package/src/compat.ts +2 -2
- package/src/components/board.tsx +62 -14
- package/src/components/event-feed.tsx +42 -1
- package/src/components/plugin-frame.tsx +437 -0
- package/src/components/plugin-settings.tsx +1111 -0
- package/src/components/shell.tsx +82 -14
- package/src/components/workspace.tsx +18 -0
- package/src/console.tsx +286 -19
- package/src/data.ts +27 -2
- package/src/i18n/en.ts +168 -0
- package/src/i18n/ko.ts +148 -0
- package/src/index.ts +12 -0
- package/src/plugin/args.ts +526 -0
- package/src/plugin/bridge.ts +314 -0
- package/src/plugin/capabilities.ts +150 -0
- package/src/plugin/context.ts +416 -0
- package/src/plugin/define.ts +792 -0
- package/src/plugin/definition-error.ts +19 -0
- package/src/plugin/limits.ts +70 -0
- package/src/plugin/load-worker.ts +50 -0
- package/src/plugin/load.ts +82 -0
- package/src/plugin/manifest.ts +561 -0
- package/src/plugin/refusals.ts +94 -0
- package/src/plugin/rest-schemas.ts +354 -0
- package/src/plugin/sandbox.ts +123 -0
- package/src/plugin/testing.ts +612 -0
- package/src/plugin/web-react.ts +45 -0
- package/src/plugin/web.ts +403 -0
- package/src/plugin-nav.ts +132 -0
- package/src/plugin-testing.ts +10 -0
- package/src/plugin-web-react.ts +4 -0
- package/src/plugin-web.ts +35 -0
- package/src/plugin.ts +126 -0
- package/src/router.ts +73 -0
- package/src/routes.ts +7 -0
- package/src/schemas.ts +43 -0
- package/src/styles/console.css +327 -0
- package/src/tokens.ts +101 -0
- package/src/vocabulary.ts +30 -0
- package/src/web.ts +2 -0
- package/template/plugin/.gitignore +2 -0
- package/template/plugin/README.md +26 -0
- package/template/plugin/SKILLCDN.md +13 -0
- package/template/plugin/build.mjs +40 -0
- package/template/plugin/console-plugin.json +50 -0
- package/template/plugin/package.json +27 -0
- package/template/plugin/skills/__key__/SKILL.md +23 -0
- package/template/plugin/src/check.test.ts +34 -0
- package/template/plugin/src/server/index.test.ts +41 -0
- package/template/plugin/src/server/index.ts +72 -0
- package/template/plugin/src/web/index.css +51 -0
- package/template/plugin/src/web/index.ts +115 -0
- package/template/plugin/tsconfig.json +17 -0
- package/skills/working-the-board/references/setting-up.md +0 -3
- /package/skills/{working-the-board → planning-the-board}/references/project-rules.md +0 -0
package/src/components/shell.tsx
CHANGED
|
@@ -13,6 +13,8 @@ export interface NavItem {
|
|
|
13
13
|
readonly current: boolean;
|
|
14
14
|
/** A count beside the label: the decisions waiting, for example. */
|
|
15
15
|
readonly count?: number | undefined;
|
|
16
|
+
/** The pages the tab opens a list of, as a group of plugins does (ADR-0054); the tab itself leads to the first. */
|
|
17
|
+
readonly children?: readonly NavItem[] | undefined;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/** The addresses of a brand's marks (ADR-0013): shown beside the workspace's name, and above it when signing in. */
|
|
@@ -67,6 +69,68 @@ export function isPlainClick(event: {
|
|
|
67
69
|
return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
|
|
68
70
|
}
|
|
69
71
|
|
|
72
|
+
/** A tab that opens a list of pages, as a group of several plugins does: a details, as the person's menu is. Closes once used. */
|
|
73
|
+
function TabGroup(props: { readonly item: NavItem; readonly onNavigate: (href: string) => void }) {
|
|
74
|
+
const details = useRef<HTMLDetailsElement>(null);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
const onClick = (event: MouseEvent) => {
|
|
77
|
+
const element = details.current;
|
|
78
|
+
if (
|
|
79
|
+
element?.open === true &&
|
|
80
|
+
!(event.target instanceof Node && element.contains(event.target))
|
|
81
|
+
) {
|
|
82
|
+
element.open = false;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
document.addEventListener("click", onClick);
|
|
86
|
+
return () => document.removeEventListener("click", onClick);
|
|
87
|
+
}, []);
|
|
88
|
+
const { item } = props;
|
|
89
|
+
return (
|
|
90
|
+
<details className="sc-tab-group" ref={details}>
|
|
91
|
+
<summary
|
|
92
|
+
className={cx("sc-tab", "sc-tab-summary", item.current && "sc-tab-current")}
|
|
93
|
+
aria-current={item.current ? "page" : undefined}
|
|
94
|
+
>
|
|
95
|
+
{item.label}
|
|
96
|
+
<svg className="sc-menu-caret" viewBox="0 0 16 16" aria-hidden="true">
|
|
97
|
+
<path
|
|
98
|
+
d="M4 6l4 4 4-4"
|
|
99
|
+
fill="none"
|
|
100
|
+
stroke="currentColor"
|
|
101
|
+
strokeWidth="1.5"
|
|
102
|
+
strokeLinecap="round"
|
|
103
|
+
strokeLinejoin="round"
|
|
104
|
+
/>
|
|
105
|
+
</svg>
|
|
106
|
+
</summary>
|
|
107
|
+
<div className="sc-menu-panel sc-tab-group-panel">
|
|
108
|
+
<div className="sc-menu-list">
|
|
109
|
+
{(item.children ?? []).map((child) => (
|
|
110
|
+
<a
|
|
111
|
+
key={child.href}
|
|
112
|
+
href={child.href}
|
|
113
|
+
className="sc-menu-link"
|
|
114
|
+
aria-current={child.current ? "page" : undefined}
|
|
115
|
+
onClick={(event) => {
|
|
116
|
+
if (isPlainClick(event)) {
|
|
117
|
+
event.preventDefault();
|
|
118
|
+
if (details.current !== null) {
|
|
119
|
+
details.current.open = false;
|
|
120
|
+
}
|
|
121
|
+
props.onNavigate(child.href);
|
|
122
|
+
}
|
|
123
|
+
}}
|
|
124
|
+
>
|
|
125
|
+
{child.label}
|
|
126
|
+
</a>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</details>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
70
134
|
/** The person's menu: who they are, the workspace's pages, the languages, and the way out. Closes once used. */
|
|
71
135
|
function PersonMenu(props: {
|
|
72
136
|
readonly person: RestPerson;
|
|
@@ -296,20 +360,24 @@ export function Shell(props: ShellProps) {
|
|
|
296
360
|
{props.nav.length > 0 && (
|
|
297
361
|
<nav ref={tabs} className="sc-tabs" aria-label={words.shell.pages}>
|
|
298
362
|
<div className="sc-tabs-inner">
|
|
299
|
-
{props.nav.map((item) =>
|
|
300
|
-
|
|
301
|
-
key={item.href}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
363
|
+
{props.nav.map((item) =>
|
|
364
|
+
item.children !== undefined && item.children.length > 0 ? (
|
|
365
|
+
<TabGroup key={item.href} item={item} onNavigate={props.onNavigate} />
|
|
366
|
+
) : (
|
|
367
|
+
<a
|
|
368
|
+
key={item.href}
|
|
369
|
+
href={item.href}
|
|
370
|
+
className={cx("sc-tab", item.current && "sc-tab-current")}
|
|
371
|
+
aria-current={item.current ? "page" : undefined}
|
|
372
|
+
onClick={follow(item.href)}
|
|
373
|
+
>
|
|
374
|
+
{item.label}
|
|
375
|
+
{item.count !== undefined && item.count > 0 && (
|
|
376
|
+
<span className="sc-nav-count">{item.count}</span>
|
|
377
|
+
)}
|
|
378
|
+
</a>
|
|
379
|
+
),
|
|
380
|
+
)}
|
|
313
381
|
</div>
|
|
314
382
|
</nav>
|
|
315
383
|
)}
|
|
@@ -2,6 +2,7 @@ import { type FormEvent, useId, useMemo, useState } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
PEOPLE_VISIBILITIES,
|
|
4
4
|
type PeopleVisibility,
|
|
5
|
+
type RestMe,
|
|
5
6
|
type RestWorkspace,
|
|
6
7
|
type RestWorkspacePatch,
|
|
7
8
|
WEEK_STARTS,
|
|
@@ -117,6 +118,12 @@ const labelOf = (zone: TimeZoneOption): string =>
|
|
|
117
118
|
|
|
118
119
|
export interface WorkspaceSettingsViewProps {
|
|
119
120
|
readonly workspace: RestWorkspace;
|
|
121
|
+
/**
|
|
122
|
+
* The console's version with the range of command versions it takes, as `GET /api/v1/me`
|
|
123
|
+
* says them (ADR-0025): shown first, so that whoever keeps the agents' commands up to date
|
|
124
|
+
* reads here which console they must match. Left out, nothing is said.
|
|
125
|
+
*/
|
|
126
|
+
readonly console?: RestMe["console"];
|
|
120
127
|
}
|
|
121
128
|
|
|
122
129
|
/** The settings as they stand, one row each; where they are changed is the page's business. */
|
|
@@ -128,6 +135,17 @@ export function WorkspaceSettingsView(props: WorkspaceSettingsViewProps) {
|
|
|
128
135
|
const offset = zone === undefined ? undefined : offsetOf(zone, new Date());
|
|
129
136
|
return (
|
|
130
137
|
<dl className="sc-settings-list">
|
|
138
|
+
{props.console !== undefined && (
|
|
139
|
+
<div className="sc-settings-item">
|
|
140
|
+
<dt>{settings.consoleVersion}</dt>
|
|
141
|
+
<dd>
|
|
142
|
+
{props.console.version}
|
|
143
|
+
<span className="sc-settings-note">
|
|
144
|
+
{settings.consoleTakes(props.console.clientRange)}
|
|
145
|
+
</span>
|
|
146
|
+
</dd>
|
|
147
|
+
</div>
|
|
148
|
+
)}
|
|
131
149
|
<div className="sc-settings-item">
|
|
132
150
|
<dt>{settings.peopleVisibility}</dt>
|
|
133
151
|
<dd>{words.vocabulary.peopleVisibility[peopleVisibilityOf(props.workspace)]}</dd>
|
package/src/console.tsx
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
type RestDecisionPatch,
|
|
24
24
|
type RestEvent,
|
|
25
25
|
type RestMyDecisions,
|
|
26
|
+
type RestPluginDetail,
|
|
26
27
|
type RestProject,
|
|
27
28
|
type RestProjectInput,
|
|
28
29
|
type RestProjectPatch,
|
|
@@ -50,6 +51,8 @@ import { MyDecisions } from "./components/my-decisions.js";
|
|
|
50
51
|
import { PackTokenWays } from "./components/pack-token.js";
|
|
51
52
|
import { PAGE_SIZE } from "./components/paged.js";
|
|
52
53
|
import { ListingForm, PeopleList, type PeopleListProps } from "./components/people.js";
|
|
54
|
+
import { PluginFrame, PluginWidgets } from "./components/plugin-frame.js";
|
|
55
|
+
import { PluginSettingsList, PluginSettingsView } from "./components/plugin-settings.js";
|
|
53
56
|
import {
|
|
54
57
|
MemberList,
|
|
55
58
|
type MemberListProps,
|
|
@@ -104,6 +107,7 @@ import {
|
|
|
104
107
|
type Messages,
|
|
105
108
|
useWords,
|
|
106
109
|
} from "./i18n/index.js";
|
|
110
|
+
import { pluginNavItems, pluginPagesAt, pluginWordOf } from "./plugin-nav.js";
|
|
107
111
|
import {
|
|
108
112
|
boardHref,
|
|
109
113
|
calendarHref,
|
|
@@ -121,6 +125,8 @@ import {
|
|
|
121
125
|
PATHS,
|
|
122
126
|
personAgentsHref,
|
|
123
127
|
personWorkHref,
|
|
128
|
+
pluginHref,
|
|
129
|
+
pluginSettingsHref,
|
|
124
130
|
projectHref,
|
|
125
131
|
projectSettingsHref,
|
|
126
132
|
type Route,
|
|
@@ -393,11 +399,27 @@ function ProjectsPage(props: {
|
|
|
393
399
|
readonly data: ConsoleData;
|
|
394
400
|
readonly components: ConsoleComponents;
|
|
395
401
|
readonly navigation: Navigation;
|
|
402
|
+
readonly client: ConsoleClient;
|
|
403
|
+
readonly language: string;
|
|
396
404
|
/** Whether the form for a new project is open: the page is at its address. */
|
|
397
405
|
readonly making: boolean;
|
|
398
406
|
}) {
|
|
399
407
|
const words = useWords().projects;
|
|
400
|
-
const { data, components, navigation } = props;
|
|
408
|
+
const { data, components, navigation, client, language } = props;
|
|
409
|
+
const me = data.me;
|
|
410
|
+
/** The plugins' widgets at a place of this page (ADR-0054), for the person's roles. */
|
|
411
|
+
const widgets = (place: "home:top" | "home:bottom") =>
|
|
412
|
+
me === undefined ? null : (
|
|
413
|
+
<PluginWidgets
|
|
414
|
+
plugins={data.plugins}
|
|
415
|
+
place={place}
|
|
416
|
+
me={me}
|
|
417
|
+
language={language}
|
|
418
|
+
client={client}
|
|
419
|
+
navigation={navigation}
|
|
420
|
+
nameOf={(name) => pluginWordOf(name, language)}
|
|
421
|
+
/>
|
|
422
|
+
);
|
|
401
423
|
const { busy, error, act } = useAction();
|
|
402
424
|
const href = (project: RestProject) => projectHref(project.key);
|
|
403
425
|
// A project is made by an administrator (ADR-0045): a member sees the list alone, the form's
|
|
@@ -415,6 +437,7 @@ function ProjectsPage(props: {
|
|
|
415
437
|
)}
|
|
416
438
|
</div>
|
|
417
439
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
440
|
+
{widgets("home:top")}
|
|
418
441
|
{making && (
|
|
419
442
|
<section className="sc-panel sc-panel-form" aria-label={words.form.aria}>
|
|
420
443
|
<ProjectForm
|
|
@@ -450,10 +473,150 @@ function ProjectsPage(props: {
|
|
|
450
473
|
/>
|
|
451
474
|
}
|
|
452
475
|
/>
|
|
476
|
+
{widgets("home:bottom")}
|
|
477
|
+
</>
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* A plugin's page (ADR-0054): the sub-tabs of its pages at the level, where it has several, above
|
|
483
|
+
* the frame of the one shown, the first when the address names none. Nothing for a plugin the
|
|
484
|
+
* person has no page of, or that is not on the project.
|
|
485
|
+
*/
|
|
486
|
+
function PluginPage(props: {
|
|
487
|
+
readonly route: Extract<Route, { name: "plugin" }>;
|
|
488
|
+
readonly data: ConsoleData;
|
|
489
|
+
readonly project: RestProject | undefined;
|
|
490
|
+
readonly client: ConsoleClient;
|
|
491
|
+
readonly language: string;
|
|
492
|
+
readonly navigation: Navigation;
|
|
493
|
+
}) {
|
|
494
|
+
const words = useWords();
|
|
495
|
+
const { route, data, project, client, language, navigation } = props;
|
|
496
|
+
const plugin = data.plugins.find(
|
|
497
|
+
(candidate) => candidate.key === route.plugin && candidate.state === "enabled",
|
|
498
|
+
);
|
|
499
|
+
const bound =
|
|
500
|
+
route.level === "workspace" ||
|
|
501
|
+
data.projectPlugins.some((candidate) => candidate.key === route.plugin);
|
|
502
|
+
const pages = plugin === undefined ? [] : pluginPagesAt(plugin, route.level);
|
|
503
|
+
const page = route.page === undefined ? pages[0] : pages.find((p) => p.key === route.page);
|
|
504
|
+
if (plugin === undefined || !bound || page === undefined || data.me === undefined) {
|
|
505
|
+
return <EmptyState title={words.plugins.noPage.title} body={words.plugins.noPage.body} />;
|
|
506
|
+
}
|
|
507
|
+
const projectKey = route.level === "project" ? route.project : undefined;
|
|
508
|
+
return (
|
|
509
|
+
<>
|
|
510
|
+
{pages.length > 1 && (
|
|
511
|
+
<nav className="sc-subtabs" aria-label={pluginWordOf(plugin.name, language)}>
|
|
512
|
+
{pages.map((candidate) => (
|
|
513
|
+
<a
|
|
514
|
+
key={candidate.key}
|
|
515
|
+
href={pluginHref(plugin.key, candidate.key, projectKey)}
|
|
516
|
+
className={candidate.key === page.key ? "sc-tab sc-tab-current" : "sc-tab"}
|
|
517
|
+
aria-current={candidate.key === page.key ? "page" : undefined}
|
|
518
|
+
>
|
|
519
|
+
{pluginWordOf(candidate.name, language)}
|
|
520
|
+
</a>
|
|
521
|
+
))}
|
|
522
|
+
</nav>
|
|
523
|
+
)}
|
|
524
|
+
<PluginFrame
|
|
525
|
+
key={`${plugin.key}:${page.key}:${projectKey ?? ""}`}
|
|
526
|
+
plugin={plugin}
|
|
527
|
+
page={page.key}
|
|
528
|
+
project={project}
|
|
529
|
+
path={route.rest}
|
|
530
|
+
me={data.me}
|
|
531
|
+
language={language}
|
|
532
|
+
client={client}
|
|
533
|
+
navigation={navigation}
|
|
534
|
+
events={project === undefined ? undefined : data.events}
|
|
535
|
+
title={words.plugins.frame(pluginWordOf(page.name, language))}
|
|
536
|
+
/>
|
|
453
537
|
</>
|
|
454
538
|
);
|
|
455
539
|
}
|
|
456
540
|
|
|
541
|
+
/**
|
|
542
|
+
* The plugins' settings (docs/specs/plugins.md, "The settings pages"): the list, or one plugin
|
|
543
|
+
* whole, read again after every change; for an administrator.
|
|
544
|
+
*/
|
|
545
|
+
function PluginSettingsPage(props: {
|
|
546
|
+
readonly plugin: string | undefined;
|
|
547
|
+
readonly data: ConsoleData;
|
|
548
|
+
readonly client: ConsoleClient;
|
|
549
|
+
readonly language: string;
|
|
550
|
+
readonly navigation: Navigation;
|
|
551
|
+
}) {
|
|
552
|
+
const words = useWords();
|
|
553
|
+
const { data, client, language, navigation } = props;
|
|
554
|
+
const settings = words.plugins.settings;
|
|
555
|
+
const administrator = data.me?.person?.role === "admin";
|
|
556
|
+
const [detail, setDetail] = useState<RestPluginDetail | undefined>(undefined);
|
|
557
|
+
const [failure, setFailure] = useState<string | undefined>(undefined);
|
|
558
|
+
const key = props.plugin;
|
|
559
|
+
const read = useCallback(async () => {
|
|
560
|
+
if (key === undefined) {
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
setDetail(await client.plugin(key).get());
|
|
565
|
+
setFailure(undefined);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
setFailure(error instanceof ApiError ? error.message : words.common.somethingWentWrong);
|
|
568
|
+
}
|
|
569
|
+
}, [client, key, words.common.somethingWentWrong]);
|
|
570
|
+
useEffect(() => {
|
|
571
|
+
setDetail(undefined);
|
|
572
|
+
void read();
|
|
573
|
+
}, [read]);
|
|
574
|
+
if (!administrator) {
|
|
575
|
+
return <NothingHere />;
|
|
576
|
+
}
|
|
577
|
+
if (key === undefined) {
|
|
578
|
+
return (
|
|
579
|
+
<>
|
|
580
|
+
<div className="sc-page-head">
|
|
581
|
+
<h1 className="sc-page-title">{settings.title}</h1>
|
|
582
|
+
</div>
|
|
583
|
+
<PluginSettingsList
|
|
584
|
+
plugins={data.plugins}
|
|
585
|
+
language={language}
|
|
586
|
+
href={(plugin) => pluginSettingsHref(plugin.key)}
|
|
587
|
+
/>
|
|
588
|
+
</>
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
if (failure !== undefined) {
|
|
592
|
+
return <EmptyState title={words.common.somethingWentWrong} body={failure} />;
|
|
593
|
+
}
|
|
594
|
+
if (detail === undefined) {
|
|
595
|
+
return (
|
|
596
|
+
<div className="sc-loading">
|
|
597
|
+
<Spinner label={words.common.loading} />
|
|
598
|
+
</div>
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
return (
|
|
602
|
+
<PluginSettingsView
|
|
603
|
+
detail={detail}
|
|
604
|
+
projects={data.projects}
|
|
605
|
+
people={data.people}
|
|
606
|
+
language={language}
|
|
607
|
+
client={client}
|
|
608
|
+
onChanged={async () => {
|
|
609
|
+
await read();
|
|
610
|
+
await data.actions.refreshPlugins();
|
|
611
|
+
}}
|
|
612
|
+
onPurged={() => {
|
|
613
|
+
void data.actions.refreshPlugins();
|
|
614
|
+
navigation.replace(pluginSettingsHref());
|
|
615
|
+
}}
|
|
616
|
+
/>
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
457
620
|
/**
|
|
458
621
|
* The archive of the board (ADR-0029): the tasks put away, newest first, a page at a time from
|
|
459
622
|
* the server, each brought back from here by whoever may work in the project.
|
|
@@ -747,6 +910,8 @@ function ProjectPage(props: {
|
|
|
747
910
|
readonly data: ConsoleData;
|
|
748
911
|
readonly components: ConsoleComponents;
|
|
749
912
|
readonly navigation: Navigation;
|
|
913
|
+
readonly client: ConsoleClient;
|
|
914
|
+
readonly language: string;
|
|
750
915
|
}) {
|
|
751
916
|
const words = useWords();
|
|
752
917
|
const { route, project, data, components, navigation } = props;
|
|
@@ -1313,6 +1478,18 @@ function ProjectPage(props: {
|
|
|
1313
1478
|
<ProjectSettingsPage route={route} project={project} data={data} navigation={navigation} />
|
|
1314
1479
|
);
|
|
1315
1480
|
}
|
|
1481
|
+
if (route.name === "plugin") {
|
|
1482
|
+
return (
|
|
1483
|
+
<PluginPage
|
|
1484
|
+
route={route}
|
|
1485
|
+
data={data}
|
|
1486
|
+
project={project}
|
|
1487
|
+
client={props.client}
|
|
1488
|
+
language={props.language}
|
|
1489
|
+
navigation={navigation}
|
|
1490
|
+
/>
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1316
1493
|
return <NothingHere />;
|
|
1317
1494
|
}
|
|
1318
1495
|
|
|
@@ -1578,9 +1755,10 @@ function WorkspaceSettingsPage(props: {
|
|
|
1578
1755
|
readonly form: "rules" | "workspace" | "pack-token" | undefined;
|
|
1579
1756
|
readonly data: ConsoleData;
|
|
1580
1757
|
readonly navigation: Navigation;
|
|
1758
|
+
readonly language: string;
|
|
1581
1759
|
}) {
|
|
1582
1760
|
const words = useWords();
|
|
1583
|
-
const { form, data, navigation } = props;
|
|
1761
|
+
const { form, data, navigation, language } = props;
|
|
1584
1762
|
const { busy, error, act } = useAction();
|
|
1585
1763
|
const administrator = data.me?.person?.role === "admin";
|
|
1586
1764
|
const workspace = data.me?.workspace;
|
|
@@ -1729,7 +1907,7 @@ function WorkspaceSettingsPage(props: {
|
|
|
1729
1907
|
</a>
|
|
1730
1908
|
)}
|
|
1731
1909
|
</div>
|
|
1732
|
-
<WorkspaceSettingsView workspace={workspace} />
|
|
1910
|
+
<WorkspaceSettingsView workspace={workspace} console={data.me?.console} />
|
|
1733
1911
|
</section>
|
|
1734
1912
|
)}
|
|
1735
1913
|
<section className="sc-panel" aria-label={words.rules.title}>
|
|
@@ -1753,6 +1931,20 @@ function WorkspaceSettingsPage(props: {
|
|
|
1753
1931
|
/>
|
|
1754
1932
|
)}
|
|
1755
1933
|
</section>
|
|
1934
|
+
{administrator && (
|
|
1935
|
+
<section className="sc-panel" aria-label={words.plugins.settings.title}>
|
|
1936
|
+
<h2 className="sc-section-title">{words.plugins.settings.title}</h2>
|
|
1937
|
+
{data.plugins.length === 0 ? (
|
|
1938
|
+
<p className="sc-settings-hint">{words.plugins.settings.empty.body}</p>
|
|
1939
|
+
) : (
|
|
1940
|
+
<PluginSettingsList
|
|
1941
|
+
plugins={data.plugins}
|
|
1942
|
+
language={language}
|
|
1943
|
+
href={(plugin) => pluginSettingsHref(plugin.key)}
|
|
1944
|
+
/>
|
|
1945
|
+
)}
|
|
1946
|
+
</section>
|
|
1947
|
+
)}
|
|
1756
1948
|
<section className="sc-panel" aria-label={words.settings.categories}>
|
|
1757
1949
|
<h2 className="sc-section-title">{words.settings.categories}</h2>
|
|
1758
1950
|
{data.categories.length > 0 && (
|
|
@@ -2174,11 +2366,36 @@ function WorkspacePage(props: {
|
|
|
2174
2366
|
readonly data: ConsoleData;
|
|
2175
2367
|
readonly components: ConsoleComponents;
|
|
2176
2368
|
readonly navigation: Navigation;
|
|
2369
|
+
readonly client: ConsoleClient;
|
|
2370
|
+
readonly language: string;
|
|
2177
2371
|
}) {
|
|
2178
2372
|
const words = useWords();
|
|
2179
|
-
const { route, data, components, navigation } = props;
|
|
2373
|
+
const { route, data, components, navigation, client, language } = props;
|
|
2180
2374
|
const [fresh, setFresh] = useState<RestTokenCreated | undefined>(undefined);
|
|
2181
2375
|
const { busy, error, act } = useAction();
|
|
2376
|
+
if (route.name === "plugin" && route.level === "workspace") {
|
|
2377
|
+
return (
|
|
2378
|
+
<PluginPage
|
|
2379
|
+
route={route}
|
|
2380
|
+
data={data}
|
|
2381
|
+
project={undefined}
|
|
2382
|
+
client={client}
|
|
2383
|
+
language={language}
|
|
2384
|
+
navigation={navigation}
|
|
2385
|
+
/>
|
|
2386
|
+
);
|
|
2387
|
+
}
|
|
2388
|
+
if (route.name === "plugins-settings") {
|
|
2389
|
+
return (
|
|
2390
|
+
<PluginSettingsPage
|
|
2391
|
+
plugin={route.plugin}
|
|
2392
|
+
data={data}
|
|
2393
|
+
client={client}
|
|
2394
|
+
language={language}
|
|
2395
|
+
navigation={navigation}
|
|
2396
|
+
/>
|
|
2397
|
+
);
|
|
2398
|
+
}
|
|
2182
2399
|
|
|
2183
2400
|
if (route.name === "work") {
|
|
2184
2401
|
return <WorkPage data={data} components={components} week={route.week} />;
|
|
@@ -2190,7 +2407,14 @@ function WorkspacePage(props: {
|
|
|
2190
2407
|
return <PersonWorkPage id={route.id} data={data} components={components} week={route.week} />;
|
|
2191
2408
|
}
|
|
2192
2409
|
if (route.name === "workspace-settings") {
|
|
2193
|
-
return
|
|
2410
|
+
return (
|
|
2411
|
+
<WorkspaceSettingsPage
|
|
2412
|
+
form={route.form}
|
|
2413
|
+
data={data}
|
|
2414
|
+
navigation={navigation}
|
|
2415
|
+
language={language}
|
|
2416
|
+
/>
|
|
2417
|
+
);
|
|
2194
2418
|
}
|
|
2195
2419
|
if (route.name === "categories") {
|
|
2196
2420
|
const me = data.me?.person ?? undefined;
|
|
@@ -2453,13 +2677,6 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2453
2677
|
current: route.name === "agents" || route.name === "new-token",
|
|
2454
2678
|
},
|
|
2455
2679
|
]),
|
|
2456
|
-
// The settings are read by everyone: the rules every agent follows, the categories and
|
|
2457
|
-
// what the colours mean, where the skills come from; kept by an administrator.
|
|
2458
|
-
{
|
|
2459
|
-
href: PATHS.settings,
|
|
2460
|
-
label: words.nav.settings,
|
|
2461
|
-
current: route.name === "workspace-settings" || route.name === "categories",
|
|
2462
|
-
},
|
|
2463
2680
|
...pages
|
|
2464
2681
|
.filter((candidate) => candidate.level === "workspace")
|
|
2465
2682
|
.map((candidate) => ({
|
|
@@ -2470,6 +2687,28 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2470
2687
|
route.level === "workspace" &&
|
|
2471
2688
|
route.page === candidate.key,
|
|
2472
2689
|
})),
|
|
2690
|
+
// The plugins' tabs (ADR-0054): the groups that hold a plugin the person has a page of.
|
|
2691
|
+
...pluginNavItems({
|
|
2692
|
+
plugins: data.plugins,
|
|
2693
|
+
level: "workspace",
|
|
2694
|
+
current: {
|
|
2695
|
+
plugin:
|
|
2696
|
+
route.name === "plugin" && route.level === "workspace" ? route.plugin : undefined,
|
|
2697
|
+
},
|
|
2698
|
+
words,
|
|
2699
|
+
language: pack.tag,
|
|
2700
|
+
}),
|
|
2701
|
+
// The settings are read by everyone: the rules every agent follows, the categories and
|
|
2702
|
+
// what the colours mean, where the skills come from, the plugins; kept by an
|
|
2703
|
+
// administrator. Last, after the organization's own tabs and the plugins'.
|
|
2704
|
+
{
|
|
2705
|
+
href: PATHS.settings,
|
|
2706
|
+
label: words.nav.settings,
|
|
2707
|
+
current:
|
|
2708
|
+
route.name === "workspace-settings" ||
|
|
2709
|
+
route.name === "categories" ||
|
|
2710
|
+
route.name === "plugins-settings",
|
|
2711
|
+
},
|
|
2473
2712
|
];
|
|
2474
2713
|
const nav: NavItem[] =
|
|
2475
2714
|
projectKey === undefined
|
|
@@ -2521,12 +2760,6 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2521
2760
|
label: words.nav.members,
|
|
2522
2761
|
current: route.name === "members",
|
|
2523
2762
|
},
|
|
2524
|
-
// Read by every member: what the project is, and what its agents are asked (ADR-0023).
|
|
2525
|
-
{
|
|
2526
|
-
href: projectHref(projectKey, "settings"),
|
|
2527
|
-
label: words.nav.settings,
|
|
2528
|
-
current: route.name === "settings",
|
|
2529
|
-
},
|
|
2530
2763
|
...pages
|
|
2531
2764
|
.filter((candidate) => candidate.level === "project")
|
|
2532
2765
|
.map((candidate) => ({
|
|
@@ -2537,6 +2770,24 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2537
2770
|
route.level === "project" &&
|
|
2538
2771
|
route.page === candidate.key,
|
|
2539
2772
|
})),
|
|
2773
|
+
...pluginNavItems({
|
|
2774
|
+
plugins: data.plugins,
|
|
2775
|
+
level: "project",
|
|
2776
|
+
project: { key: projectKey, plugins: data.projectPlugins },
|
|
2777
|
+
current: {
|
|
2778
|
+
plugin:
|
|
2779
|
+
route.name === "plugin" && route.level === "project" ? route.plugin : undefined,
|
|
2780
|
+
},
|
|
2781
|
+
words,
|
|
2782
|
+
language: pack.tag,
|
|
2783
|
+
}),
|
|
2784
|
+
// Read by every member: what the project is, and what its agents are asked
|
|
2785
|
+
// (ADR-0023). Last, after the organization's own tabs and the plugins'.
|
|
2786
|
+
{
|
|
2787
|
+
href: projectHref(projectKey, "settings"),
|
|
2788
|
+
label: words.nav.settings,
|
|
2789
|
+
current: route.name === "settings",
|
|
2790
|
+
},
|
|
2540
2791
|
];
|
|
2541
2792
|
/** A page of the console's own at the address, or nothing there. */
|
|
2542
2793
|
const customPageOf = (key: string, level: CustomPage["level"]): ReactNode => {
|
|
@@ -2562,6 +2813,8 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2562
2813
|
data={data}
|
|
2563
2814
|
components={components}
|
|
2564
2815
|
navigation={navigation}
|
|
2816
|
+
client={client}
|
|
2817
|
+
language={pack.tag}
|
|
2565
2818
|
making={route.name === "new-project"}
|
|
2566
2819
|
/>
|
|
2567
2820
|
);
|
|
@@ -2574,6 +2827,8 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2574
2827
|
data={data}
|
|
2575
2828
|
components={components}
|
|
2576
2829
|
navigation={navigation}
|
|
2830
|
+
client={client}
|
|
2831
|
+
language={pack.tag}
|
|
2577
2832
|
/>
|
|
2578
2833
|
);
|
|
2579
2834
|
} else if (data.projectError !== undefined) {
|
|
@@ -2600,6 +2855,8 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2600
2855
|
data={data}
|
|
2601
2856
|
components={components}
|
|
2602
2857
|
navigation={navigation}
|
|
2858
|
+
client={client}
|
|
2859
|
+
language={pack.tag}
|
|
2603
2860
|
/>
|
|
2604
2861
|
);
|
|
2605
2862
|
}
|
|
@@ -2632,7 +2889,17 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2632
2889
|
route.name === "calendar" ||
|
|
2633
2890
|
(route.name === "goal" && route.form === undefined) ||
|
|
2634
2891
|
(route.name === "custom" &&
|
|
2635
|
-
pages.some((candidate) => candidate.key === route.page && candidate.wide === true))
|
|
2892
|
+
pages.some((candidate) => candidate.key === route.page && candidate.wide === true)) ||
|
|
2893
|
+
(route.name === "plugin" &&
|
|
2894
|
+
data.plugins.some(
|
|
2895
|
+
(candidate) =>
|
|
2896
|
+
candidate.key === route.plugin &&
|
|
2897
|
+
pluginPagesAt(candidate, route.level).some(
|
|
2898
|
+
(candidate, index) =>
|
|
2899
|
+
candidate.wide &&
|
|
2900
|
+
(route.page === undefined ? index === 0 : candidate.key === route.page),
|
|
2901
|
+
),
|
|
2902
|
+
))
|
|
2636
2903
|
}
|
|
2637
2904
|
onNavigate={(href) => navigation.go(href)}
|
|
2638
2905
|
onSignOut={viaToken ? undefined : () => void data.actions.signOut()}
|