@skillcdn/console 0.4.2 → 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 +120 -33
- 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 +38 -10
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +22 -15
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +10 -1
- package/dist/cli/plugin-check.d.ts.map +1 -1
- package/dist/cli/plugin-check.js +45 -4
- package/dist/cli/plugin-check.js.map +1 -1
- package/dist/cli/plugin-cli.d.ts +1 -1
- package/dist/cli/plugin-cli.d.ts.map +1 -1
- package/dist/cli/plugin-cli.js +80 -5
- package/dist/cli/plugin-cli.js.map +1 -1
- package/dist/cli/plugin-init.d.ts +19 -0
- package/dist/cli/plugin-init.d.ts.map +1 -1
- package/dist/cli/plugin-init.js +141 -3
- package/dist/cli/plugin-init.js.map +1 -1
- package/dist/client.d.ts +7 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +18 -5
- 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/comments.d.ts.map +1 -1
- package/dist/components/comments.js +1 -1
- package/dist/components/comments.js.map +1 -1
- package/dist/components/decision-form.d.ts +8 -1
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +57 -7
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +9 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +53 -7
- 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/my-decisions.d.ts +2 -0
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +2 -2
- package/dist/components/my-decisions.js.map +1 -1
- 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 +2 -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 +764 -192
- package/dist/console.d.ts +10 -3
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +49 -28
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +6 -3
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +31 -7
- 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 +180 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +181 -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 +176 -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 +8 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +8 -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 +24 -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 +574 -11
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +154 -25
- 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 +49 -35
- package/docs/specs/rest.md +20 -18
- package/package.json +7 -7
- package/skills/building-a-plugin/SKILL.md +10 -9
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +10 -0
- package/src/cli/cli.ts +162 -39
- package/src/cli/format.ts +54 -10
- package/src/cli/help.ts +23 -14
- package/src/cli/plugin-check.ts +68 -2
- package/src/cli/plugin-cli.ts +104 -6
- package/src/cli/plugin-init.ts +178 -3
- package/src/client.ts +36 -9
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +3 -3
- package/src/components/comments.tsx +1 -0
- package/src/components/decision-form.tsx +166 -29
- package/src/components/decision-list.tsx +116 -15
- 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/my-decisions.tsx +4 -1
- 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 +9 -1
- package/src/components/ui.tsx +6 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +98 -42
- package/src/data.ts +41 -11
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +185 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +181 -3
- package/src/index.ts +7 -5
- package/src/limits.ts +8 -0
- package/src/plugin/bridge.ts +77 -1
- package/src/plugin/capabilities.ts +17 -1
- package/src/plugin/context.ts +24 -2
- 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 +190 -28
- package/src/styles/console.css +764 -192
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +4 -0
- package/template/plugin/console-plugin.json +3 -1
- package/template/plugin/package.json +3 -3
|
@@ -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,
|
|
@@ -83,7 +84,8 @@ export interface TaskViewProps {
|
|
|
83
84
|
readonly error?: string | undefined;
|
|
84
85
|
readonly onChange: (task: RestTask, patch: RestTaskInput) => void;
|
|
85
86
|
readonly onMove: (task: RestTask, state: TaskState) => void;
|
|
86
|
-
|
|
87
|
+
/** Called with a decision raised about the task, and the files put on it (ADR-0055). */
|
|
88
|
+
readonly onRaiseDecision: (input: RestDecisionInput, files: readonly File[]) => void;
|
|
87
89
|
readonly onAnswer: (decision: RestDecision, input: RestAnswerInput) => void;
|
|
88
90
|
/** Called to grow a decision's record with what followed. */
|
|
89
91
|
readonly onUpdateDecision?:
|
|
@@ -325,6 +327,12 @@ export function TaskView(props: TaskViewProps) {
|
|
|
325
327
|
<PersonChip person={task.owner} />
|
|
326
328
|
</dd>
|
|
327
329
|
</div>
|
|
330
|
+
{task.agent != null && (
|
|
331
|
+
<div>
|
|
332
|
+
<dt>{words.task.writtenThrough}</dt>
|
|
333
|
+
<dd>{agentLabel(task.agent)}</dd>
|
|
334
|
+
</div>
|
|
335
|
+
)}
|
|
328
336
|
<div>
|
|
329
337
|
<dt>{words.task.workers}</dt>
|
|
330
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,
|
|
@@ -356,6 +357,14 @@ function useFollowLinks(navigation: Navigation): void {
|
|
|
356
357
|
}
|
|
357
358
|
|
|
358
359
|
/** The key of the project a route is on, or nothing. */
|
|
360
|
+
/** The files chosen or pasted on a page, as the client sends them: each by its name, its bytes and its type where the browser knows one. */
|
|
361
|
+
const uploadsOf = (files: readonly File[]): FileUpload[] =>
|
|
362
|
+
files.map((file) => ({
|
|
363
|
+
name: file.name,
|
|
364
|
+
bytes: file,
|
|
365
|
+
...(file.type === "" ? {} : { contentType: file.type }),
|
|
366
|
+
}));
|
|
367
|
+
|
|
359
368
|
const projectOf = (route: Route): string | undefined =>
|
|
360
369
|
"project" in route ? route.project : undefined;
|
|
361
370
|
|
|
@@ -376,6 +385,21 @@ function holdsToken(data: ConsoleData): boolean {
|
|
|
376
385
|
return data.me !== undefined && data.me.agent !== null;
|
|
377
386
|
}
|
|
378
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
|
+
|
|
379
403
|
/**
|
|
380
404
|
* The pages a console that holds a token does not offer: the tokens themselves, and the
|
|
381
405
|
* approval of an agent's connection, which makes one; a person's own doing on the console's
|
|
@@ -405,21 +429,11 @@ function ProjectsPage(props: {
|
|
|
405
429
|
readonly making: boolean;
|
|
406
430
|
}) {
|
|
407
431
|
const words = useWords().projects;
|
|
432
|
+
const homeWords = useWords().home;
|
|
408
433
|
const { data, components, navigation, client, language } = props;
|
|
409
434
|
const me = data.me;
|
|
410
|
-
|
|
411
|
-
const
|
|
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
|
-
);
|
|
435
|
+
// The person arranges the page (ADR-0061): the projects and the widgets, tiles on one grid.
|
|
436
|
+
const [arranging, setArranging] = useState(false);
|
|
423
437
|
const { busy, error, act } = useAction();
|
|
424
438
|
const href = (project: RestProject) => projectHref(project.key);
|
|
425
439
|
// A project is made by an administrator (ADR-0045): a member sees the list alone, the form's
|
|
@@ -430,14 +444,22 @@ function ProjectsPage(props: {
|
|
|
430
444
|
<>
|
|
431
445
|
<div className="sc-page-head">
|
|
432
446
|
<h1 className="sc-page-title">{words.title}</h1>
|
|
433
|
-
{
|
|
434
|
-
<
|
|
435
|
-
{
|
|
436
|
-
|
|
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>
|
|
437
460
|
)}
|
|
438
461
|
</div>
|
|
439
462
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
440
|
-
{widgets("home:top")}
|
|
441
463
|
{making && (
|
|
442
464
|
<section className="sc-panel sc-panel-form" aria-label={words.form.aria}>
|
|
443
465
|
<ProjectForm
|
|
@@ -457,10 +479,30 @@ function ProjectsPage(props: {
|
|
|
457
479
|
/>
|
|
458
480
|
</section>
|
|
459
481
|
)}
|
|
460
|
-
<components.
|
|
482
|
+
<components.HomeGrid
|
|
461
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)}
|
|
462
489
|
projectHref={href}
|
|
463
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
|
+
}
|
|
464
506
|
empty={
|
|
465
507
|
<EmptyState
|
|
466
508
|
title={words.empty.title}
|
|
@@ -473,7 +515,6 @@ function ProjectsPage(props: {
|
|
|
473
515
|
/>
|
|
474
516
|
}
|
|
475
517
|
/>
|
|
476
|
-
{widgets("home:bottom")}
|
|
477
518
|
</>
|
|
478
519
|
);
|
|
479
520
|
}
|
|
@@ -508,7 +549,7 @@ function PluginPage(props: {
|
|
|
508
549
|
return (
|
|
509
550
|
<>
|
|
510
551
|
{pages.length > 1 && (
|
|
511
|
-
<nav className="sc-
|
|
552
|
+
<nav className="sc-page-tabs" aria-label={pluginWordOf(plugin.name, language)}>
|
|
512
553
|
{pages.map((candidate) => (
|
|
513
554
|
<a
|
|
514
555
|
key={candidate.key}
|
|
@@ -564,9 +605,9 @@ function PluginSettingsPage(props: {
|
|
|
564
605
|
setDetail(await client.plugin(key).get());
|
|
565
606
|
setFailure(undefined);
|
|
566
607
|
} catch (error) {
|
|
567
|
-
setFailure(error
|
|
608
|
+
setFailure(errorWords(error, words.common.somethingWentWrong, words));
|
|
568
609
|
}
|
|
569
|
-
}, [client, key, words
|
|
610
|
+
}, [client, key, words]);
|
|
570
611
|
useEffect(() => {
|
|
571
612
|
setDetail(undefined);
|
|
572
613
|
void read();
|
|
@@ -863,9 +904,9 @@ function TaskPage(props: {
|
|
|
863
904
|
})
|
|
864
905
|
}
|
|
865
906
|
onMove={(target, state) => void act(() => data.actions.updateTask(target.id, { state }))}
|
|
866
|
-
onRaiseDecision={(input) =>
|
|
907
|
+
onRaiseDecision={(input, files) =>
|
|
867
908
|
void act(async () => {
|
|
868
|
-
await data.actions.raiseDecision(input);
|
|
909
|
+
await data.actions.raiseDecision(input, uploadsOf(files));
|
|
869
910
|
navigation.replace(here);
|
|
870
911
|
})
|
|
871
912
|
}
|
|
@@ -889,14 +930,7 @@ function TaskPage(props: {
|
|
|
889
930
|
}
|
|
890
931
|
onComment={(draft) =>
|
|
891
932
|
void act(async () => {
|
|
892
|
-
await data.actions.comment(task.id, {
|
|
893
|
-
body: draft.body,
|
|
894
|
-
files: draft.files.map((file) => ({
|
|
895
|
-
name: file.name,
|
|
896
|
-
bytes: file,
|
|
897
|
-
...(file.type === "" ? {} : { contentType: file.type }),
|
|
898
|
-
})),
|
|
899
|
-
});
|
|
933
|
+
await data.actions.comment(task.id, { body: draft.body, files: uploadsOf(draft.files) });
|
|
900
934
|
setComments(await taskComments(task.id));
|
|
901
935
|
})
|
|
902
936
|
}
|
|
@@ -1311,6 +1345,7 @@ function ProjectPage(props: {
|
|
|
1311
1345
|
tasks={tasksById}
|
|
1312
1346
|
taskHref={href}
|
|
1313
1347
|
docHref={toDoc}
|
|
1348
|
+
fileHref={(fileId) => projectPath(key, "files", fileId)}
|
|
1314
1349
|
decisionHref={(decision) => decisionHref(key, decision.id)}
|
|
1315
1350
|
page={route.page}
|
|
1316
1351
|
pageHref={(page) => decisionsHref(key, page)}
|
|
@@ -1337,9 +1372,9 @@ function ProjectPage(props: {
|
|
|
1337
1372
|
deciders={data.members.map((member) => member.person)}
|
|
1338
1373
|
me={data.me?.person ?? undefined}
|
|
1339
1374
|
busy={busy}
|
|
1340
|
-
onSubmit={(input: RestDecisionInput) =>
|
|
1375
|
+
onSubmit={(input: RestDecisionInput, files: readonly File[]) =>
|
|
1341
1376
|
void act(async () => {
|
|
1342
|
-
const made = await data.actions.raiseDecision(input);
|
|
1377
|
+
const made = await data.actions.raiseDecision(input, uploadsOf(files));
|
|
1343
1378
|
navigation.replace(decisionHref(key, made.id));
|
|
1344
1379
|
})
|
|
1345
1380
|
}
|
|
@@ -1366,6 +1401,7 @@ function ProjectPage(props: {
|
|
|
1366
1401
|
task={decision.taskId === null ? undefined : tasksById.get(decision.taskId)}
|
|
1367
1402
|
taskHref={href}
|
|
1368
1403
|
docHref={toDoc}
|
|
1404
|
+
fileHref={(fileId) => projectPath(key, "files", fileId)}
|
|
1369
1405
|
onAnswer={onAnswer}
|
|
1370
1406
|
onUpdate={onUpdate}
|
|
1371
1407
|
busy={busy}
|
|
@@ -2301,6 +2337,7 @@ function MyDecisionsPage(props: { readonly data: ConsoleData }) {
|
|
|
2301
2337
|
addressed={{ items: addressed.items, more: addressed.more, onMore: addressed.readMore }}
|
|
2302
2338
|
raised={{ items: raised.items, more: raised.more, onMore: raised.readMore }}
|
|
2303
2339
|
decisionHref={decisionHref}
|
|
2340
|
+
fileHref={(projectKey, fileId) => projectPath(projectKey, "files", fileId)}
|
|
2304
2341
|
onAnswer={onAnswer}
|
|
2305
2342
|
busy={busy || addressed.busy || raised.busy}
|
|
2306
2343
|
/>
|
|
@@ -2502,6 +2539,12 @@ function WorkspacePage(props: {
|
|
|
2502
2539
|
? (person, status) => void act(() => data.actions.updatePerson(person.id, { status }))
|
|
2503
2540
|
: undefined
|
|
2504
2541
|
}
|
|
2542
|
+
onChangeManager={
|
|
2543
|
+
administrator
|
|
2544
|
+
? (person, managerId) =>
|
|
2545
|
+
void act(() => data.actions.updatePerson(person.id, { managerId }))
|
|
2546
|
+
: undefined
|
|
2547
|
+
}
|
|
2505
2548
|
onUnlist={
|
|
2506
2549
|
administrator
|
|
2507
2550
|
? (listing) => void act(() => data.actions.removeListing(listing.id))
|
|
@@ -2575,6 +2618,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2575
2618
|
const languages = config.languages ?? DEFAULT_LANGUAGES;
|
|
2576
2619
|
const pages = config.pages ?? [];
|
|
2577
2620
|
|
|
2621
|
+
// biome-ignore lint/correctness/noComponentHookFactories: createConsole runs once, when a console is built, never during a render; App is that console's one root component, closing over its config.
|
|
2578
2622
|
function App(props: { readonly initialPath?: string | undefined }) {
|
|
2579
2623
|
const { location, navigation } = useNavigation(props.initialPath);
|
|
2580
2624
|
useFollowLinks(navigation);
|
|
@@ -2587,7 +2631,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2587
2631
|
const failure = signInFailureOf(location.search);
|
|
2588
2632
|
const title = data.me?.workspace.name ?? fallbackTitle;
|
|
2589
2633
|
const viaToken = holdsToken(data);
|
|
2590
|
-
// The language: the person's
|
|
2634
|
+
// The language: the person's own, else the browser's, else the first the console speaks.
|
|
2591
2635
|
const chosen = data.me?.language;
|
|
2592
2636
|
const pack = useMemo(
|
|
2593
2637
|
() =>
|
|
@@ -2600,6 +2644,17 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2600
2644
|
);
|
|
2601
2645
|
const words = pack.messages;
|
|
2602
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
|
+
|
|
2603
2658
|
// An address that moved is shown at its new one, in its place.
|
|
2604
2659
|
useEffect(() => {
|
|
2605
2660
|
if (route.name === "moved") {
|
|
@@ -2627,7 +2682,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2627
2682
|
title={words.common.couldNotReach}
|
|
2628
2683
|
action={<Button onClick={data.reload}>{words.common.tryAgain}</Button>}
|
|
2629
2684
|
>
|
|
2630
|
-
{data.error}
|
|
2685
|
+
{errorWords(data.failure, data.error, words)}
|
|
2631
2686
|
</Callout>
|
|
2632
2687
|
)}
|
|
2633
2688
|
</div>
|
|
@@ -2659,6 +2714,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2659
2714
|
href: PATHS.decisions,
|
|
2660
2715
|
label: words.nav.myDecisions,
|
|
2661
2716
|
current: route.name === "my-decisions",
|
|
2717
|
+
count: data.me?.decisions?.waiting,
|
|
2662
2718
|
},
|
|
2663
2719
|
{
|
|
2664
2720
|
href: PATHS.people,
|
package/src/data.ts
CHANGED
|
@@ -125,7 +125,8 @@ export interface ConsoleActions {
|
|
|
125
125
|
page: { readonly limit: number; readonly offset: number },
|
|
126
126
|
signal?: AbortSignal,
|
|
127
127
|
): Promise<RestGoals>;
|
|
128
|
-
|
|
128
|
+
/** Raises a decision in the project; with `files`, the files put on it (ADR-0055). */
|
|
129
|
+
raiseDecision(input: RestDecisionInput, files?: readonly FileUpload[]): Promise<RestDecision>;
|
|
129
130
|
answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
130
131
|
/** Answers a decision of any project the person may see, by its project's key: for the person's own page of decisions (ADR-0042). */
|
|
131
132
|
answerDecisionIn(projectKey: string, id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
@@ -172,7 +173,7 @@ export interface ConsoleActions {
|
|
|
172
173
|
createListing(input: RestListingInput): Promise<RestListing>;
|
|
173
174
|
/** Takes a listing away that nobody signed in with; for an administrator. */
|
|
174
175
|
removeListing(id: string): Promise<void>;
|
|
175
|
-
/** Keeps the person's
|
|
176
|
+
/** Keeps the person's language (ADR-0065) or their front page. */
|
|
176
177
|
updateMe(patch: RestMePatch): Promise<RestMe>;
|
|
177
178
|
/** Changes the workspace's settings (ADR-0035); for an administrator. `me` says them from then on. */
|
|
178
179
|
updateWorkspace(patch: RestWorkspacePatch): Promise<RestWorkspace>;
|
|
@@ -213,8 +214,10 @@ export interface ConsoleActions {
|
|
|
213
214
|
export interface ConsoleData {
|
|
214
215
|
/** Who is signed in and what the board is called; `undefined` until the server has answered. */
|
|
215
216
|
readonly me: RestMe | undefined;
|
|
216
|
-
/** 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. */
|
|
217
218
|
readonly error: string | undefined;
|
|
219
|
+
/** The failure itself, for a page to say in the person's language (`errorWords`). */
|
|
220
|
+
readonly failure?: unknown;
|
|
218
221
|
/** True once the workspace has been loaded for the first time: the projects, the people, the tokens. */
|
|
219
222
|
readonly loaded: boolean;
|
|
220
223
|
/** The projects the person may see, by name. */
|
|
@@ -356,6 +359,7 @@ const wordsOf = (failure: unknown, fallback: string): string =>
|
|
|
356
359
|
export function useConsoleData(client: ConsoleClient, projectKey: string | undefined): ConsoleData {
|
|
357
360
|
const [me, setMe] = useState<RestMe | undefined>(undefined);
|
|
358
361
|
const [error, setError] = useState<string | undefined>(undefined);
|
|
362
|
+
const [failure, setFailure] = useState<unknown>(undefined);
|
|
359
363
|
const [loaded, setLoaded] = useState(false);
|
|
360
364
|
const [projects, setProjects] = useState<readonly RestProject[]>([]);
|
|
361
365
|
const [people, setPeople] = useState<readonly RestPerson[]>([]);
|
|
@@ -382,6 +386,20 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
382
386
|
|
|
383
387
|
const reload = useCallback(() => setGeneration((current) => current + 1), []);
|
|
384
388
|
|
|
389
|
+
/**
|
|
390
|
+
* Asks again who is signed in, for what moves with the board: the decisions waiting for the
|
|
391
|
+
* person, said beside My decisions. What fails stays as it was.
|
|
392
|
+
*/
|
|
393
|
+
const refreshMe = useCallback(
|
|
394
|
+
async (signal?: AbortSignal) => {
|
|
395
|
+
const answer = await client.me(signal);
|
|
396
|
+
if (signal?.aborted !== true) {
|
|
397
|
+
setMe(answer);
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
[client],
|
|
401
|
+
);
|
|
402
|
+
|
|
385
403
|
/** Loads the workspace's lists again. What fails stays as it was. */
|
|
386
404
|
const refreshWorkspace = useCallback(
|
|
387
405
|
async (signal?: AbortSignal) => {
|
|
@@ -446,12 +464,14 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
446
464
|
const controller = new AbortController();
|
|
447
465
|
const { signal } = controller;
|
|
448
466
|
setError(undefined);
|
|
467
|
+
setFailure(undefined);
|
|
449
468
|
const run = async () => {
|
|
450
469
|
let answer: RestMe;
|
|
451
470
|
try {
|
|
452
471
|
answer = await client.me(signal);
|
|
453
472
|
} catch (failure) {
|
|
454
473
|
if (!signal.aborted) {
|
|
474
|
+
setFailure(failure);
|
|
455
475
|
setError(wordsOf(failure, "The server could not be reached."));
|
|
456
476
|
}
|
|
457
477
|
return;
|
|
@@ -477,6 +497,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
477
497
|
setLoaded(true);
|
|
478
498
|
} catch (failure) {
|
|
479
499
|
if (!signal.aborted) {
|
|
500
|
+
setFailure(failure);
|
|
480
501
|
setError(wordsOf(failure, "The workspace could not be loaded."));
|
|
481
502
|
}
|
|
482
503
|
}
|
|
@@ -571,6 +592,11 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
571
592
|
if (event.kind.startsWith("project.")) {
|
|
572
593
|
refreshWorkspace(signal).catch(() => undefined);
|
|
573
594
|
}
|
|
595
|
+
// A decision raised or answered here may be one of the person's own: the count beside
|
|
596
|
+
// My decisions follows it.
|
|
597
|
+
if (event.kind.startsWith("decision.")) {
|
|
598
|
+
refreshMe(signal).catch(() => undefined);
|
|
599
|
+
}
|
|
574
600
|
}, RELOAD_DELAY_MS);
|
|
575
601
|
};
|
|
576
602
|
for (const kind of EVENT_KINDS) {
|
|
@@ -584,7 +610,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
584
610
|
source?.close();
|
|
585
611
|
setLive(false);
|
|
586
612
|
};
|
|
587
|
-
}, [client, projectKey, signedIn, generation, refreshProject, refreshWorkspace]);
|
|
613
|
+
}, [client, projectKey, signedIn, generation, refreshProject, refreshWorkspace, refreshMe]);
|
|
588
614
|
|
|
589
615
|
const actions = useMemo<ConsoleActions>(() => {
|
|
590
616
|
/** The project the page is on, for an action that needs one. */
|
|
@@ -684,16 +710,19 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
684
710
|
scope().tasks({ archived: false, ...searchWordsOf(query), ...page }, signal),
|
|
685
711
|
findGoals: (query, page, signal) =>
|
|
686
712
|
scope().goals({ open: true, ...searchWordsOf(query), ...page }, signal),
|
|
687
|
-
async raiseDecision(input) {
|
|
688
|
-
const decision = await scope().raiseDecision(input);
|
|
689
|
-
await refreshProject();
|
|
713
|
+
async raiseDecision(input, files) {
|
|
714
|
+
const decision = await scope().raiseDecision(input, files);
|
|
715
|
+
await Promise.all([refreshProject(), refreshMe().catch(() => undefined)]);
|
|
716
|
+
return decision;
|
|
717
|
+
},
|
|
718
|
+
async answerDecisionIn(projectKey, id, input) {
|
|
719
|
+
const decision = await client.project(projectKey).answerDecision(id, input);
|
|
720
|
+
await refreshMe().catch(() => undefined);
|
|
690
721
|
return decision;
|
|
691
722
|
},
|
|
692
|
-
answerDecisionIn: (projectKey, id, input) =>
|
|
693
|
-
client.project(projectKey).answerDecision(id, input),
|
|
694
723
|
async answerDecision(id, input) {
|
|
695
724
|
const decision = await scope().answerDecision(id, input);
|
|
696
|
-
await refreshProject();
|
|
725
|
+
await Promise.all([refreshProject(), refreshMe().catch(() => undefined)]);
|
|
697
726
|
return decision;
|
|
698
727
|
},
|
|
699
728
|
async updateDecision(id, patch) {
|
|
@@ -834,11 +863,12 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
834
863
|
setLoaded(false);
|
|
835
864
|
},
|
|
836
865
|
};
|
|
837
|
-
}, [client, projectKey, refreshProject, refreshWorkspace]);
|
|
866
|
+
}, [client, projectKey, refreshProject, refreshWorkspace, refreshMe]);
|
|
838
867
|
|
|
839
868
|
return {
|
|
840
869
|
me,
|
|
841
870
|
error,
|
|
871
|
+
failure,
|
|
842
872
|
loaded,
|
|
843
873
|
projects,
|
|
844
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);
|