@skillcdn/console 0.4.3 → 0.4.5
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 +6 -6
- package/dist/api.d.ts +4 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +4 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +255 -33
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +20 -5
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +98 -26
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +39 -15
- 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 +32 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -2
- 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 +49 -18
- package/dist/components/board.js.map +1 -1
- package/dist/components/decision-form.d.ts +6 -3
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +24 -18
- 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 +34 -8
- 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 +20 -3
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +14 -1
- 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 +45 -1
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +125 -4
- package/dist/components/people.js.map +1 -1
- package/dist/components/picker.d.ts +8 -0
- package/dist/components/picker.d.ts.map +1 -1
- package/dist/components/picker.js +2 -2
- package/dist/components/picker.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +3 -13
- package/dist/components/plugin-frame.d.ts.map +1 -1
- package/dist/components/plugin-frame.js +75 -21
- package/dist/components/plugin-frame.js.map +1 -1
- package/dist/components/plugin-settings.d.ts +5 -1
- package/dist/components/plugin-settings.d.ts.map +1 -1
- package/dist/components/plugin-settings.js +283 -45
- 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 +4 -14
- package/dist/components/projects.js.map +1 -1
- package/dist/components/searches.d.ts +11 -0
- package/dist/components/searches.d.ts.map +1 -0
- package/dist/components/searches.js +30 -0
- package/dist/components/searches.js.map +1 -0
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +6 -5
- package/dist/components/task-form.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 +16 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +15 -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 +1123 -258
- package/dist/console.d.ts +12 -4
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +123 -27
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +18 -3
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +41 -1
- 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 +264 -1
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +260 -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 +255 -4
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +12 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +12 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/args.d.ts +6 -1
- package/dist/plugin/args.d.ts.map +1 -1
- package/dist/plugin/args.js +16 -11
- package/dist/plugin/args.js.map +1 -1
- package/dist/plugin/bridge.d.ts +63 -3
- package/dist/plugin/bridge.d.ts.map +1 -1
- package/dist/plugin/bridge.js +54 -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 +24 -1
- package/dist/plugin/capabilities.js.map +1 -1
- package/dist/plugin/context.d.ts +174 -12
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/define.d.ts +14 -1
- package/dist/plugin/define.d.ts.map +1 -1
- package/dist/plugin/define.js +18 -2
- package/dist/plugin/define.js.map +1 -1
- package/dist/plugin/limits.d.ts +9 -2
- package/dist/plugin/limits.d.ts.map +1 -1
- package/dist/plugin/limits.js +9 -2
- package/dist/plugin/limits.js.map +1 -1
- package/dist/plugin/manifest.d.ts +98 -6
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +171 -10
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/rest-schemas.d.ts +120 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -1
- package/dist/plugin/rest-schemas.js +78 -5
- 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 +15 -1
- package/dist/plugin/testing.d.ts.map +1 -1
- package/dist/plugin/testing.js +42 -5
- package/dist/plugin/testing.js.map +1 -1
- package/dist/plugin/web.d.ts +16 -1
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +44 -1
- 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/router.d.ts +19 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +18 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +11 -2
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +14 -1
- package/dist/routes.js.map +1 -1
- package/dist/rules.d.ts +2 -2
- package/dist/rules.js +3 -3
- package/dist/schemas.d.ts +864 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +156 -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 +9 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +10 -6
- package/docs/specs/plugins.md +83 -62
- package/docs/specs/rest.md +34 -16
- package/package.json +1 -1
- package/skills/building-a-plugin/SKILL.md +15 -12
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +23 -0
- package/src/cli/cli.ts +323 -30
- package/src/cli/format.ts +122 -26
- package/src/cli/help.ts +43 -14
- 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 +78 -0
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +86 -45
- package/src/components/decision-form.tsx +73 -36
- package/src/components/decision-list.tsx +45 -13
- package/src/components/event-feed.tsx +40 -7
- package/src/components/goals.tsx +4 -1
- package/src/components/home.tsx +615 -0
- package/src/components/my-decisions.tsx +50 -5
- package/src/components/pack-connect.tsx +5 -4
- package/src/components/people.tsx +500 -61
- package/src/components/picker.tsx +11 -2
- package/src/components/plugin-frame.tsx +85 -63
- package/src/components/plugin-settings.tsx +735 -185
- package/src/components/projects.tsx +14 -69
- package/src/components/searches.ts +47 -0
- package/src/components/task-form.tsx +23 -28
- package/src/components/task-view.tsx +7 -0
- package/src/components/ui.tsx +42 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +303 -64
- package/src/data.ts +62 -3
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +274 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +267 -4
- package/src/index.ts +18 -5
- package/src/limits.ts +12 -0
- package/src/plugin/args.ts +16 -11
- package/src/plugin/bridge.ts +122 -1
- package/src/plugin/capabilities.ts +28 -1
- package/src/plugin/context.ts +186 -9
- package/src/plugin/define.ts +38 -1
- package/src/plugin/limits.ts +9 -2
- package/src/plugin/manifest.ts +228 -13
- package/src/plugin/rest-schemas.ts +89 -6
- package/src/plugin/sandbox.ts +36 -8
- package/src/plugin/semver.ts +89 -0
- package/src/plugin/testing.ts +63 -6
- package/src/plugin/web.ts +66 -0
- package/src/plugin.ts +19 -0
- package/src/router.ts +25 -0
- package/src/routes.ts +17 -2
- package/src/rules.ts +3 -3
- package/src/schemas.ts +197 -10
- package/src/styles/console.css +1123 -258
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +10 -0
- package/template/plugin/console-plugin.json +3 -1
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
type RestProjectPatch,
|
|
24
24
|
} from "../api.js";
|
|
25
25
|
import { useWords } from "../i18n/index.js";
|
|
26
|
+
import { Picker } from "./picker.js";
|
|
27
|
+
import { peopleSearch } from "./searches.js";
|
|
26
28
|
import { Badge, Button, cx, PersonChip, personName, Time } from "./ui.js";
|
|
27
29
|
|
|
28
30
|
/**
|
|
@@ -99,61 +101,6 @@ export function ProjectRoleBadge(props: { readonly role: ProjectRole }) {
|
|
|
99
101
|
);
|
|
100
102
|
}
|
|
101
103
|
|
|
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
104
|
export interface ProjectFormProps {
|
|
158
105
|
/** The project to change; left out, the form makes a new one, with a key. */
|
|
159
106
|
readonly project?: RestProject | undefined;
|
|
@@ -517,21 +464,19 @@ export function MemberList(props: MemberListProps) {
|
|
|
517
464
|
}}
|
|
518
465
|
>
|
|
519
466
|
<div className="sc-field-row">
|
|
520
|
-
<
|
|
467
|
+
<div className="sc-field">
|
|
521
468
|
<span className="sc-field-label">{members.person}</span>
|
|
522
|
-
<
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
{
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
</select>
|
|
534
|
-
</label>
|
|
469
|
+
<Picker
|
|
470
|
+
value={candidates.find((person) => person.id === personId)}
|
|
471
|
+
search={peopleSearch(candidates)}
|
|
472
|
+
keyOf={(person) => person.id}
|
|
473
|
+
render={(person) => <PersonChip person={person} size="sm" />}
|
|
474
|
+
label={members.person}
|
|
475
|
+
placeholder={members.chooseSomeone}
|
|
476
|
+
disabled={props.busy === true}
|
|
477
|
+
onChange={(person) => setPersonId(person?.id ?? "")}
|
|
478
|
+
/>
|
|
479
|
+
</div>
|
|
535
480
|
<label className="sc-field">
|
|
536
481
|
<span className="sc-field-label">{members.as}</span>
|
|
537
482
|
<select
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RestGoal, RestPerson, RestProject } from "../api.js";
|
|
2
|
+
import type { PickerSearch } from "./picker.js";
|
|
3
|
+
import { personName } from "./ui.js";
|
|
4
|
+
|
|
5
|
+
// What a picker asks for, answered from a listing the page already holds (ADR-0037): the people,
|
|
6
|
+
// the projects or the goals the page was given, found by the words typed, a page at a time, so
|
|
7
|
+
// that choosing one among many is the same wherever one is chosen, and the board's filters, a
|
|
8
|
+
// setting, a manager or a worker never offer a list too long to read.
|
|
9
|
+
|
|
10
|
+
/** A page of `items` found by the words typed, as a picker searches; everything while the words are empty. */
|
|
11
|
+
export function listSearch<T>(
|
|
12
|
+
items: readonly T[],
|
|
13
|
+
matches: (item: T, words: string) => boolean,
|
|
14
|
+
): PickerSearch<T> {
|
|
15
|
+
return async (query, page) => {
|
|
16
|
+
const words = query.trim().toLowerCase();
|
|
17
|
+
const found = words.length === 0 ? items : items.filter((item) => matches(item, words));
|
|
18
|
+
return {
|
|
19
|
+
items: found.slice(page.offset, page.offset + page.limit),
|
|
20
|
+
more: found.length > page.offset + page.limit,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** The people given, found by name or login. */
|
|
26
|
+
export function peopleSearch(people: readonly RestPerson[]): PickerSearch<RestPerson> {
|
|
27
|
+
return listSearch(
|
|
28
|
+
people,
|
|
29
|
+
(person, words) =>
|
|
30
|
+
personName(person).toLowerCase().includes(words) ||
|
|
31
|
+
person.login.toLowerCase().includes(words),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** The projects given, found by name or key. */
|
|
36
|
+
export function projectsSearch(projects: readonly RestProject[]): PickerSearch<RestProject> {
|
|
37
|
+
return listSearch(
|
|
38
|
+
projects,
|
|
39
|
+
(project, words) =>
|
|
40
|
+
project.name.toLowerCase().includes(words) || project.key.toLowerCase().includes(words),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The goals given, found by title. */
|
|
45
|
+
export function goalsSearch(goals: readonly RestGoal[]): PickerSearch<RestGoal> {
|
|
46
|
+
return listSearch(goals, (goal, words) => goal.title.toLowerCase().includes(words));
|
|
47
|
+
}
|
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
} from "../api.js";
|
|
17
17
|
import { useWords } from "../i18n/index.js";
|
|
18
18
|
import { Picker, type PickerSearch } from "./picker.js";
|
|
19
|
-
import {
|
|
19
|
+
import { peopleSearch } from "./searches.js";
|
|
20
|
+
import { Button, Chip, GoalStateBadge, PersonChip, personName, StateBadge } from "./ui.js";
|
|
20
21
|
|
|
21
22
|
// Writing a task, or changing one. The bounds are the package's, said before a request is made.
|
|
22
23
|
// The plan is two days (ADR-0019); what happened to the task is the board's to keep, not a
|
|
@@ -222,37 +223,31 @@ export function TaskForm(props: TaskFormProps) {
|
|
|
222
223
|
<legend className="sc-field-label">{form.workers}</legend>
|
|
223
224
|
<div className="sc-chips">
|
|
224
225
|
{chosenWorkers.map((person) => (
|
|
225
|
-
<
|
|
226
|
+
<Chip
|
|
227
|
+
key={person.id}
|
|
228
|
+
onRemove={() => setWorkerIds(workerIds.filter((id) => id !== person.id))}
|
|
229
|
+
removeLabel={`${words.common.remove}: ${personName(person)}`}
|
|
230
|
+
>
|
|
226
231
|
<PersonChip person={person} />
|
|
227
|
-
|
|
228
|
-
type="button"
|
|
229
|
-
className="sc-chip-remove"
|
|
230
|
-
aria-label={`${words.common.remove}: ${personName(person)}`}
|
|
231
|
-
onClick={() => setWorkerIds(workerIds.filter((id) => id !== person.id))}
|
|
232
|
-
>
|
|
233
|
-
<span aria-hidden="true">×</span>
|
|
234
|
-
</button>
|
|
235
|
-
</span>
|
|
232
|
+
</Chip>
|
|
236
233
|
))}
|
|
237
|
-
{
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
234
|
+
{otherPeople.length > 0 && workerIds.length < MAX_WORKERS ? (
|
|
235
|
+
<Picker
|
|
236
|
+
className="sc-picker-add"
|
|
237
|
+
value={undefined}
|
|
238
|
+
search={peopleSearch(otherPeople)}
|
|
239
|
+
keyOf={(person) => person.id}
|
|
240
|
+
render={(person) => <PersonChip person={person} size="sm" />}
|
|
241
|
+
label={form.addWorker}
|
|
242
|
+
placeholder={form.addWorker}
|
|
243
|
+
onChange={(person) => {
|
|
244
|
+
if (person !== undefined) {
|
|
245
|
+
setWorkerIds([...workerIds, person.id]);
|
|
246
246
|
}
|
|
247
247
|
}}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
<option key={person.id} value={person.id}>
|
|
252
|
-
{personName(person)}
|
|
253
|
-
</option>
|
|
254
|
-
))}
|
|
255
|
-
</select>
|
|
248
|
+
/>
|
|
249
|
+
) : (
|
|
250
|
+
chosenWorkers.length === 0 && <span className="sc-muted">{words.common.nobody}</span>
|
|
256
251
|
)}
|
|
257
252
|
</div>
|
|
258
253
|
</fieldset>
|
|
@@ -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
|
}
|
|
@@ -160,6 +164,42 @@ export function PersonChip(props: { readonly person: RestPerson; readonly size?:
|
|
|
160
164
|
);
|
|
161
165
|
}
|
|
162
166
|
|
|
167
|
+
/**
|
|
168
|
+
* A thing named among others in a row (`sc-chips`): a person (a `PersonChip` inside), a team, an
|
|
169
|
+
* item of a list, a holder of a role. Given `onRemove`, it carries the way to take it out, named
|
|
170
|
+
* for a screen reader by `removeLabel`; left out, the chip is read, not changed. Every row of
|
|
171
|
+
* things a person adds to and takes from is made of these, so that they read alike everywhere.
|
|
172
|
+
*/
|
|
173
|
+
export function Chip(props: {
|
|
174
|
+
readonly children: ReactNode;
|
|
175
|
+
readonly onRemove?: (() => void) | undefined;
|
|
176
|
+
/** What the way out says to a screen reader: the word for removing, and what the chip names. */
|
|
177
|
+
readonly removeLabel?: string | undefined;
|
|
178
|
+
readonly disabled?: boolean | undefined;
|
|
179
|
+
readonly title?: string | undefined;
|
|
180
|
+
/** A class of the chip's own beside `sc-chip`. */
|
|
181
|
+
readonly className?: string | undefined;
|
|
182
|
+
}) {
|
|
183
|
+
const words = useWords().common;
|
|
184
|
+
return (
|
|
185
|
+
<span className={cx("sc-chip", props.className)} title={props.title}>
|
|
186
|
+
<span className="sc-chip-label">{props.children}</span>
|
|
187
|
+
{props.onRemove !== undefined && (
|
|
188
|
+
<button
|
|
189
|
+
type="button"
|
|
190
|
+
className="sc-chip-remove"
|
|
191
|
+
aria-label={props.removeLabel ?? words.remove}
|
|
192
|
+
title={props.removeLabel ?? words.remove}
|
|
193
|
+
disabled={props.disabled}
|
|
194
|
+
onClick={props.onRemove}
|
|
195
|
+
>
|
|
196
|
+
<span aria-hidden="true">×</span>
|
|
197
|
+
</button>
|
|
198
|
+
)}
|
|
199
|
+
</span>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
163
203
|
/** How many of a task's workers are shown as pictures before the rest are a count. */
|
|
164
204
|
const WORKERS_SHOWN = 3;
|
|
165
205
|
|
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">
|