@skillcdn/console 0.4.3 → 0.4.4
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 +4 -4
- package/dist/api.d.ts +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +3 -3
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +95 -20
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +4 -2
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +32 -9
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +17 -14
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +5 -1
- package/dist/cli/plugin-check.d.ts.map +1 -1
- package/dist/cli/plugin-check.js +23 -4
- package/dist/cli/plugin-check.js.map +1 -1
- package/dist/cli/plugin-cli.d.ts.map +1 -1
- package/dist/cli/plugin-cli.js +10 -1
- package/dist/cli/plugin-cli.js.map +1 -1
- package/dist/cli/plugin-init.d.ts +2 -0
- package/dist/cli/plugin-init.d.ts.map +1 -1
- package/dist/cli/plugin-init.js +6 -0
- package/dist/cli/plugin-init.js.map +1 -1
- package/dist/client.d.ts +4 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +3 -3
- package/dist/components/board.js.map +1 -1
- package/dist/components/decision-form.d.ts +6 -0
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +23 -5
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +5 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +25 -5
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +7 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/goals.d.ts.map +1 -1
- package/dist/components/goals.js +2 -2
- package/dist/components/goals.js.map +1 -1
- package/dist/components/home.d.ts +35 -0
- package/dist/components/home.d.ts.map +1 -0
- package/dist/components/home.js +326 -0
- package/dist/components/home.js.map +1 -0
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +5 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/people.d.ts +2 -0
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +15 -2
- package/dist/components/people.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +3 -12
- package/dist/components/plugin-frame.d.ts.map +1 -1
- package/dist/components/plugin-frame.js +53 -20
- package/dist/components/plugin-frame.js.map +1 -1
- package/dist/components/plugin-settings.d.ts.map +1 -1
- package/dist/components/plugin-settings.js +151 -26
- package/dist/components/plugin-settings.js.map +1 -1
- package/dist/components/projects.d.ts +0 -8
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +1 -13
- package/dist/components/projects.js.map +1 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -1
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +5 -2
- package/dist/components/ui.js.map +1 -1
- package/dist/connect-page.d.ts.map +1 -1
- package/dist/connect-page.js +5 -6
- package/dist/connect-page.js.map +1 -1
- package/dist/console.css +761 -191
- package/dist/console.d.ts +10 -3
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +33 -13
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +4 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +5 -0
- package/dist/data.js.map +1 -1
- package/dist/docs-page.d.ts.map +1 -1
- package/dist/docs-page.js +6 -5
- package/dist/docs-page.js.map +1 -1
- package/dist/home-tiles.d.ts +92 -0
- package/dist/home-tiles.d.ts.map +1 -0
- package/dist/home-tiles.js +294 -0
- package/dist/home-tiles.js.map +1 -0
- package/dist/i18n/en.d.ts +174 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +175 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/index.d.ts +4 -2
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +10 -11
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +170 -3
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +6 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +6 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/bridge.d.ts +44 -3
- package/dist/plugin/bridge.d.ts.map +1 -1
- package/dist/plugin/bridge.js +26 -0
- package/dist/plugin/bridge.js.map +1 -1
- package/dist/plugin/capabilities.d.ts +7 -1
- package/dist/plugin/capabilities.d.ts.map +1 -1
- package/dist/plugin/capabilities.js +14 -1
- package/dist/plugin/capabilities.js.map +1 -1
- package/dist/plugin/context.d.ts +18 -1
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/define.d.ts +1 -0
- package/dist/plugin/define.d.ts.map +1 -1
- package/dist/plugin/define.js +1 -1
- package/dist/plugin/define.js.map +1 -1
- package/dist/plugin/limits.d.ts +5 -2
- package/dist/plugin/limits.d.ts.map +1 -1
- package/dist/plugin/limits.js +5 -2
- package/dist/plugin/limits.js.map +1 -1
- package/dist/plugin/manifest.d.ts +83 -6
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +111 -7
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/rest-schemas.d.ts +76 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -1
- package/dist/plugin/rest-schemas.js +23 -2
- package/dist/plugin/rest-schemas.js.map +1 -1
- package/dist/plugin/sandbox.d.ts +4 -3
- package/dist/plugin/sandbox.d.ts.map +1 -1
- package/dist/plugin/sandbox.js +33 -6
- package/dist/plugin/sandbox.js.map +1 -1
- package/dist/plugin/semver.d.ts +26 -0
- package/dist/plugin/semver.d.ts.map +1 -0
- package/dist/plugin/semver.js +76 -0
- package/dist/plugin/semver.js.map +1 -0
- package/dist/plugin/testing.d.ts.map +1 -1
- package/dist/plugin/testing.js +2 -1
- package/dist/plugin/testing.js.map +1 -1
- package/dist/plugin/web.d.ts +10 -1
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +34 -0
- package/dist/plugin/web.js.map +1 -1
- package/dist/plugin.d.ts +3 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -2
- package/dist/plugin.js.map +1 -1
- package/dist/rules.d.ts +2 -2
- package/dist/rules.js +3 -3
- package/dist/schemas.d.ts +497 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +96 -9
- package/dist/schemas.js.map +1 -1
- package/dist/use-action.d.ts +8 -2
- package/dist/use-action.d.ts.map +1 -1
- package/dist/use-action.js +13 -6
- package/dist/use-action.js.map +1 -1
- package/dist/vocabulary.d.ts +3 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +3 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +4 -3
- package/docs/specs/plugins.md +46 -33
- package/docs/specs/rest.md +16 -15
- package/package.json +1 -1
- package/skills/building-a-plugin/SKILL.md +8 -7
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +6 -0
- package/src/cli/cli.ts +124 -19
- package/src/cli/format.ts +42 -9
- package/src/cli/help.ts +17 -13
- package/src/cli/plugin-check.ts +41 -2
- package/src/cli/plugin-cli.ts +10 -1
- package/src/cli/plugin-init.ts +8 -0
- package/src/client.ts +8 -0
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +3 -3
- package/src/components/decision-form.tsx +75 -19
- package/src/components/decision-list.tsx +45 -13
- package/src/components/event-feed.tsx +7 -1
- package/src/components/goals.tsx +4 -1
- package/src/components/home.tsx +615 -0
- package/src/components/pack-connect.tsx +5 -4
- package/src/components/people.tsx +136 -54
- package/src/components/plugin-frame.tsx +62 -63
- package/src/components/plugin-settings.tsx +428 -87
- package/src/components/projects.tsx +0 -55
- package/src/components/task-view.tsx +7 -0
- package/src/components/ui.tsx +6 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +80 -30
- package/src/data.ts +9 -2
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +178 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +174 -3
- package/src/index.ts +7 -5
- package/src/limits.ts +6 -0
- package/src/plugin/bridge.ts +77 -1
- package/src/plugin/capabilities.ts +17 -1
- package/src/plugin/context.ts +18 -1
- package/src/plugin/define.ts +4 -1
- package/src/plugin/limits.ts +5 -2
- package/src/plugin/manifest.ts +154 -10
- package/src/plugin/rest-schemas.ts +27 -1
- package/src/plugin/sandbox.ts +36 -8
- package/src/plugin/semver.ts +89 -0
- package/src/plugin/testing.ts +2 -1
- package/src/plugin/web.ts +49 -0
- package/src/plugin.ts +18 -0
- package/src/rules.ts +3 -3
- package/src/schemas.ts +118 -10
- package/src/styles/console.css +761 -191
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +4 -0
- package/template/plugin/console-plugin.json +3 -1
|
@@ -99,61 +99,6 @@ export function ProjectRoleBadge(props: { readonly role: ProjectRole }) {
|
|
|
99
99
|
);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
export interface ProjectListProps {
|
|
103
|
-
readonly projects: readonly RestProject[];
|
|
104
|
-
/** The link to a project's board, as the composition routes it. */
|
|
105
|
-
readonly projectHref: (project: RestProject) => string;
|
|
106
|
-
readonly onOpen: (project: RestProject) => void;
|
|
107
|
-
readonly empty?: ReactNode;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function ProjectList(props: ProjectListProps) {
|
|
111
|
-
const words = useWords().projects;
|
|
112
|
-
if (props.projects.length === 0) {
|
|
113
|
-
return <>{props.empty ?? null}</>;
|
|
114
|
-
}
|
|
115
|
-
return (
|
|
116
|
-
<ul className="sc-projects" aria-label={words.list}>
|
|
117
|
-
{props.projects.map((project) => (
|
|
118
|
-
<li key={project.id} className="sc-project">
|
|
119
|
-
<div className="sc-project-head">
|
|
120
|
-
<ProjectMark project={project} size="md" />
|
|
121
|
-
<a
|
|
122
|
-
className="sc-project-name"
|
|
123
|
-
href={props.projectHref(project)}
|
|
124
|
-
onClick={(event) => {
|
|
125
|
-
if (event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey) {
|
|
126
|
-
event.preventDefault();
|
|
127
|
-
props.onOpen(project);
|
|
128
|
-
}
|
|
129
|
-
}}
|
|
130
|
-
>
|
|
131
|
-
{project.name}
|
|
132
|
-
</a>
|
|
133
|
-
<code className="sc-project-key">{project.key}</code>
|
|
134
|
-
<ProjectRoleBadge role={project.role} />
|
|
135
|
-
{project.visibility === "private" ? (
|
|
136
|
-
<Badge tone="neutral">{words.private}</Badge>
|
|
137
|
-
) : (
|
|
138
|
-
<Badge tone="warning">{words.public}</Badge>
|
|
139
|
-
)}
|
|
140
|
-
</div>
|
|
141
|
-
{project.description.length > 0 && (
|
|
142
|
-
<p className="sc-project-description">{project.description}</p>
|
|
143
|
-
)}
|
|
144
|
-
{project.openDecisions > 0 && (
|
|
145
|
-
<p className="sc-project-meta">
|
|
146
|
-
<Badge tone="warning" kind="count">
|
|
147
|
-
{words.decisionsWaiting(project.openDecisions)}
|
|
148
|
-
</Badge>
|
|
149
|
-
</p>
|
|
150
|
-
)}
|
|
151
|
-
</li>
|
|
152
|
-
))}
|
|
153
|
-
</ul>
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
102
|
export interface ProjectFormProps {
|
|
158
103
|
/** The project to change; left out, the form makes a new one, with a key. */
|
|
159
104
|
readonly project?: RestProject | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agentLabel } from "../agent-name.js";
|
|
1
2
|
import type {
|
|
2
3
|
RestAnswerInput,
|
|
3
4
|
RestCategory,
|
|
@@ -326,6 +327,12 @@ export function TaskView(props: TaskViewProps) {
|
|
|
326
327
|
<PersonChip person={task.owner} />
|
|
327
328
|
</dd>
|
|
328
329
|
</div>
|
|
330
|
+
{task.agent != null && (
|
|
331
|
+
<div>
|
|
332
|
+
<dt>{words.task.writtenThrough}</dt>
|
|
333
|
+
<dd>{agentLabel(task.agent)}</dd>
|
|
334
|
+
</div>
|
|
335
|
+
)}
|
|
329
336
|
<div>
|
|
330
337
|
<dt>{words.task.workers}</dt>
|
|
331
338
|
<dd>
|
package/src/components/ui.tsx
CHANGED
|
@@ -108,7 +108,10 @@ export function Avatar(props: {
|
|
|
108
108
|
const remote = useRemotePictureSrc();
|
|
109
109
|
const size = props.size ?? "md";
|
|
110
110
|
const initial = personName(props.person).slice(0, 1).toUpperCase();
|
|
111
|
-
|
|
111
|
+
const src = props.person.avatar === null ? undefined : remote(props.person.avatar);
|
|
112
|
+
// A picture that would not load gives way to the initial, and never to a broken image.
|
|
113
|
+
const [failed, setFailed] = useState<string | undefined>(undefined);
|
|
114
|
+
if (src === undefined || src === failed) {
|
|
112
115
|
return (
|
|
113
116
|
<span
|
|
114
117
|
className={cx("sc-avatar", "sc-avatar-initial", `sc-avatar-${size}`)}
|
|
@@ -121,11 +124,12 @@ export function Avatar(props: {
|
|
|
121
124
|
return (
|
|
122
125
|
<img
|
|
123
126
|
className={cx("sc-avatar", `sc-avatar-${size}`)}
|
|
124
|
-
src={
|
|
127
|
+
src={src}
|
|
125
128
|
alt=""
|
|
126
129
|
loading="lazy"
|
|
127
130
|
decoding="async"
|
|
128
131
|
referrerPolicy="no-referrer"
|
|
132
|
+
onError={() => setFailed(src)}
|
|
129
133
|
/>
|
|
130
134
|
);
|
|
131
135
|
}
|
package/src/connect-page.tsx
CHANGED
|
@@ -10,7 +10,7 @@ import { Callout, Spinner } from "./components/ui.js";
|
|
|
10
10
|
import type { ConsoleData } from "./data.js";
|
|
11
11
|
import { useWords } from "./i18n/index.js";
|
|
12
12
|
import { connectHref, type Navigation, PATHS } from "./router.js";
|
|
13
|
-
import { useAction } from "./use-action.js";
|
|
13
|
+
import { errorWords, useAction } from "./use-action.js";
|
|
14
14
|
|
|
15
15
|
// The page where a person connects their agent (ADR-0011): the code typed when the address
|
|
16
16
|
// does not carry one, what asks under it, the approval, and what came of it. Part of the
|
|
@@ -24,7 +24,8 @@ export interface ConnectPageProps {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function ConnectPage(props: ConnectPageProps) {
|
|
27
|
-
const
|
|
27
|
+
const pack = useWords();
|
|
28
|
+
const words = pack.connect;
|
|
28
29
|
const { data, navigation } = props;
|
|
29
30
|
const code = props.code === undefined ? undefined : normalizeConnectCode(props.code);
|
|
30
31
|
const [request, setRequest] = useState<RestConnectRequest | undefined>(undefined);
|
|
@@ -55,13 +56,11 @@ export function ConnectPage(props: ConnectPageProps) {
|
|
|
55
56
|
setProblem(
|
|
56
57
|
failure instanceof ApiError && failure.code === "connect.not_found"
|
|
57
58
|
? words.noSuchCode
|
|
58
|
-
: failure
|
|
59
|
-
? failure.message
|
|
60
|
-
: words.couldNotAsk,
|
|
59
|
+
: errorWords(failure, words.couldNotAsk, pack),
|
|
61
60
|
);
|
|
62
61
|
});
|
|
63
62
|
return () => controller.abort();
|
|
64
|
-
}, [code, connectRequest, words]);
|
|
63
|
+
}, [code, connectRequest, pack, words]);
|
|
65
64
|
|
|
66
65
|
const head = (
|
|
67
66
|
<div className="sc-page-head">
|
package/src/console.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type RestDecisionInput,
|
|
23
23
|
type RestDecisionPatch,
|
|
24
24
|
type RestEvent,
|
|
25
|
+
type RestMe,
|
|
25
26
|
type RestMyDecisions,
|
|
26
27
|
type RestPluginDetail,
|
|
27
28
|
type RestProject,
|
|
@@ -47,18 +48,17 @@ import { DecisionForm } from "./components/decision-form.js";
|
|
|
47
48
|
import { DecisionCard, DecisionList, type DecisionListProps } from "./components/decision-list.js";
|
|
48
49
|
import { EventFeed, type EventFeedProps } from "./components/event-feed.js";
|
|
49
50
|
import { GoalForm, GoalList, type GoalListProps, GoalView, goalMonth } from "./components/goals.js";
|
|
51
|
+
import { HomeGrid, type HomeGridProps } from "./components/home.js";
|
|
50
52
|
import { MyDecisions } from "./components/my-decisions.js";
|
|
51
53
|
import { PackTokenWays } from "./components/pack-token.js";
|
|
52
54
|
import { PAGE_SIZE } from "./components/paged.js";
|
|
53
55
|
import { ListingForm, PeopleList, type PeopleListProps } from "./components/people.js";
|
|
54
|
-
import { PluginFrame
|
|
56
|
+
import { PluginFrame } from "./components/plugin-frame.js";
|
|
55
57
|
import { PluginSettingsList, PluginSettingsView } from "./components/plugin-settings.js";
|
|
56
58
|
import {
|
|
57
59
|
MemberList,
|
|
58
60
|
type MemberListProps,
|
|
59
61
|
ProjectForm,
|
|
60
|
-
ProjectList,
|
|
61
|
-
type ProjectListProps,
|
|
62
62
|
ProjectMark,
|
|
63
63
|
} from "./components/projects.js";
|
|
64
64
|
import { type RemotePictureSrc, RemotePicturesContext } from "./components/remote-pictures.js";
|
|
@@ -100,6 +100,7 @@ import { type ConsoleData, useConsoleData } from "./data.js";
|
|
|
100
100
|
import { monthOf, today } from "./dates.js";
|
|
101
101
|
import { DocsPage } from "./docs-page.js";
|
|
102
102
|
import {
|
|
103
|
+
browserPack,
|
|
103
104
|
chooseLanguage,
|
|
104
105
|
DEFAULT_LANGUAGES,
|
|
105
106
|
LanguageContext,
|
|
@@ -157,7 +158,7 @@ export interface ConsoleComponents {
|
|
|
157
158
|
readonly TokenList: ComponentType<TokenListProps>;
|
|
158
159
|
readonly PeopleList: ComponentType<PeopleListProps>;
|
|
159
160
|
readonly SkillList: ComponentType<SkillListProps>;
|
|
160
|
-
readonly
|
|
161
|
+
readonly HomeGrid: ComponentType<HomeGridProps>;
|
|
161
162
|
readonly MemberList: ComponentType<MemberListProps>;
|
|
162
163
|
readonly Calendar: ComponentType<CalendarProps>;
|
|
163
164
|
readonly GoalList: ComponentType<GoalListProps>;
|
|
@@ -175,7 +176,7 @@ export const DEFAULT_COMPONENTS: ConsoleComponents = {
|
|
|
175
176
|
TokenList,
|
|
176
177
|
PeopleList,
|
|
177
178
|
SkillList,
|
|
178
|
-
|
|
179
|
+
HomeGrid,
|
|
179
180
|
MemberList,
|
|
180
181
|
Calendar,
|
|
181
182
|
GoalList,
|
|
@@ -384,6 +385,21 @@ function holdsToken(data: ConsoleData): boolean {
|
|
|
384
385
|
return data.me !== undefined && data.me.agent !== null;
|
|
385
386
|
}
|
|
386
387
|
|
|
388
|
+
/**
|
|
389
|
+
* The language to keep for a person the pages see for the first time (ADR-0065): the one their
|
|
390
|
+
* browser asks for, so that a plugin or an agent can write for them while they are away. Nothing
|
|
391
|
+
* for a person who has a language, for nobody, and where the pages only fall back on a language.
|
|
392
|
+
*/
|
|
393
|
+
export function languageToKeep(
|
|
394
|
+
me: RestMe | undefined,
|
|
395
|
+
browser: readonly string[],
|
|
396
|
+
packs: readonly LanguagePack[],
|
|
397
|
+
): string | undefined {
|
|
398
|
+
return me?.person !== null && me?.person !== undefined && me.language === null
|
|
399
|
+
? browserPack(browser, packs)?.tag
|
|
400
|
+
: undefined;
|
|
401
|
+
}
|
|
402
|
+
|
|
387
403
|
/**
|
|
388
404
|
* The pages a console that holds a token does not offer: the tokens themselves, and the
|
|
389
405
|
* approval of an agent's connection, which makes one; a person's own doing on the console's
|
|
@@ -413,21 +429,11 @@ function ProjectsPage(props: {
|
|
|
413
429
|
readonly making: boolean;
|
|
414
430
|
}) {
|
|
415
431
|
const words = useWords().projects;
|
|
432
|
+
const homeWords = useWords().home;
|
|
416
433
|
const { data, components, navigation, client, language } = props;
|
|
417
434
|
const me = data.me;
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
me === undefined ? null : (
|
|
421
|
-
<PluginWidgets
|
|
422
|
-
plugins={data.plugins}
|
|
423
|
-
place={place}
|
|
424
|
-
me={me}
|
|
425
|
-
language={language}
|
|
426
|
-
client={client}
|
|
427
|
-
navigation={navigation}
|
|
428
|
-
nameOf={(name) => pluginWordOf(name, language)}
|
|
429
|
-
/>
|
|
430
|
-
);
|
|
435
|
+
// The person arranges the page (ADR-0061): the projects and the widgets, tiles on one grid.
|
|
436
|
+
const [arranging, setArranging] = useState(false);
|
|
431
437
|
const { busy, error, act } = useAction();
|
|
432
438
|
const href = (project: RestProject) => projectHref(project.key);
|
|
433
439
|
// A project is made by an administrator (ADR-0045): a member sees the list alone, the form's
|
|
@@ -438,14 +444,22 @@ function ProjectsPage(props: {
|
|
|
438
444
|
<>
|
|
439
445
|
<div className="sc-page-head">
|
|
440
446
|
<h1 className="sc-page-title">{words.title}</h1>
|
|
441
|
-
{
|
|
442
|
-
<
|
|
443
|
-
{
|
|
444
|
-
|
|
447
|
+
{!arranging && (
|
|
448
|
+
<div className="sc-page-actions">
|
|
449
|
+
{me !== undefined && me.person !== null && (
|
|
450
|
+
<Button size="sm" variant="ghost" onClick={() => setArranging(true)}>
|
|
451
|
+
{homeWords.edit}
|
|
452
|
+
</Button>
|
|
453
|
+
)}
|
|
454
|
+
{administrator && !making && (
|
|
455
|
+
<LinkButton href={PATHS.newProject} variant="primary">
|
|
456
|
+
{words.newProject}
|
|
457
|
+
</LinkButton>
|
|
458
|
+
)}
|
|
459
|
+
</div>
|
|
445
460
|
)}
|
|
446
461
|
</div>
|
|
447
462
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
448
|
-
{widgets("home:top")}
|
|
449
463
|
{making && (
|
|
450
464
|
<section className="sc-panel sc-panel-form" aria-label={words.form.aria}>
|
|
451
465
|
<ProjectForm
|
|
@@ -465,10 +479,30 @@ function ProjectsPage(props: {
|
|
|
465
479
|
/>
|
|
466
480
|
</section>
|
|
467
481
|
)}
|
|
468
|
-
<components.
|
|
482
|
+
<components.HomeGrid
|
|
469
483
|
projects={data.projects}
|
|
484
|
+
plugins={data.plugins}
|
|
485
|
+
home={me?.home}
|
|
486
|
+
editing={arranging}
|
|
487
|
+
onSave={(home) => data.actions.updateMe({ home })}
|
|
488
|
+
onDone={() => setArranging(false)}
|
|
470
489
|
projectHref={href}
|
|
471
490
|
onOpen={(project) => navigation.go(href(project))}
|
|
491
|
+
nameOf={(name) => pluginWordOf(name, language)}
|
|
492
|
+
renderWidget={(tile, size) =>
|
|
493
|
+
me === undefined ? null : (
|
|
494
|
+
<PluginFrame
|
|
495
|
+
plugin={tile.plugin}
|
|
496
|
+
widget={tile.widget.key}
|
|
497
|
+
size={size}
|
|
498
|
+
me={me}
|
|
499
|
+
language={language}
|
|
500
|
+
client={client}
|
|
501
|
+
navigation={navigation}
|
|
502
|
+
title={pluginWordOf(tile.widget.name, language)}
|
|
503
|
+
/>
|
|
504
|
+
)
|
|
505
|
+
}
|
|
472
506
|
empty={
|
|
473
507
|
<EmptyState
|
|
474
508
|
title={words.empty.title}
|
|
@@ -481,7 +515,6 @@ function ProjectsPage(props: {
|
|
|
481
515
|
/>
|
|
482
516
|
}
|
|
483
517
|
/>
|
|
484
|
-
{widgets("home:bottom")}
|
|
485
518
|
</>
|
|
486
519
|
);
|
|
487
520
|
}
|
|
@@ -516,7 +549,7 @@ function PluginPage(props: {
|
|
|
516
549
|
return (
|
|
517
550
|
<>
|
|
518
551
|
{pages.length > 1 && (
|
|
519
|
-
<nav className="sc-
|
|
552
|
+
<nav className="sc-page-tabs" aria-label={pluginWordOf(plugin.name, language)}>
|
|
520
553
|
{pages.map((candidate) => (
|
|
521
554
|
<a
|
|
522
555
|
key={candidate.key}
|
|
@@ -572,9 +605,9 @@ function PluginSettingsPage(props: {
|
|
|
572
605
|
setDetail(await client.plugin(key).get());
|
|
573
606
|
setFailure(undefined);
|
|
574
607
|
} catch (error) {
|
|
575
|
-
setFailure(error
|
|
608
|
+
setFailure(errorWords(error, words.common.somethingWentWrong, words));
|
|
576
609
|
}
|
|
577
|
-
}, [client, key, words
|
|
610
|
+
}, [client, key, words]);
|
|
578
611
|
useEffect(() => {
|
|
579
612
|
setDetail(undefined);
|
|
580
613
|
void read();
|
|
@@ -2506,6 +2539,12 @@ function WorkspacePage(props: {
|
|
|
2506
2539
|
? (person, status) => void act(() => data.actions.updatePerson(person.id, { status }))
|
|
2507
2540
|
: undefined
|
|
2508
2541
|
}
|
|
2542
|
+
onChangeManager={
|
|
2543
|
+
administrator
|
|
2544
|
+
? (person, managerId) =>
|
|
2545
|
+
void act(() => data.actions.updatePerson(person.id, { managerId }))
|
|
2546
|
+
: undefined
|
|
2547
|
+
}
|
|
2509
2548
|
onUnlist={
|
|
2510
2549
|
administrator
|
|
2511
2550
|
? (listing) => void act(() => data.actions.removeListing(listing.id))
|
|
@@ -2592,7 +2631,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2592
2631
|
const failure = signInFailureOf(location.search);
|
|
2593
2632
|
const title = data.me?.workspace.name ?? fallbackTitle;
|
|
2594
2633
|
const viaToken = holdsToken(data);
|
|
2595
|
-
// The language: the person's
|
|
2634
|
+
// The language: the person's own, else the browser's, else the first the console speaks.
|
|
2596
2635
|
const chosen = data.me?.language;
|
|
2597
2636
|
const pack = useMemo(
|
|
2598
2637
|
() =>
|
|
@@ -2605,6 +2644,17 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2605
2644
|
);
|
|
2606
2645
|
const words = pack.messages;
|
|
2607
2646
|
|
|
2647
|
+
const toKeep = languageToKeep(
|
|
2648
|
+
data.me,
|
|
2649
|
+
typeof navigator === "undefined" ? [] : navigator.languages,
|
|
2650
|
+
languages,
|
|
2651
|
+
);
|
|
2652
|
+
useEffect(() => {
|
|
2653
|
+
if (toKeep !== undefined) {
|
|
2654
|
+
void data.actions.updateMe({ language: toKeep }).catch(() => undefined);
|
|
2655
|
+
}
|
|
2656
|
+
}, [toKeep, data.actions]);
|
|
2657
|
+
|
|
2608
2658
|
// An address that moved is shown at its new one, in its place.
|
|
2609
2659
|
useEffect(() => {
|
|
2610
2660
|
if (route.name === "moved") {
|
|
@@ -2632,7 +2682,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2632
2682
|
title={words.common.couldNotReach}
|
|
2633
2683
|
action={<Button onClick={data.reload}>{words.common.tryAgain}</Button>}
|
|
2634
2684
|
>
|
|
2635
|
-
{data.error}
|
|
2685
|
+
{errorWords(data.failure, data.error, words)}
|
|
2636
2686
|
</Callout>
|
|
2637
2687
|
)}
|
|
2638
2688
|
</div>
|
package/src/data.ts
CHANGED
|
@@ -173,7 +173,7 @@ export interface ConsoleActions {
|
|
|
173
173
|
createListing(input: RestListingInput): Promise<RestListing>;
|
|
174
174
|
/** Takes a listing away that nobody signed in with; for an administrator. */
|
|
175
175
|
removeListing(id: string): Promise<void>;
|
|
176
|
-
/** Keeps the person's
|
|
176
|
+
/** Keeps the person's language (ADR-0065) or their front page. */
|
|
177
177
|
updateMe(patch: RestMePatch): Promise<RestMe>;
|
|
178
178
|
/** Changes the workspace's settings (ADR-0035); for an administrator. `me` says them from then on. */
|
|
179
179
|
updateWorkspace(patch: RestWorkspacePatch): Promise<RestWorkspace>;
|
|
@@ -214,8 +214,10 @@ export interface ConsoleActions {
|
|
|
214
214
|
export interface ConsoleData {
|
|
215
215
|
/** Who is signed in and what the board is called; `undefined` until the server has answered. */
|
|
216
216
|
readonly me: RestMe | undefined;
|
|
217
|
-
/** What went wrong asking who is signed in, for a person. */
|
|
217
|
+
/** What went wrong asking who is signed in, for a person, in English. */
|
|
218
218
|
readonly error: string | undefined;
|
|
219
|
+
/** The failure itself, for a page to say in the person's language (`errorWords`). */
|
|
220
|
+
readonly failure?: unknown;
|
|
219
221
|
/** True once the workspace has been loaded for the first time: the projects, the people, the tokens. */
|
|
220
222
|
readonly loaded: boolean;
|
|
221
223
|
/** The projects the person may see, by name. */
|
|
@@ -357,6 +359,7 @@ const wordsOf = (failure: unknown, fallback: string): string =>
|
|
|
357
359
|
export function useConsoleData(client: ConsoleClient, projectKey: string | undefined): ConsoleData {
|
|
358
360
|
const [me, setMe] = useState<RestMe | undefined>(undefined);
|
|
359
361
|
const [error, setError] = useState<string | undefined>(undefined);
|
|
362
|
+
const [failure, setFailure] = useState<unknown>(undefined);
|
|
360
363
|
const [loaded, setLoaded] = useState(false);
|
|
361
364
|
const [projects, setProjects] = useState<readonly RestProject[]>([]);
|
|
362
365
|
const [people, setPeople] = useState<readonly RestPerson[]>([]);
|
|
@@ -461,12 +464,14 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
461
464
|
const controller = new AbortController();
|
|
462
465
|
const { signal } = controller;
|
|
463
466
|
setError(undefined);
|
|
467
|
+
setFailure(undefined);
|
|
464
468
|
const run = async () => {
|
|
465
469
|
let answer: RestMe;
|
|
466
470
|
try {
|
|
467
471
|
answer = await client.me(signal);
|
|
468
472
|
} catch (failure) {
|
|
469
473
|
if (!signal.aborted) {
|
|
474
|
+
setFailure(failure);
|
|
470
475
|
setError(wordsOf(failure, "The server could not be reached."));
|
|
471
476
|
}
|
|
472
477
|
return;
|
|
@@ -492,6 +497,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
492
497
|
setLoaded(true);
|
|
493
498
|
} catch (failure) {
|
|
494
499
|
if (!signal.aborted) {
|
|
500
|
+
setFailure(failure);
|
|
495
501
|
setError(wordsOf(failure, "The workspace could not be loaded."));
|
|
496
502
|
}
|
|
497
503
|
}
|
|
@@ -862,6 +868,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
862
868
|
return {
|
|
863
869
|
me,
|
|
864
870
|
error,
|
|
871
|
+
failure,
|
|
865
872
|
loaded,
|
|
866
873
|
projects,
|
|
867
874
|
people,
|
package/src/docs-page.tsx
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
taskHref,
|
|
31
31
|
} from "./router.js";
|
|
32
32
|
import { projectPath } from "./routes.js";
|
|
33
|
-
import { useAction } from "./use-action.js";
|
|
33
|
+
import { errorWords, useAction } from "./use-action.js";
|
|
34
34
|
|
|
35
35
|
// The Docs page of a project (ADR-0009): the folder or the page the path names, read on its
|
|
36
36
|
// own since the tree may be large, and read again whenever the project's feed grows; and the
|
|
@@ -51,7 +51,8 @@ export interface DocsPageProps {
|
|
|
51
51
|
type Found = RestDocument | null | undefined;
|
|
52
52
|
|
|
53
53
|
export function DocsPage(props: DocsPageProps) {
|
|
54
|
-
const
|
|
54
|
+
const pack = useWords();
|
|
55
|
+
const words = pack.docs;
|
|
55
56
|
const { project, path, view, data, navigation } = props;
|
|
56
57
|
const [listing, setListing] = useState<RestDocuments | undefined>(undefined);
|
|
57
58
|
const [document, setDocument] = useState<Found>(undefined);
|
|
@@ -105,7 +106,7 @@ export function DocsPage(props: DocsPageProps) {
|
|
|
105
106
|
};
|
|
106
107
|
load().catch((failure: unknown) => {
|
|
107
108
|
if (!signal.aborted) {
|
|
108
|
-
setProblem(failure
|
|
109
|
+
setProblem(errorWords(failure, words.couldNotLoad, pack));
|
|
109
110
|
}
|
|
110
111
|
});
|
|
111
112
|
return () => controller.abort();
|
|
@@ -145,11 +146,11 @@ export function DocsPage(props: DocsPageProps) {
|
|
|
145
146
|
})
|
|
146
147
|
.catch((failure: unknown) => {
|
|
147
148
|
if (!controller.signal.aborted) {
|
|
148
|
-
setProblem(failure
|
|
149
|
+
setProblem(errorWords(failure, words.searchFailed, pack));
|
|
149
150
|
}
|
|
150
151
|
});
|
|
151
152
|
return () => controller.abort();
|
|
152
|
-
}, [actions, query, words]);
|
|
153
|
+
}, [actions, pack, query, words]);
|
|
153
154
|
|
|
154
155
|
const toDoc = (target: string) => docHref(key, target);
|
|
155
156
|
const toFolder = (folder: string) => docHref(key, folder);
|