@skillcdn/console 0.4.0 → 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 +20 -2
- package/SKILLCDN.md +7 -5
- package/dist/api.d.ts +5 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +5 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +579 -45
- 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 +60 -7
- 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 +109 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +67 -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/markdown.d.ts.map +1 -1
- package/dist/components/markdown.js +6 -3
- package/dist/components/markdown.js.map +1 -1
- package/dist/components/pack-connect.d.ts +3 -1
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +4 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/pack-token.d.ts +3 -1
- package/dist/components/pack-token.d.ts.map +1 -1
- package/dist/components/pack-token.js +1 -1
- package/dist/components/pack-token.js.map +1 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +14 -3
- package/dist/components/people.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/remote-pictures.d.ts +6 -0
- package/dist/components/remote-pictures.d.ts.map +1 -0
- package/dist/components/remote-pictures.js +8 -0
- package/dist/components/remote-pictures.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/sign-in.d.ts.map +1 -1
- package/dist/components/sign-in.js +49 -2
- package/dist/components/sign-in.js.map +1 -1
- package/dist/components/ui.d.ts +3 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +6 -3
- package/dist/components/ui.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 +384 -5
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +146 -29
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +11 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +35 -21
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +163 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +174 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +154 -1
- 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/limits.d.ts +14 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +14 -0
- package/dist/limits.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 +6 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +8 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +147 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +55 -5
- 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 +5 -2
- package/dist/web.js.map +1 -1
- package/docs/specs/cli.md +16 -9
- package/docs/specs/plugins.md +487 -0
- package/docs/specs/rest.md +25 -13
- package/package.json +21 -3
- 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 +16 -0
- package/src/cli/cli.ts +736 -45
- package/src/cli/format.ts +356 -9
- package/src/cli/help.ts +71 -6
- 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 +324 -1
- package/src/compat.ts +2 -2
- package/src/components/board.tsx +62 -14
- package/src/components/event-feed.tsx +42 -1
- package/src/components/markdown.tsx +6 -3
- package/src/components/pack-connect.tsx +10 -7
- package/src/components/pack-token.tsx +4 -1
- package/src/components/people.tsx +17 -7
- package/src/components/plugin-frame.tsx +437 -0
- package/src/components/plugin-settings.tsx +1111 -0
- package/src/components/remote-pictures.ts +19 -0
- package/src/components/shell.tsx +82 -14
- package/src/components/sign-in.tsx +114 -13
- package/src/components/ui.tsx +6 -3
- package/src/components/workspace.tsx +18 -0
- package/src/console.tsx +309 -23
- package/src/data.ts +47 -22
- package/src/i18n/en.ts +183 -1
- package/src/i18n/ko.ts +159 -1
- package/src/index.ts +12 -0
- package/src/limits.ts +14 -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 +9 -0
- package/src/schemas.ts +60 -4
- package/src/styles/console.css +384 -5
- package/src/tokens.ts +101 -0
- package/src/vocabulary.ts +30 -0
- package/src/web.ts +5 -2
- 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
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import { REST_ROUTES } from "../routes.js";
|
|
3
|
+
|
|
4
|
+
// Where a picture from elsewhere is read (ADR-0048): through the console, which fetches it and
|
|
5
|
+
// keeps it a while, so that the person's browser reaches nobody's server and the pages load
|
|
6
|
+
// nothing from elsewhere. The composition provides the client's way to it; a component outside
|
|
7
|
+
// one reads it at the API's own path, which a console served beside its API answers.
|
|
8
|
+
|
|
9
|
+
/** The address a picture from elsewhere is read at, given the https URL that names it. */
|
|
10
|
+
export type RemotePictureSrc = (url: string) => string;
|
|
11
|
+
|
|
12
|
+
export const defaultRemotePictureSrc: RemotePictureSrc = (url) =>
|
|
13
|
+
`${REST_ROUTES.pictures}?src=${encodeURIComponent(url)}`;
|
|
14
|
+
|
|
15
|
+
export const RemotePicturesContext = createContext<RemotePictureSrc>(defaultRemotePictureSrc);
|
|
16
|
+
|
|
17
|
+
export function useRemotePictureSrc(): RemotePictureSrc {
|
|
18
|
+
return useContext(RemotePicturesContext);
|
|
19
|
+
}
|
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
|
)}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type MouseEvent, useEffect, useState } from "react";
|
|
2
|
+
import { loginPath, type ProviderKey, type RestProvider, type SignInFailure } from "../api.js";
|
|
2
3
|
import { useWords } from "../i18n/index.js";
|
|
3
4
|
import type { Brand } from "./shell.js";
|
|
4
|
-
import { Callout } from "./ui.js";
|
|
5
|
+
import { Callout, cx } from "./ui.js";
|
|
5
6
|
|
|
6
7
|
// The page a person sees before they are anyone: the way to each provider the deployment has,
|
|
7
|
-
// and what went wrong the last time, when something did.
|
|
8
|
+
// who gets in through it, and what went wrong the last time, when something did.
|
|
8
9
|
|
|
9
10
|
export interface SignInProps {
|
|
10
11
|
readonly title: string;
|
|
@@ -19,6 +20,35 @@ export interface SignInProps {
|
|
|
19
20
|
|
|
20
21
|
export function SignIn(props: SignInProps) {
|
|
21
22
|
const words = useWords().signIn;
|
|
23
|
+
// The way being taken: its button says so from the click until the page leaves, and says so
|
|
24
|
+
// no more when the browser brings this page back from its cache, as on the way back.
|
|
25
|
+
const [taken, setTaken] = useState<ProviderKey | undefined>(undefined);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const shown = (event: PageTransitionEvent): void => {
|
|
28
|
+
if (event.persisted) {
|
|
29
|
+
setTaken(undefined);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
window.addEventListener("pageshow", shown);
|
|
33
|
+
return () => window.removeEventListener("pageshow", shown);
|
|
34
|
+
}, []);
|
|
35
|
+
const take = (provider: ProviderKey) => (event: MouseEvent<HTMLAnchorElement>) => {
|
|
36
|
+
if (taken !== undefined) {
|
|
37
|
+
// One way at a time: a second click, on any of the buttons, goes nowhere.
|
|
38
|
+
event.preventDefault();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
setTaken(provider);
|
|
42
|
+
};
|
|
43
|
+
const admitsOf = (admits: NonNullable<RestProvider["admits"]>): string => {
|
|
44
|
+
if (admits.organization !== null) {
|
|
45
|
+
return words.admits.organization(admits.organization);
|
|
46
|
+
}
|
|
47
|
+
if (admits.domains.length > 0) {
|
|
48
|
+
return words.admits.domains(admits.domains.join(", "));
|
|
49
|
+
}
|
|
50
|
+
return words.admits.listed;
|
|
51
|
+
};
|
|
22
52
|
return (
|
|
23
53
|
<div className="sc-sign-in">
|
|
24
54
|
{props.brand?.symbol !== undefined && (
|
|
@@ -44,18 +74,89 @@ export function SignIn(props: SignInProps) {
|
|
|
44
74
|
</Callout>
|
|
45
75
|
) : (
|
|
46
76
|
<div className="sc-sign-in-ways">
|
|
47
|
-
{props.providers.map((provider) =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
key={provider.key}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
{props.providers.map((provider) => {
|
|
78
|
+
const going = taken === provider.key;
|
|
79
|
+
return (
|
|
80
|
+
<div key={provider.key} className="sc-sign-in-way">
|
|
81
|
+
{/* A real navigation: signing in happens at the provider, not in this page. */}
|
|
82
|
+
<a
|
|
83
|
+
className={cx(
|
|
84
|
+
"sc-button sc-button-primary sc-sign-in-button",
|
|
85
|
+
going && "sc-sign-in-button-going",
|
|
86
|
+
)}
|
|
87
|
+
href={loginPath(provider.key, props.returnTo)}
|
|
88
|
+
aria-busy={going ? true : undefined}
|
|
89
|
+
aria-disabled={taken !== undefined && !going ? true : undefined}
|
|
90
|
+
onClick={take(provider.key)}
|
|
91
|
+
>
|
|
92
|
+
<ProviderMark provider={provider.key} />
|
|
93
|
+
<span className="sc-sign-in-button-label">
|
|
94
|
+
{going ? words.going(provider.label) : words.signInWith(provider.label)}
|
|
95
|
+
</span>
|
|
96
|
+
{going && (
|
|
97
|
+
<span className="sc-spinner sc-sign-in-spinner" aria-hidden="true">
|
|
98
|
+
<span className="sc-spinner-ring" />
|
|
99
|
+
</span>
|
|
100
|
+
)}
|
|
101
|
+
</a>
|
|
102
|
+
{provider.admits !== undefined && (
|
|
103
|
+
<p className="sc-sign-in-admits">{admitsOf(provider.admits)}</p>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
57
108
|
</div>
|
|
58
109
|
)}
|
|
59
110
|
</div>
|
|
60
111
|
);
|
|
61
112
|
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The provider's mark, as people know it from sign-in buttons elsewhere, beside the label and
|
|
116
|
+
* decorative: the host's own mark in the button's text color, Google's lettermark on a light
|
|
117
|
+
* tile, since its colors are its own. A provider without a mark shows the label alone.
|
|
118
|
+
*/
|
|
119
|
+
function ProviderMark(props: { readonly provider: ProviderKey }) {
|
|
120
|
+
if (props.provider === "gh") {
|
|
121
|
+
return (
|
|
122
|
+
<svg
|
|
123
|
+
className="sc-sign-in-mark"
|
|
124
|
+
viewBox="0 0 16 16"
|
|
125
|
+
width="20"
|
|
126
|
+
height="20"
|
|
127
|
+
aria-hidden="true"
|
|
128
|
+
focusable="false"
|
|
129
|
+
>
|
|
130
|
+
<path
|
|
131
|
+
fill="currentColor"
|
|
132
|
+
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"
|
|
133
|
+
/>
|
|
134
|
+
</svg>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (props.provider === "google") {
|
|
138
|
+
return (
|
|
139
|
+
<span className="sc-sign-in-mark sc-sign-in-mark-tile" aria-hidden="true">
|
|
140
|
+
<svg viewBox="0 0 48 48" width="16" height="16" aria-hidden="true" focusable="false">
|
|
141
|
+
<path
|
|
142
|
+
fill="#FFC107"
|
|
143
|
+
d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z"
|
|
144
|
+
/>
|
|
145
|
+
<path
|
|
146
|
+
fill="#FF3D00"
|
|
147
|
+
d="M6.306 14.691l6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 16.318 4 9.656 8.337 6.306 14.691z"
|
|
148
|
+
/>
|
|
149
|
+
<path
|
|
150
|
+
fill="#4CAF50"
|
|
151
|
+
d="M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238A11.91 11.91 0 0 1 24 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z"
|
|
152
|
+
/>
|
|
153
|
+
<path
|
|
154
|
+
fill="#1976D2"
|
|
155
|
+
d="M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 0 1-4.087 5.571l.003-.002 6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z"
|
|
156
|
+
/>
|
|
157
|
+
</svg>
|
|
158
|
+
</span>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
package/src/components/ui.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "../api.js";
|
|
12
12
|
import { dayOf, daysBetween, formatDay, today } from "../dates.js";
|
|
13
13
|
import { useLanguage, useWords } from "../i18n/index.js";
|
|
14
|
+
import { useRemotePictureSrc } from "./remote-pictures.js";
|
|
14
15
|
|
|
15
16
|
// Small building blocks. Each one is a class in styles/console.css and nothing more. Classes
|
|
16
17
|
// are prefixed `sc-` so that a custom console's own styles never collide with them. The words
|
|
@@ -96,13 +97,15 @@ export function personName(person: {
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
/**
|
|
99
|
-
* A person's picture as the git host serves it
|
|
100
|
-
* a whole when there is none or it cannot be loaded,
|
|
100
|
+
* A person's picture as the git host serves it, read through the console (ADR-0048): loaded
|
|
101
|
+
* lazily, without a referrer, and gone as a whole when there is none or it cannot be loaded,
|
|
102
|
+
* leaving the initial of the name instead.
|
|
101
103
|
*/
|
|
102
104
|
export function Avatar(props: {
|
|
103
105
|
readonly person: RestPerson;
|
|
104
106
|
readonly size?: "xs" | "sm" | "md" | "lg";
|
|
105
107
|
}) {
|
|
108
|
+
const remote = useRemotePictureSrc();
|
|
106
109
|
const size = props.size ?? "md";
|
|
107
110
|
const initial = personName(props.person).slice(0, 1).toUpperCase();
|
|
108
111
|
if (props.person.avatar === null) {
|
|
@@ -118,7 +121,7 @@ export function Avatar(props: {
|
|
|
118
121
|
return (
|
|
119
122
|
<img
|
|
120
123
|
className={cx("sc-avatar", `sc-avatar-${size}`)}
|
|
121
|
-
src={props.person.avatar}
|
|
124
|
+
src={remote(props.person.avatar)}
|
|
122
125
|
alt=""
|
|
123
126
|
loading="lazy"
|
|
124
127
|
decoding="async"
|
|
@@ -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>
|