@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
package/src/console.tsx
CHANGED
|
@@ -22,12 +22,14 @@ 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,
|
|
28
29
|
type RestProjectInput,
|
|
29
30
|
type RestProjectPatch,
|
|
30
31
|
type RestProjectRules,
|
|
32
|
+
type RestReachedDecision,
|
|
31
33
|
type RestRules,
|
|
32
34
|
type RestTask,
|
|
33
35
|
type RestTaskInput,
|
|
@@ -47,18 +49,24 @@ import { DecisionForm } from "./components/decision-form.js";
|
|
|
47
49
|
import { DecisionCard, DecisionList, type DecisionListProps } from "./components/decision-list.js";
|
|
48
50
|
import { EventFeed, type EventFeedProps } from "./components/event-feed.js";
|
|
49
51
|
import { GoalForm, GoalList, type GoalListProps, GoalView, goalMonth } from "./components/goals.js";
|
|
50
|
-
import {
|
|
52
|
+
import { HomeGrid, type HomeGridProps } from "./components/home.js";
|
|
53
|
+
import { MyDecisions, ReachedDecision } from "./components/my-decisions.js";
|
|
51
54
|
import { PackTokenWays } from "./components/pack-token.js";
|
|
52
55
|
import { PAGE_SIZE } from "./components/paged.js";
|
|
53
|
-
import {
|
|
54
|
-
|
|
56
|
+
import {
|
|
57
|
+
ListingForm,
|
|
58
|
+
PeopleList,
|
|
59
|
+
type PeopleListProps,
|
|
60
|
+
TeamForm,
|
|
61
|
+
TeamList,
|
|
62
|
+
type TeamListProps,
|
|
63
|
+
} from "./components/people.js";
|
|
64
|
+
import { PluginFrame } from "./components/plugin-frame.js";
|
|
55
65
|
import { PluginSettingsList, PluginSettingsView } from "./components/plugin-settings.js";
|
|
56
66
|
import {
|
|
57
67
|
MemberList,
|
|
58
68
|
type MemberListProps,
|
|
59
69
|
ProjectForm,
|
|
60
|
-
ProjectList,
|
|
61
|
-
type ProjectListProps,
|
|
62
70
|
ProjectMark,
|
|
63
71
|
} from "./components/projects.js";
|
|
64
72
|
import { type RemotePictureSrc, RemotePicturesContext } from "./components/remote-pictures.js";
|
|
@@ -81,6 +89,7 @@ import {
|
|
|
81
89
|
Button,
|
|
82
90
|
Callout,
|
|
83
91
|
CategoryBadge,
|
|
92
|
+
cx,
|
|
84
93
|
EmptyState,
|
|
85
94
|
LinkButton,
|
|
86
95
|
personName,
|
|
@@ -100,6 +109,7 @@ import { type ConsoleData, useConsoleData } from "./data.js";
|
|
|
100
109
|
import { monthOf, today } from "./dates.js";
|
|
101
110
|
import { DocsPage } from "./docs-page.js";
|
|
102
111
|
import {
|
|
112
|
+
browserPack,
|
|
103
113
|
chooseLanguage,
|
|
104
114
|
DEFAULT_LANGUAGES,
|
|
105
115
|
LanguageContext,
|
|
@@ -118,6 +128,7 @@ import {
|
|
|
118
128
|
type GoalTaskView,
|
|
119
129
|
goalHref,
|
|
120
130
|
matchRoute,
|
|
131
|
+
myDecisionHref,
|
|
121
132
|
type Navigation,
|
|
122
133
|
newDecisionHref,
|
|
123
134
|
newGoalHref,
|
|
@@ -156,8 +167,9 @@ export interface ConsoleComponents {
|
|
|
156
167
|
readonly SignIn: ComponentType<SignInProps>;
|
|
157
168
|
readonly TokenList: ComponentType<TokenListProps>;
|
|
158
169
|
readonly PeopleList: ComponentType<PeopleListProps>;
|
|
170
|
+
readonly TeamList: ComponentType<TeamListProps>;
|
|
159
171
|
readonly SkillList: ComponentType<SkillListProps>;
|
|
160
|
-
readonly
|
|
172
|
+
readonly HomeGrid: ComponentType<HomeGridProps>;
|
|
161
173
|
readonly MemberList: ComponentType<MemberListProps>;
|
|
162
174
|
readonly Calendar: ComponentType<CalendarProps>;
|
|
163
175
|
readonly GoalList: ComponentType<GoalListProps>;
|
|
@@ -174,8 +186,9 @@ export const DEFAULT_COMPONENTS: ConsoleComponents = {
|
|
|
174
186
|
SignIn,
|
|
175
187
|
TokenList,
|
|
176
188
|
PeopleList,
|
|
189
|
+
TeamList,
|
|
177
190
|
SkillList,
|
|
178
|
-
|
|
191
|
+
HomeGrid,
|
|
179
192
|
MemberList,
|
|
180
193
|
Calendar,
|
|
181
194
|
GoalList,
|
|
@@ -384,6 +397,21 @@ function holdsToken(data: ConsoleData): boolean {
|
|
|
384
397
|
return data.me !== undefined && data.me.agent !== null;
|
|
385
398
|
}
|
|
386
399
|
|
|
400
|
+
/**
|
|
401
|
+
* The language to keep for a person the pages see for the first time (ADR-0065): the one their
|
|
402
|
+
* browser asks for, so that a plugin or an agent can write for them while they are away. Nothing
|
|
403
|
+
* for a person who has a language, for nobody, and where the pages only fall back on a language.
|
|
404
|
+
*/
|
|
405
|
+
export function languageToKeep(
|
|
406
|
+
me: RestMe | undefined,
|
|
407
|
+
browser: readonly string[],
|
|
408
|
+
packs: readonly LanguagePack[],
|
|
409
|
+
): string | undefined {
|
|
410
|
+
return me?.person !== null && me?.person !== undefined && me.language === null
|
|
411
|
+
? browserPack(browser, packs)?.tag
|
|
412
|
+
: undefined;
|
|
413
|
+
}
|
|
414
|
+
|
|
387
415
|
/**
|
|
388
416
|
* The pages a console that holds a token does not offer: the tokens themselves, and the
|
|
389
417
|
* approval of an agent's connection, which makes one; a person's own doing on the console's
|
|
@@ -413,21 +441,11 @@ function ProjectsPage(props: {
|
|
|
413
441
|
readonly making: boolean;
|
|
414
442
|
}) {
|
|
415
443
|
const words = useWords().projects;
|
|
444
|
+
const homeWords = useWords().home;
|
|
416
445
|
const { data, components, navigation, client, language } = props;
|
|
417
446
|
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
|
-
);
|
|
447
|
+
// The person arranges the page (ADR-0061): the projects and the widgets, tiles on one grid.
|
|
448
|
+
const [arranging, setArranging] = useState(false);
|
|
431
449
|
const { busy, error, act } = useAction();
|
|
432
450
|
const href = (project: RestProject) => projectHref(project.key);
|
|
433
451
|
// A project is made by an administrator (ADR-0045): a member sees the list alone, the form's
|
|
@@ -438,14 +456,22 @@ function ProjectsPage(props: {
|
|
|
438
456
|
<>
|
|
439
457
|
<div className="sc-page-head">
|
|
440
458
|
<h1 className="sc-page-title">{words.title}</h1>
|
|
441
|
-
{
|
|
442
|
-
<
|
|
443
|
-
{
|
|
444
|
-
|
|
459
|
+
{!arranging && (
|
|
460
|
+
<div className="sc-page-actions">
|
|
461
|
+
{me !== undefined && me.person !== null && (
|
|
462
|
+
<Button size="sm" variant="ghost" onClick={() => setArranging(true)}>
|
|
463
|
+
{homeWords.edit}
|
|
464
|
+
</Button>
|
|
465
|
+
)}
|
|
466
|
+
{administrator && !making && (
|
|
467
|
+
<LinkButton href={PATHS.newProject} variant="primary">
|
|
468
|
+
{words.newProject}
|
|
469
|
+
</LinkButton>
|
|
470
|
+
)}
|
|
471
|
+
</div>
|
|
445
472
|
)}
|
|
446
473
|
</div>
|
|
447
474
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
448
|
-
{widgets("home:top")}
|
|
449
475
|
{making && (
|
|
450
476
|
<section className="sc-panel sc-panel-form" aria-label={words.form.aria}>
|
|
451
477
|
<ProjectForm
|
|
@@ -465,10 +491,30 @@ function ProjectsPage(props: {
|
|
|
465
491
|
/>
|
|
466
492
|
</section>
|
|
467
493
|
)}
|
|
468
|
-
<components.
|
|
494
|
+
<components.HomeGrid
|
|
469
495
|
projects={data.projects}
|
|
496
|
+
plugins={data.plugins}
|
|
497
|
+
home={me?.home}
|
|
498
|
+
editing={arranging}
|
|
499
|
+
onSave={(home) => data.actions.updateMe({ home })}
|
|
500
|
+
onDone={() => setArranging(false)}
|
|
470
501
|
projectHref={href}
|
|
471
502
|
onOpen={(project) => navigation.go(href(project))}
|
|
503
|
+
nameOf={(name) => pluginWordOf(name, language)}
|
|
504
|
+
renderWidget={(tile, size) =>
|
|
505
|
+
me === undefined ? null : (
|
|
506
|
+
<PluginFrame
|
|
507
|
+
plugin={tile.plugin}
|
|
508
|
+
widget={tile.widget.key}
|
|
509
|
+
size={size}
|
|
510
|
+
me={me}
|
|
511
|
+
language={language}
|
|
512
|
+
client={client}
|
|
513
|
+
navigation={navigation}
|
|
514
|
+
title={pluginWordOf(tile.widget.name, language)}
|
|
515
|
+
/>
|
|
516
|
+
)
|
|
517
|
+
}
|
|
472
518
|
empty={
|
|
473
519
|
<EmptyState
|
|
474
520
|
title={words.empty.title}
|
|
@@ -481,7 +527,6 @@ function ProjectsPage(props: {
|
|
|
481
527
|
/>
|
|
482
528
|
}
|
|
483
529
|
/>
|
|
484
|
-
{widgets("home:bottom")}
|
|
485
530
|
</>
|
|
486
531
|
);
|
|
487
532
|
}
|
|
@@ -516,7 +561,7 @@ function PluginPage(props: {
|
|
|
516
561
|
return (
|
|
517
562
|
<>
|
|
518
563
|
{pages.length > 1 && (
|
|
519
|
-
<nav className="sc-
|
|
564
|
+
<nav className="sc-page-tabs" aria-label={pluginWordOf(plugin.name, language)}>
|
|
520
565
|
{pages.map((candidate) => (
|
|
521
566
|
<a
|
|
522
567
|
key={candidate.key}
|
|
@@ -572,9 +617,9 @@ function PluginSettingsPage(props: {
|
|
|
572
617
|
setDetail(await client.plugin(key).get());
|
|
573
618
|
setFailure(undefined);
|
|
574
619
|
} catch (error) {
|
|
575
|
-
setFailure(error
|
|
620
|
+
setFailure(errorWords(error, words.common.somethingWentWrong, words));
|
|
576
621
|
}
|
|
577
|
-
}, [client, key, words
|
|
622
|
+
}, [client, key, words]);
|
|
578
623
|
useEffect(() => {
|
|
579
624
|
setDetail(undefined);
|
|
580
625
|
void read();
|
|
@@ -611,6 +656,8 @@ function PluginSettingsPage(props: {
|
|
|
611
656
|
detail={detail}
|
|
612
657
|
projects={data.projects}
|
|
613
658
|
people={data.people}
|
|
659
|
+
plugins={data.plugins}
|
|
660
|
+
teams={data.teams}
|
|
614
661
|
language={language}
|
|
615
662
|
client={client}
|
|
616
663
|
onChanged={async () => {
|
|
@@ -2209,6 +2256,70 @@ function PlatePage(props: {
|
|
|
2209
2256
|
);
|
|
2210
2257
|
}
|
|
2211
2258
|
|
|
2259
|
+
/**
|
|
2260
|
+
* One decision wherever it is (ADR-0068): in a project the person may see, or one that reaches
|
|
2261
|
+
* them from elsewhere, addressed to them, raised by them, or a plugin's addressed to someone
|
|
2262
|
+
* they manage; answered here where they may. The address a plugin's page sends a person to,
|
|
2263
|
+
* who may not be in the project the decision waits in.
|
|
2264
|
+
*/
|
|
2265
|
+
function MyDecisionPage(props: { readonly id: string; readonly data: ConsoleData }) {
|
|
2266
|
+
const words = useWords();
|
|
2267
|
+
const { id, data } = props;
|
|
2268
|
+
const { reachDecision, answerReachedDecision } = data.actions;
|
|
2269
|
+
const [reached, setReached] = useState<RestReachedDecision | undefined>(undefined);
|
|
2270
|
+
const [problem, setProblem] = useState<string | undefined>(undefined);
|
|
2271
|
+
const { busy, error, act } = useAction();
|
|
2272
|
+
const couldNotLoad = words.myDecisions.couldNotLoad;
|
|
2273
|
+
useEffect(() => {
|
|
2274
|
+
const controller = new AbortController();
|
|
2275
|
+
setReached(undefined);
|
|
2276
|
+
setProblem(undefined);
|
|
2277
|
+
reachDecision(id, controller.signal).then(
|
|
2278
|
+
(found) => {
|
|
2279
|
+
if (!controller.signal.aborted) {
|
|
2280
|
+
setReached(found);
|
|
2281
|
+
}
|
|
2282
|
+
},
|
|
2283
|
+
(failure: unknown) => {
|
|
2284
|
+
if (!controller.signal.aborted) {
|
|
2285
|
+
setProblem(errorWords(failure, couldNotLoad, words));
|
|
2286
|
+
}
|
|
2287
|
+
},
|
|
2288
|
+
);
|
|
2289
|
+
return () => controller.abort();
|
|
2290
|
+
}, [reachDecision, id, couldNotLoad, words]);
|
|
2291
|
+
const shown = problem ?? error;
|
|
2292
|
+
return (
|
|
2293
|
+
<>
|
|
2294
|
+
<div className="sc-page-head">
|
|
2295
|
+
<h1 className="sc-page-title">{words.decisions.one}</h1>
|
|
2296
|
+
<LinkButton href={PATHS.decisions}>{words.myDecisions.title}</LinkButton>
|
|
2297
|
+
</div>
|
|
2298
|
+
{shown !== undefined && <Callout tone="danger">{shown}</Callout>}
|
|
2299
|
+
{reached === undefined ? (
|
|
2300
|
+
shown === undefined && (
|
|
2301
|
+
<div className="sc-loading">
|
|
2302
|
+
<Spinner label={words.myDecisions.loading} />
|
|
2303
|
+
</div>
|
|
2304
|
+
)
|
|
2305
|
+
) : (
|
|
2306
|
+
<ReachedDecision
|
|
2307
|
+
reached={reached}
|
|
2308
|
+
onAnswer={(_decision, input) =>
|
|
2309
|
+
void act(async () => {
|
|
2310
|
+
const answered = await answerReachedDecision(id, input);
|
|
2311
|
+
setReached((current) =>
|
|
2312
|
+
current === undefined ? current : { ...current, decision: answered },
|
|
2313
|
+
);
|
|
2314
|
+
})
|
|
2315
|
+
}
|
|
2316
|
+
busy={busy}
|
|
2317
|
+
/>
|
|
2318
|
+
)}
|
|
2319
|
+
</>
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2212
2323
|
/** What is on the person's own plate, across their projects. */
|
|
2213
2324
|
/** How many of a person's decisions are read at a time. */
|
|
2214
2325
|
const MY_DECISIONS_PAGE = 50;
|
|
@@ -2274,14 +2385,17 @@ function useMyDecisions(
|
|
|
2274
2385
|
*/
|
|
2275
2386
|
function MyDecisionsPage(props: { readonly data: ConsoleData }) {
|
|
2276
2387
|
const words = useWords();
|
|
2277
|
-
const { myDecisions,
|
|
2388
|
+
const { myDecisions, answerReachedDecision } = props.data.actions;
|
|
2278
2389
|
const addressed = useMyDecisions(myDecisions, false, words.myDecisions.couldNotLoad);
|
|
2279
2390
|
const raised = useMyDecisions(myDecisions, true, words.myDecisions.couldNotLoad);
|
|
2280
2391
|
const { busy, error, act } = useAction();
|
|
2281
2392
|
const problem = addressed.problem ?? raised.problem ?? error;
|
|
2282
|
-
|
|
2393
|
+
// Answered at the decision's own address (ADR-0068), so that one that reaches the person from a
|
|
2394
|
+
// project they are not in, a plugin's up the reporting line or one they were named in before
|
|
2395
|
+
// they left it, is answered here as it is read here.
|
|
2396
|
+
const onAnswer = (_projectKey: string, decision: RestDecision, input: RestAnswerInput) =>
|
|
2283
2397
|
void act(async () => {
|
|
2284
|
-
await
|
|
2398
|
+
await answerReachedDecision(decision.id, input);
|
|
2285
2399
|
addressed.reload();
|
|
2286
2400
|
raised.reload();
|
|
2287
2401
|
});
|
|
@@ -2303,8 +2417,11 @@ function MyDecisionsPage(props: { readonly data: ConsoleData }) {
|
|
|
2303
2417
|
<MyDecisions
|
|
2304
2418
|
addressed={{ items: addressed.items, more: addressed.more, onMore: addressed.readMore }}
|
|
2305
2419
|
raised={{ items: raised.items, more: raised.more, onMore: raised.readMore }}
|
|
2306
|
-
decisionHref={
|
|
2307
|
-
|
|
2420
|
+
decisionHref={(projectKey, decisionId) =>
|
|
2421
|
+
props.data.projects.some((project) => project.key === projectKey)
|
|
2422
|
+
? decisionHref(projectKey, decisionId)
|
|
2423
|
+
: myDecisionHref(decisionId)
|
|
2424
|
+
}
|
|
2308
2425
|
onAnswer={onAnswer}
|
|
2309
2426
|
busy={busy || addressed.busy || raised.busy}
|
|
2310
2427
|
/>
|
|
@@ -2407,6 +2524,9 @@ function WorkspacePage(props: {
|
|
|
2407
2524
|
if (route.name === "my-decisions") {
|
|
2408
2525
|
return <MyDecisionsPage data={data} />;
|
|
2409
2526
|
}
|
|
2527
|
+
if (route.name === "my-decision") {
|
|
2528
|
+
return <MyDecisionPage id={route.id} data={data} />;
|
|
2529
|
+
}
|
|
2410
2530
|
if (route.name === "person-work") {
|
|
2411
2531
|
return <PersonWorkPage id={route.id} data={data} components={components} week={route.week} />;
|
|
2412
2532
|
}
|
|
@@ -2455,26 +2575,106 @@ function WorkspacePage(props: {
|
|
|
2455
2575
|
);
|
|
2456
2576
|
}
|
|
2457
2577
|
|
|
2458
|
-
if (
|
|
2578
|
+
if (
|
|
2579
|
+
route.name === "people" ||
|
|
2580
|
+
route.name === "new-listing" ||
|
|
2581
|
+
route.name === "teams" ||
|
|
2582
|
+
route.name === "new-team"
|
|
2583
|
+
) {
|
|
2459
2584
|
const me = data.me?.person ?? undefined;
|
|
2460
2585
|
const administrator = me?.role === "admin";
|
|
2586
|
+
// The people and the teams (ADR-0073) are two views of one page, each with its form.
|
|
2587
|
+
const onTeams = route.name === "teams" || route.name === "new-team";
|
|
2461
2588
|
const making = route.name === "new-listing" && administrator;
|
|
2589
|
+
const makingTeam = route.name === "new-team" && administrator;
|
|
2462
2590
|
// An owner sees the work of the people in their projects; the console refuses the rest.
|
|
2463
2591
|
const oversees = administrator || data.projects.some((project) => project.role === "owner");
|
|
2464
2592
|
// The list is what the policy shows (ADR-0035): everyone, or the people one shares a project with.
|
|
2465
2593
|
const narrowed = peopleVisibilityOf(data.me?.workspace) === "shared_projects" && !administrator;
|
|
2594
|
+
const views = words.people.views;
|
|
2595
|
+
const inCount = data.people.filter((person) => person.status === "active").length;
|
|
2466
2596
|
return (
|
|
2467
2597
|
<>
|
|
2468
2598
|
<div className="sc-page-head">
|
|
2469
2599
|
<h1 className="sc-page-title">{words.people.title}</h1>
|
|
2470
|
-
{administrator && !making && (
|
|
2600
|
+
{administrator && !onTeams && !making && (
|
|
2471
2601
|
<LinkButton href={PATHS.newListing} variant="primary">
|
|
2472
2602
|
{words.people.letIn}
|
|
2473
2603
|
</LinkButton>
|
|
2474
2604
|
)}
|
|
2605
|
+
{administrator && onTeams && !makingTeam && (
|
|
2606
|
+
<LinkButton href={PATHS.newTeam} variant="primary">
|
|
2607
|
+
{words.teams.newTeam}
|
|
2608
|
+
</LinkButton>
|
|
2609
|
+
)}
|
|
2475
2610
|
</div>
|
|
2611
|
+
<nav className="sc-subtabs sc-people-views" aria-label={views.aria}>
|
|
2612
|
+
<a
|
|
2613
|
+
className={cx("sc-tab", !onTeams && "sc-tab-current")}
|
|
2614
|
+
href={PATHS.people}
|
|
2615
|
+
aria-current={onTeams ? undefined : "page"}
|
|
2616
|
+
>
|
|
2617
|
+
{views.people}
|
|
2618
|
+
<span className="sc-tab-count">{inCount}</span>
|
|
2619
|
+
</a>
|
|
2620
|
+
<a
|
|
2621
|
+
className={cx("sc-tab", onTeams && "sc-tab-current")}
|
|
2622
|
+
href={PATHS.teams}
|
|
2623
|
+
aria-current={onTeams ? "page" : undefined}
|
|
2624
|
+
>
|
|
2625
|
+
{views.teams}
|
|
2626
|
+
<span className="sc-tab-count">{data.teams.length}</span>
|
|
2627
|
+
</a>
|
|
2628
|
+
</nav>
|
|
2476
2629
|
{narrowed && <p className="sc-lead">{words.people.narrowed}</p>}
|
|
2477
2630
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
2631
|
+
{makingTeam && (
|
|
2632
|
+
<section className="sc-panel sc-panel-form" aria-label={words.teams.newAria}>
|
|
2633
|
+
<TeamForm
|
|
2634
|
+
busy={busy}
|
|
2635
|
+
onSubmit={(input) =>
|
|
2636
|
+
void act(async () => {
|
|
2637
|
+
await data.actions.createTeam(input);
|
|
2638
|
+
navigation.replace(PATHS.teams);
|
|
2639
|
+
})
|
|
2640
|
+
}
|
|
2641
|
+
onCancel={() => navigation.back(PATHS.teams)}
|
|
2642
|
+
/>
|
|
2643
|
+
</section>
|
|
2644
|
+
)}
|
|
2645
|
+
{onTeams && (
|
|
2646
|
+
<components.TeamList
|
|
2647
|
+
teams={data.teams}
|
|
2648
|
+
people={data.people}
|
|
2649
|
+
busy={busy}
|
|
2650
|
+
onRename={
|
|
2651
|
+
administrator
|
|
2652
|
+
? (team, name) => void act(() => data.actions.renameTeam(team.key, { name }))
|
|
2653
|
+
: undefined
|
|
2654
|
+
}
|
|
2655
|
+
onRemove={
|
|
2656
|
+
administrator
|
|
2657
|
+
? (team) => {
|
|
2658
|
+
// The roles granted to the team go with it: asked first, as nothing brings them back.
|
|
2659
|
+
if (window.confirm(words.teams.removeAsk(team.name))) {
|
|
2660
|
+
void act(() => data.actions.removeTeam(team.key));
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
: undefined
|
|
2664
|
+
}
|
|
2665
|
+
onAddMember={
|
|
2666
|
+
administrator
|
|
2667
|
+
? (team, person) => void act(() => data.actions.addTeamMember(team.key, person.id))
|
|
2668
|
+
: undefined
|
|
2669
|
+
}
|
|
2670
|
+
onRemoveMember={
|
|
2671
|
+
administrator
|
|
2672
|
+
? (team, person) =>
|
|
2673
|
+
void act(() => data.actions.removeTeamMember(team.key, person.id))
|
|
2674
|
+
: undefined
|
|
2675
|
+
}
|
|
2676
|
+
/>
|
|
2677
|
+
)}
|
|
2478
2678
|
{making && (
|
|
2479
2679
|
<section className="sc-panel sc-panel-form" aria-label={words.people.letInAria}>
|
|
2480
2680
|
<ListingForm
|
|
@@ -2490,30 +2690,56 @@ function WorkspacePage(props: {
|
|
|
2490
2690
|
/>
|
|
2491
2691
|
</section>
|
|
2492
2692
|
)}
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2693
|
+
{!onTeams && (
|
|
2694
|
+
<components.PeopleList
|
|
2695
|
+
people={data.people}
|
|
2696
|
+
listed={data.listed}
|
|
2697
|
+
organization={data.organization}
|
|
2698
|
+
me={me}
|
|
2699
|
+
busy={busy}
|
|
2700
|
+
onChangeRole={
|
|
2701
|
+
administrator
|
|
2702
|
+
? (person, role) => void act(() => data.actions.updatePerson(person.id, { role }))
|
|
2703
|
+
: undefined
|
|
2704
|
+
}
|
|
2705
|
+
onChangeStatus={
|
|
2706
|
+
administrator
|
|
2707
|
+
? (person, status) =>
|
|
2708
|
+
void act(() => data.actions.updatePerson(person.id, { status }))
|
|
2709
|
+
: undefined
|
|
2710
|
+
}
|
|
2711
|
+
onChangeManager={
|
|
2712
|
+
administrator
|
|
2713
|
+
? (person, managerId) =>
|
|
2714
|
+
void act(() => data.actions.updatePerson(person.id, { managerId }))
|
|
2715
|
+
: undefined
|
|
2716
|
+
}
|
|
2717
|
+
onChangeEmail={
|
|
2718
|
+
administrator
|
|
2719
|
+
? (person, email) => void act(() => data.actions.updatePerson(person.id, { email }))
|
|
2720
|
+
: undefined
|
|
2721
|
+
}
|
|
2722
|
+
onUnlist={
|
|
2723
|
+
administrator
|
|
2724
|
+
? (listing) => void act(() => data.actions.removeListing(listing.id))
|
|
2725
|
+
: undefined
|
|
2726
|
+
}
|
|
2727
|
+
teams={data.teams}
|
|
2728
|
+
onJoinTeam={
|
|
2729
|
+
administrator
|
|
2730
|
+
? (person, team) => void act(() => data.actions.addTeamMember(team.key, person.id))
|
|
2731
|
+
: undefined
|
|
2732
|
+
}
|
|
2733
|
+
onLeaveTeam={
|
|
2734
|
+
administrator
|
|
2735
|
+
? (person, team) =>
|
|
2736
|
+
void act(() => data.actions.removeTeamMember(team.key, person.id))
|
|
2737
|
+
: undefined
|
|
2738
|
+
}
|
|
2739
|
+
workHref={oversees ? (person) => personWorkHref(person.id) : undefined}
|
|
2740
|
+
agentsHref={administrator ? (person) => personAgentsHref(person.id) : undefined}
|
|
2741
|
+
/>
|
|
2742
|
+
)}
|
|
2517
2743
|
</>
|
|
2518
2744
|
);
|
|
2519
2745
|
}
|
|
@@ -2592,7 +2818,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2592
2818
|
const failure = signInFailureOf(location.search);
|
|
2593
2819
|
const title = data.me?.workspace.name ?? fallbackTitle;
|
|
2594
2820
|
const viaToken = holdsToken(data);
|
|
2595
|
-
// The language: the person's
|
|
2821
|
+
// The language: the person's own, else the browser's, else the first the console speaks.
|
|
2596
2822
|
const chosen = data.me?.language;
|
|
2597
2823
|
const pack = useMemo(
|
|
2598
2824
|
() =>
|
|
@@ -2605,6 +2831,17 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2605
2831
|
);
|
|
2606
2832
|
const words = pack.messages;
|
|
2607
2833
|
|
|
2834
|
+
const toKeep = languageToKeep(
|
|
2835
|
+
data.me,
|
|
2836
|
+
typeof navigator === "undefined" ? [] : navigator.languages,
|
|
2837
|
+
languages,
|
|
2838
|
+
);
|
|
2839
|
+
useEffect(() => {
|
|
2840
|
+
if (toKeep !== undefined) {
|
|
2841
|
+
void data.actions.updateMe({ language: toKeep }).catch(() => undefined);
|
|
2842
|
+
}
|
|
2843
|
+
}, [toKeep, data.actions]);
|
|
2844
|
+
|
|
2608
2845
|
// An address that moved is shown at its new one, in its place.
|
|
2609
2846
|
useEffect(() => {
|
|
2610
2847
|
if (route.name === "moved") {
|
|
@@ -2632,7 +2869,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2632
2869
|
title={words.common.couldNotReach}
|
|
2633
2870
|
action={<Button onClick={data.reload}>{words.common.tryAgain}</Button>}
|
|
2634
2871
|
>
|
|
2635
|
-
{data.error}
|
|
2872
|
+
{errorWords(data.failure, data.error, words)}
|
|
2636
2873
|
</Callout>
|
|
2637
2874
|
)}
|
|
2638
2875
|
</div>
|
|
@@ -2663,7 +2900,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2663
2900
|
{
|
|
2664
2901
|
href: PATHS.decisions,
|
|
2665
2902
|
label: words.nav.myDecisions,
|
|
2666
|
-
current: route.name === "my-decisions",
|
|
2903
|
+
current: route.name === "my-decisions" || route.name === "my-decision",
|
|
2667
2904
|
count: data.me?.decisions?.waiting,
|
|
2668
2905
|
},
|
|
2669
2906
|
{
|
|
@@ -2672,6 +2909,8 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2672
2909
|
current:
|
|
2673
2910
|
route.name === "people" ||
|
|
2674
2911
|
route.name === "new-listing" ||
|
|
2912
|
+
route.name === "teams" ||
|
|
2913
|
+
route.name === "new-team" ||
|
|
2675
2914
|
route.name === "person-agents",
|
|
2676
2915
|
},
|
|
2677
2916
|
...(viaToken
|