@skillcdn/console 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- 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 +5 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +290 -46
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/credentials.d.ts +5 -0
- package/dist/cli/credentials.d.ts.map +1 -1
- package/dist/cli/credentials.js +2 -0
- package/dist/cli/credentials.js.map +1 -1
- package/dist/cli/directory.d.ts +7 -0
- package/dist/cli/directory.d.ts.map +1 -1
- package/dist/cli/directory.js +7 -0
- package/dist/cli/directory.js.map +1 -1
- package/dist/cli/format.d.ts +10 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +76 -20
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +42 -23
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/main.js +1 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/client.d.ts +39 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +24 -3
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +28 -6
- package/dist/compat.d.ts.map +1 -1
- package/dist/compat.js +55 -14
- package/dist/compat.js.map +1 -1
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +6 -4
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.d.ts +18 -8
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +39 -27
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +2 -0
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/pack-connect.d.ts +23 -0
- package/dist/components/pack-connect.d.ts.map +1 -0
- package/dist/components/pack-connect.js +86 -0
- package/dist/components/pack-connect.js.map +1 -0
- package/dist/components/pack-token.d.ts +27 -0
- package/dist/components/pack-token.d.ts.map +1 -0
- package/dist/components/pack-token.js +11 -0
- package/dist/components/pack-token.js.map +1 -0
- package/dist/components/picker.d.ts +39 -0
- package/dist/components/picker.d.ts.map +1 -0
- package/dist/components/picker.js +155 -0
- package/dist/components/picker.js.map +1 -0
- package/dist/components/projects.d.ts +13 -0
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +37 -5
- package/dist/components/projects.js.map +1 -1
- package/dist/components/task-form.d.ts +18 -4
- package/dist/components/task-form.d.ts.map +1 -1
- package/dist/components/task-form.js +56 -13
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/task-list.js +2 -2
- package/dist/components/task-list.js.map +1 -1
- package/dist/components/task-view.d.ts +7 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +6 -5
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +7 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +11 -0
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts +3 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +6 -4
- package/dist/components/work.js.map +1 -1
- package/dist/components/workspace.d.ts +46 -0
- package/dist/components/workspace.d.ts.map +1 -0
- package/dist/components/workspace.js +124 -0
- package/dist/components/workspace.js.map +1 -0
- package/dist/console.css +319 -11
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +83 -24
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +26 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +59 -1
- package/dist/data.js.map +1 -1
- package/dist/dates.d.ts +12 -2
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +20 -2
- package/dist/dates.js.map +1 -1
- package/dist/i18n/en.d.ts +64 -3
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +81 -16
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +77 -16
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +2 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +2 -0
- package/dist/limits.js.map +1 -1
- package/dist/router.d.ts +4 -4
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +11 -2
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +4 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +4 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +194 -13
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +121 -21
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +21 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +18 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +25 -20
- package/docs/specs/rest.md +42 -19
- package/package.json +2 -2
- package/skills/building-a-console/SKILL.md +6 -6
- package/skills/building-a-console/references/the-look-from-a-website.md +39 -0
- package/skills/deploying-a-console/SKILL.md +9 -8
- package/skills/deploying-a-console/references/resources.md +1 -1
- package/skills/working-the-board/SKILL.md +5 -5
- package/skills/working-the-board/references/setting-up.md +1 -1
- package/skills/working-the-board/references/terms.md +3 -1
- package/skills/working-the-board/references/the-sentence.md +1 -1
- package/src/api.ts +21 -0
- package/src/cli/cli.ts +384 -51
- package/src/cli/credentials.ts +7 -0
- package/src/cli/directory.ts +9 -0
- package/src/cli/format.ts +83 -20
- package/src/cli/help.ts +50 -23
- package/src/cli/main.ts +1 -0
- package/src/client.ts +107 -10
- package/src/compat.ts +63 -14
- package/src/components/board.tsx +10 -4
- package/src/components/calendar.tsx +76 -34
- package/src/components/event-feed.tsx +2 -0
- package/src/components/pack-connect.tsx +179 -0
- package/src/components/pack-token.tsx +74 -0
- package/src/components/picker.tsx +297 -0
- package/src/components/projects.tsx +108 -9
- package/src/components/task-form.tsx +149 -63
- package/src/components/task-list.tsx +2 -2
- package/src/components/task-view.tsx +23 -9
- package/src/components/ui.tsx +25 -0
- package/src/components/work.tsx +10 -5
- package/src/components/workspace.tsx +298 -0
- package/src/console.tsx +202 -32
- package/src/data.ts +101 -1
- package/src/dates.ts +23 -2
- package/src/i18n/en.ts +92 -16
- package/src/i18n/ko.ts +84 -16
- package/src/index.ts +32 -1
- package/src/limits.ts +2 -0
- package/src/router.ts +17 -5
- package/src/routes.ts +4 -0
- package/src/schemas.ts +140 -19
- package/src/styles/console.css +319 -11
- package/src/vocabulary.ts +28 -0
package/src/console.tsx
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
RULES_PATH,
|
|
36
36
|
RULES_TITLE,
|
|
37
37
|
type TaskState,
|
|
38
|
+
type WeekStart,
|
|
38
39
|
} from "./api.js";
|
|
39
40
|
import { Board, BoardFilters, type BoardProps, byDue, filterTasks } from "./components/board.js";
|
|
40
41
|
import { Calendar, type CalendarProps, Workload } from "./components/calendar.js";
|
|
@@ -44,12 +45,12 @@ import { DecisionForm } from "./components/decision-form.js";
|
|
|
44
45
|
import { DecisionCard, DecisionList, type DecisionListProps } from "./components/decision-list.js";
|
|
45
46
|
import { EventFeed, type EventFeedProps } from "./components/event-feed.js";
|
|
46
47
|
import { GoalForm, GoalList, type GoalListProps, GoalView, goalMonth } from "./components/goals.js";
|
|
48
|
+
import { PackTokenWays } from "./components/pack-token.js";
|
|
47
49
|
import { PAGE_SIZE } from "./components/paged.js";
|
|
48
50
|
import { ListingForm, PeopleList, type PeopleListProps } from "./components/people.js";
|
|
49
51
|
import {
|
|
50
52
|
MemberList,
|
|
51
53
|
type MemberListProps,
|
|
52
|
-
PackTokenForm,
|
|
53
54
|
ProjectForm,
|
|
54
55
|
ProjectList,
|
|
55
56
|
type ProjectListProps,
|
|
@@ -81,6 +82,12 @@ import {
|
|
|
81
82
|
Time,
|
|
82
83
|
} from "./components/ui.js";
|
|
83
84
|
import { WorkList, type WorkListProps } from "./components/work.js";
|
|
85
|
+
import {
|
|
86
|
+
peopleVisibilityOf,
|
|
87
|
+
WorkspaceSettingsForm,
|
|
88
|
+
WorkspaceSettingsView,
|
|
89
|
+
weekStartOf,
|
|
90
|
+
} from "./components/workspace.js";
|
|
84
91
|
import { ConnectPage } from "./connect-page.js";
|
|
85
92
|
import { type ConsoleData, useConsoleData } from "./data.js";
|
|
86
93
|
import { monthOf, today } from "./dates.js";
|
|
@@ -593,6 +600,28 @@ function TaskPage(props: {
|
|
|
593
600
|
(fetched !== undefined && (fetched.id === route.id || fetched.number === route.number)
|
|
594
601
|
? fetched
|
|
595
602
|
: undefined);
|
|
603
|
+
// The task this one is part of: among the board's, or asked for by itself when it was put away.
|
|
604
|
+
const parentId = task?.parentId ?? null;
|
|
605
|
+
const parentOnBoard =
|
|
606
|
+
parentId === null ? undefined : data.tasks.find((candidate) => candidate.id === parentId);
|
|
607
|
+
const [parentFetched, setParentFetched] = useState<RestTask | undefined>(undefined);
|
|
608
|
+
const parentWanted =
|
|
609
|
+
parentId !== null && parentOnBoard === undefined && data.projectLoaded ? parentId : undefined;
|
|
610
|
+
useEffect(() => {
|
|
611
|
+
if (parentWanted === undefined) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const controller = new AbortController();
|
|
615
|
+
fetchTask(parentWanted, controller.signal)
|
|
616
|
+
.then((found) => {
|
|
617
|
+
if (!controller.signal.aborted) {
|
|
618
|
+
setParentFetched(found);
|
|
619
|
+
}
|
|
620
|
+
})
|
|
621
|
+
.catch(() => undefined);
|
|
622
|
+
return () => controller.abort();
|
|
623
|
+
}, [parentWanted, fetchTask]);
|
|
624
|
+
const parent = parentOnBoard ?? (parentFetched?.id === parentId ? parentFetched : undefined);
|
|
596
625
|
const [history, setHistory] = useState<readonly RestEvent[] | undefined>(undefined);
|
|
597
626
|
const [comments, setComments] = useState<readonly RestComment[] | undefined>(undefined);
|
|
598
627
|
const { taskHistory, taskComments } = data.actions;
|
|
@@ -646,6 +675,9 @@ function TaskPage(props: {
|
|
|
646
675
|
task={task}
|
|
647
676
|
people={data.people.filter(inWorkspace)}
|
|
648
677
|
tasks={data.tasks}
|
|
678
|
+
parent={parent}
|
|
679
|
+
findTasks={data.actions.findTasks}
|
|
680
|
+
findGoals={data.actions.findGoals}
|
|
649
681
|
decisions={data.decisions}
|
|
650
682
|
runs={data.runs}
|
|
651
683
|
comments={comments}
|
|
@@ -798,9 +830,9 @@ function ProjectPage(props: {
|
|
|
798
830
|
<section className="sc-panel sc-panel-form" aria-label={words.task.newAria}>
|
|
799
831
|
<TaskForm
|
|
800
832
|
people={data.people.filter(inWorkspace)}
|
|
801
|
-
|
|
833
|
+
findTasks={data.actions.findTasks}
|
|
802
834
|
categories={data.categories}
|
|
803
|
-
|
|
835
|
+
findGoals={data.actions.findGoals}
|
|
804
836
|
busy={busy}
|
|
805
837
|
onSubmit={(input: RestTaskInput) =>
|
|
806
838
|
void act(async () => {
|
|
@@ -861,6 +893,7 @@ function ProjectPage(props: {
|
|
|
861
893
|
tasks={shown}
|
|
862
894
|
month={month}
|
|
863
895
|
today={now}
|
|
896
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
864
897
|
categories={data.categories}
|
|
865
898
|
goals={data.goals}
|
|
866
899
|
taskHref={href}
|
|
@@ -899,6 +932,7 @@ function ProjectPage(props: {
|
|
|
899
932
|
<Workload
|
|
900
933
|
tasks={shown}
|
|
901
934
|
today={now}
|
|
935
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
902
936
|
personHref={(person) =>
|
|
903
937
|
boardHref(key, person === undefined ? {} : { person: person.id })
|
|
904
938
|
}
|
|
@@ -1075,6 +1109,7 @@ function ProjectPage(props: {
|
|
|
1075
1109
|
tasks={towards}
|
|
1076
1110
|
month={month}
|
|
1077
1111
|
today={now}
|
|
1112
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
1078
1113
|
categories={data.categories}
|
|
1079
1114
|
goals={[goal]}
|
|
1080
1115
|
taskHref={href}
|
|
@@ -1220,19 +1255,34 @@ function ProjectPage(props: {
|
|
|
1220
1255
|
const me = data.me?.person ?? undefined;
|
|
1221
1256
|
const configures = owner;
|
|
1222
1257
|
const members = words.projects.members;
|
|
1258
|
+
// In a project open to the workspace everyone is a member already (ADR-0035): the list is
|
|
1259
|
+
// everyone, nobody is added or removed, and an owner is made from a row. Where the People
|
|
1260
|
+
// page shows a member only whom they share a project with, an owner lists someone the list
|
|
1261
|
+
// does not show by their login.
|
|
1262
|
+
const open = project.visibility === "workspace";
|
|
1263
|
+
const narrowed =
|
|
1264
|
+
peopleVisibilityOf(data.me?.workspace) === "shared_projects" && me?.role !== "admin";
|
|
1223
1265
|
return (
|
|
1224
1266
|
<>
|
|
1225
1267
|
<div className="sc-page-head">
|
|
1226
1268
|
<h1 className="sc-page-title">{members.title}</h1>
|
|
1227
1269
|
</div>
|
|
1228
|
-
<p className="sc-lead">{
|
|
1270
|
+
{open && <p className="sc-lead">{members.open}</p>}
|
|
1229
1271
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1230
1272
|
<components.MemberList
|
|
1231
1273
|
members={data.members}
|
|
1232
1274
|
people={data.people.filter(inWorkspace)}
|
|
1233
1275
|
me={me}
|
|
1276
|
+
open={open}
|
|
1234
1277
|
busy={busy}
|
|
1235
|
-
onAdd={
|
|
1278
|
+
onAdd={
|
|
1279
|
+
configures && !open
|
|
1280
|
+
? (input) => void act(() => data.actions.addMember(input))
|
|
1281
|
+
: undefined
|
|
1282
|
+
}
|
|
1283
|
+
lookup={
|
|
1284
|
+
configures && !open && narrowed ? (login) => data.actions.findPerson(login) : undefined
|
|
1285
|
+
}
|
|
1236
1286
|
onChangeRole={
|
|
1237
1287
|
configures
|
|
1238
1288
|
? (member, role) =>
|
|
@@ -1240,7 +1290,7 @@ function ProjectPage(props: {
|
|
|
1240
1290
|
: undefined
|
|
1241
1291
|
}
|
|
1242
1292
|
onRemove={
|
|
1243
|
-
configures
|
|
1293
|
+
configures && !open
|
|
1244
1294
|
? (member) => void act(() => data.actions.removeMember(member.person.id))
|
|
1245
1295
|
: undefined
|
|
1246
1296
|
}
|
|
@@ -1382,28 +1432,33 @@ function ProjectSettingsPage(props: {
|
|
|
1382
1432
|
<>
|
|
1383
1433
|
{head}
|
|
1384
1434
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1385
|
-
<
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1435
|
+
<PackTokenWays
|
|
1436
|
+
title={settings.packToken}
|
|
1437
|
+
intro={words.projects.packToken.intro}
|
|
1438
|
+
set={project.packToken !== null}
|
|
1439
|
+
setAt={project.packToken?.setAt}
|
|
1440
|
+
busy={busy}
|
|
1441
|
+
onStart={data.actions.connectPackToken}
|
|
1442
|
+
poll={data.actions.packTokenConnection}
|
|
1443
|
+
onCancelConnect={data.actions.cancelPackTokenConnection}
|
|
1444
|
+
onConnected={() => navigation.replace(here)}
|
|
1445
|
+
onSubmit={(token) =>
|
|
1446
|
+
void act(async () => {
|
|
1447
|
+
await data.actions.setPackToken(token);
|
|
1448
|
+
navigation.replace(here);
|
|
1449
|
+
})
|
|
1450
|
+
}
|
|
1451
|
+
onRemove={
|
|
1452
|
+
project.packToken === null
|
|
1453
|
+
? undefined
|
|
1454
|
+
: () =>
|
|
1455
|
+
void act(async () => {
|
|
1456
|
+
await data.actions.removePackToken();
|
|
1457
|
+
navigation.replace(here);
|
|
1458
|
+
})
|
|
1459
|
+
}
|
|
1460
|
+
onBack={() => navigation.back(here)}
|
|
1461
|
+
/>
|
|
1407
1462
|
</>
|
|
1408
1463
|
);
|
|
1409
1464
|
}
|
|
@@ -1455,7 +1510,9 @@ function ProjectSettingsPage(props: {
|
|
|
1455
1510
|
{owner && (
|
|
1456
1511
|
<>
|
|
1457
1512
|
{" "}
|
|
1458
|
-
<a href={projectSettingsHref(key, "pack-token")}>
|
|
1513
|
+
<a href={projectSettingsHref(key, "pack-token")}>
|
|
1514
|
+
{project.packToken === null ? settings.packTokenAdd : settings.packTokenEdit}
|
|
1515
|
+
</a>
|
|
1459
1516
|
</>
|
|
1460
1517
|
)}
|
|
1461
1518
|
</dd>
|
|
@@ -1516,7 +1573,7 @@ function ProjectSettingsPage(props: {
|
|
|
1516
1573
|
* from, which whoever runs the console set. Read by everyone; the rules written by an administrator.
|
|
1517
1574
|
*/
|
|
1518
1575
|
function WorkspaceSettingsPage(props: {
|
|
1519
|
-
readonly form: "rules" | undefined;
|
|
1576
|
+
readonly form: "rules" | "workspace" | "pack-token" | undefined;
|
|
1520
1577
|
readonly data: ConsoleData;
|
|
1521
1578
|
readonly navigation: Navigation;
|
|
1522
1579
|
}) {
|
|
@@ -1524,7 +1581,8 @@ function WorkspaceSettingsPage(props: {
|
|
|
1524
1581
|
const { form, data, navigation } = props;
|
|
1525
1582
|
const { busy, error, act } = useAction();
|
|
1526
1583
|
const administrator = data.me?.person?.role === "admin";
|
|
1527
|
-
const
|
|
1584
|
+
const workspace = data.me?.workspace;
|
|
1585
|
+
const skills = workspace?.skills;
|
|
1528
1586
|
const [rules, setRules] = useState<RestRules | undefined>(undefined);
|
|
1529
1587
|
useEffect(() => {
|
|
1530
1588
|
const controller = new AbortController();
|
|
@@ -1587,10 +1645,90 @@ function WorkspaceSettingsPage(props: {
|
|
|
1587
1645
|
</>
|
|
1588
1646
|
);
|
|
1589
1647
|
}
|
|
1648
|
+
if (form === "pack-token") {
|
|
1649
|
+
if (!administrator || workspace === undefined) {
|
|
1650
|
+
return <NothingHere />;
|
|
1651
|
+
}
|
|
1652
|
+
const held = workspace.skills.packToken ?? null;
|
|
1653
|
+
return (
|
|
1654
|
+
<>
|
|
1655
|
+
{head}
|
|
1656
|
+
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1657
|
+
<PackTokenWays
|
|
1658
|
+
title={words.settings.skillsToken}
|
|
1659
|
+
intro={words.settings.skillsTokenIntro}
|
|
1660
|
+
set={held !== null}
|
|
1661
|
+
setAt={held?.setAt}
|
|
1662
|
+
busy={busy}
|
|
1663
|
+
onStart={data.actions.connectWorkspacePackToken}
|
|
1664
|
+
poll={data.actions.workspacePackTokenConnection}
|
|
1665
|
+
onCancelConnect={data.actions.cancelWorkspacePackTokenConnection}
|
|
1666
|
+
onConnected={() => navigation.replace(PATHS.settings)}
|
|
1667
|
+
onSubmit={(token) =>
|
|
1668
|
+
void act(async () => {
|
|
1669
|
+
await data.actions.setWorkspacePackToken(token);
|
|
1670
|
+
navigation.replace(PATHS.settings);
|
|
1671
|
+
})
|
|
1672
|
+
}
|
|
1673
|
+
onRemove={
|
|
1674
|
+
held === null
|
|
1675
|
+
? undefined
|
|
1676
|
+
: () =>
|
|
1677
|
+
void act(async () => {
|
|
1678
|
+
await data.actions.removeWorkspacePackToken();
|
|
1679
|
+
navigation.replace(PATHS.settings);
|
|
1680
|
+
})
|
|
1681
|
+
}
|
|
1682
|
+
onBack={() => navigation.back(PATHS.settings)}
|
|
1683
|
+
/>
|
|
1684
|
+
</>
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
if (form === "workspace") {
|
|
1688
|
+
if (!administrator || workspace === undefined) {
|
|
1689
|
+
return <NothingHere />;
|
|
1690
|
+
}
|
|
1691
|
+
return (
|
|
1692
|
+
<>
|
|
1693
|
+
{head}
|
|
1694
|
+
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1695
|
+
<section className="sc-panel sc-panel-form" aria-label={words.settings.workspace.aria}>
|
|
1696
|
+
<h2 className="sc-section-title">{words.settings.workspace.title}</h2>
|
|
1697
|
+
<WorkspaceSettingsForm
|
|
1698
|
+
workspace={workspace}
|
|
1699
|
+
busy={busy}
|
|
1700
|
+
onSubmit={(patch) =>
|
|
1701
|
+
void act(async () => {
|
|
1702
|
+
await data.actions.updateWorkspace(patch);
|
|
1703
|
+
navigation.replace(PATHS.settings);
|
|
1704
|
+
})
|
|
1705
|
+
}
|
|
1706
|
+
onCancel={() => navigation.back(PATHS.settings)}
|
|
1707
|
+
/>
|
|
1708
|
+
</section>
|
|
1709
|
+
</>
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1590
1712
|
return (
|
|
1591
1713
|
<>
|
|
1592
1714
|
{head}
|
|
1593
1715
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1716
|
+
{workspace !== undefined && (
|
|
1717
|
+
<section className="sc-panel" aria-label={words.settings.workspace.aria}>
|
|
1718
|
+
<div className="sc-section-head">
|
|
1719
|
+
<h2 className="sc-section-title">{words.settings.workspace.title}</h2>
|
|
1720
|
+
{administrator && (
|
|
1721
|
+
<a
|
|
1722
|
+
className="sc-button sc-button-secondary sc-button-sm"
|
|
1723
|
+
href={settingsHref("workspace")}
|
|
1724
|
+
>
|
|
1725
|
+
{words.common.edit}
|
|
1726
|
+
</a>
|
|
1727
|
+
)}
|
|
1728
|
+
</div>
|
|
1729
|
+
<WorkspaceSettingsView workspace={workspace} />
|
|
1730
|
+
</section>
|
|
1731
|
+
)}
|
|
1594
1732
|
<section className="sc-panel" aria-label={words.rules.title}>
|
|
1595
1733
|
<h2 className="sc-section-title">{words.rules.title}</h2>
|
|
1596
1734
|
{rules === undefined ? (
|
|
@@ -1651,6 +1789,30 @@ function WorkspaceSettingsPage(props: {
|
|
|
1651
1789
|
)}
|
|
1652
1790
|
</dd>
|
|
1653
1791
|
</div>
|
|
1792
|
+
{skills.address !== null && (
|
|
1793
|
+
<div>
|
|
1794
|
+
<dt>{words.settings.skillsToken}</dt>
|
|
1795
|
+
<dd>
|
|
1796
|
+
{skills.packToken == null ? (
|
|
1797
|
+
<span className="sc-muted">{words.projects.settings.packTokenNone}</span>
|
|
1798
|
+
) : (
|
|
1799
|
+
<>
|
|
1800
|
+
{words.projects.settings.packTokenSet} <Time iso={skills.packToken.setAt} />
|
|
1801
|
+
</>
|
|
1802
|
+
)}
|
|
1803
|
+
{administrator && (
|
|
1804
|
+
<>
|
|
1805
|
+
{" "}
|
|
1806
|
+
<a href={settingsHref("pack-token")}>
|
|
1807
|
+
{skills.packToken == null
|
|
1808
|
+
? words.projects.settings.packTokenAdd
|
|
1809
|
+
: words.projects.settings.packTokenEdit}
|
|
1810
|
+
</a>
|
|
1811
|
+
</>
|
|
1812
|
+
)}
|
|
1813
|
+
</dd>
|
|
1814
|
+
</div>
|
|
1815
|
+
)}
|
|
1654
1816
|
</dl>
|
|
1655
1817
|
<p className="sc-muted">{words.settings.skillsFromConfiguration}</p>
|
|
1656
1818
|
</section>
|
|
@@ -1660,7 +1822,7 @@ function WorkspaceSettingsPage(props: {
|
|
|
1660
1822
|
}
|
|
1661
1823
|
|
|
1662
1824
|
/** A file a person chose, as the client sends it: its name, its bytes and the type the browser says. */
|
|
1663
|
-
/** Whether a person is in: who a picker of
|
|
1825
|
+
/** Whether a person is in: who a picker of workers and of members offers (ADR-0022). */
|
|
1664
1826
|
const inWorkspace = (person: { readonly status: "active" | "deactivated" }): boolean =>
|
|
1665
1827
|
person.status === "active";
|
|
1666
1828
|
|
|
@@ -1777,6 +1939,8 @@ function PlatePage(props: {
|
|
|
1777
1939
|
readonly empty: { readonly title: string; readonly body: string };
|
|
1778
1940
|
/** What a refusal says where the plate may not be the asker's to see; the server's words otherwise. */
|
|
1779
1941
|
readonly refused?: { readonly title: string; readonly body: string } | undefined;
|
|
1942
|
+
/** The day the week begins on, as the workspace says (ADR-0035). */
|
|
1943
|
+
readonly weekStart?: WeekStart | undefined;
|
|
1780
1944
|
}) {
|
|
1781
1945
|
const words = useWords();
|
|
1782
1946
|
const { components, load, couldNotLoad, refused } = props;
|
|
@@ -1837,6 +2001,7 @@ function PlatePage(props: {
|
|
|
1837
2001
|
<components.WorkList
|
|
1838
2002
|
work={plate}
|
|
1839
2003
|
week={props.week}
|
|
2004
|
+
weekStart={props.weekStart}
|
|
1840
2005
|
taskHref={(projectKey, task) => taskHref(projectKey, task.number)}
|
|
1841
2006
|
decisionHref={(projectKey, decisionId) => decisionHref(projectKey, decisionId)}
|
|
1842
2007
|
weekHref={props.weekHref}
|
|
@@ -1860,6 +2025,7 @@ function WorkPage(props: {
|
|
|
1860
2025
|
load={work}
|
|
1861
2026
|
title={words.work.title}
|
|
1862
2027
|
week={props.week}
|
|
2028
|
+
weekStart={weekStartOf(props.data.me?.workspace)}
|
|
1863
2029
|
weekHref={workHref}
|
|
1864
2030
|
loading={words.work.loading}
|
|
1865
2031
|
couldNotLoad={words.work.couldNotLoad}
|
|
@@ -1888,6 +2054,7 @@ function PersonWorkPage(props: {
|
|
|
1888
2054
|
title={words.people.workOf(name)}
|
|
1889
2055
|
aside={<LinkButton href={PATHS.people}>{words.nav.people}</LinkButton>}
|
|
1890
2056
|
week={props.week}
|
|
2057
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
1891
2058
|
weekHref={(day) => personWorkHref(id, day)}
|
|
1892
2059
|
loading={words.people.loadingWork}
|
|
1893
2060
|
couldNotLoad={words.people.couldNotLoadWork}
|
|
@@ -1958,6 +2125,8 @@ function WorkspacePage(props: {
|
|
|
1958
2125
|
const making = route.name === "new-listing" && administrator;
|
|
1959
2126
|
// An owner sees the work of the people in their projects; the console refuses the rest.
|
|
1960
2127
|
const oversees = administrator || data.projects.some((project) => project.role === "owner");
|
|
2128
|
+
// The list is what the policy shows (ADR-0035): everyone, or the people one shares a project with.
|
|
2129
|
+
const narrowed = peopleVisibilityOf(data.me?.workspace) === "shared_projects" && !administrator;
|
|
1961
2130
|
return (
|
|
1962
2131
|
<>
|
|
1963
2132
|
<div className="sc-page-head">
|
|
@@ -1968,6 +2137,7 @@ function WorkspacePage(props: {
|
|
|
1968
2137
|
</LinkButton>
|
|
1969
2138
|
)}
|
|
1970
2139
|
</div>
|
|
2140
|
+
{narrowed && <p className="sc-lead">{words.people.narrowed}</p>}
|
|
1971
2141
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1972
2142
|
{making && (
|
|
1973
2143
|
<section className="sc-panel sc-panel-form" aria-label={words.people.letInAria}>
|
package/src/data.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type RestGoal,
|
|
25
25
|
type RestGoalInput,
|
|
26
26
|
type RestGoalPatch,
|
|
27
|
+
type RestGoals,
|
|
27
28
|
type RestListing,
|
|
28
29
|
type RestListingInput,
|
|
29
30
|
type RestMe,
|
|
@@ -32,6 +33,8 @@ import {
|
|
|
32
33
|
type RestMemberPatch,
|
|
33
34
|
type RestMePatch,
|
|
34
35
|
type RestOrganization,
|
|
36
|
+
type RestPackConnect,
|
|
37
|
+
type RestPackConnectStarted,
|
|
35
38
|
type RestPerson,
|
|
36
39
|
type RestPersonPatch,
|
|
37
40
|
type RestPersonWork,
|
|
@@ -54,6 +57,9 @@ import {
|
|
|
54
57
|
type RestVersion,
|
|
55
58
|
type RestVersions,
|
|
56
59
|
type RestWork,
|
|
60
|
+
type RestWorkspace,
|
|
61
|
+
type RestWorkspacePackConnect,
|
|
62
|
+
type RestWorkspacePatch,
|
|
57
63
|
restEventSchema,
|
|
58
64
|
} from "./api.js";
|
|
59
65
|
|
|
@@ -80,6 +86,11 @@ export interface ConsoleActions {
|
|
|
80
86
|
/** Sets the pack token of the project the page is on, or takes it away; for an owner (ADR-0028). */
|
|
81
87
|
setPackToken(token: string): Promise<RestProject>;
|
|
82
88
|
removePackToken(): Promise<RestProject>;
|
|
89
|
+
/** Asks the deployment to connect the project's pack with a code a person approves (ADR-0036); for an owner. */
|
|
90
|
+
connectPackToken(): Promise<RestPackConnectStarted>;
|
|
91
|
+
/** How that connection stands; once connected, the project is the one answered. */
|
|
92
|
+
packTokenConnection(signal?: AbortSignal): Promise<RestPackConnect>;
|
|
93
|
+
cancelPackTokenConnection(): Promise<void>;
|
|
83
94
|
addMember(input: RestMemberInput): Promise<RestMember>;
|
|
84
95
|
updateMember(personId: string, patch: RestMemberPatch): Promise<RestMember>;
|
|
85
96
|
removeMember(personId: string): Promise<void>;
|
|
@@ -97,6 +108,18 @@ export interface ConsoleActions {
|
|
|
97
108
|
page: { readonly limit: number; readonly offset: number },
|
|
98
109
|
signal?: AbortSignal,
|
|
99
110
|
): Promise<RestTasks>;
|
|
111
|
+
/** What words find among the current tasks of the project, a page at a time, for a picker (ADR-0037); the first page of them for no words. */
|
|
112
|
+
findTasks(
|
|
113
|
+
query: string,
|
|
114
|
+
page: { readonly limit: number; readonly offset: number },
|
|
115
|
+
signal?: AbortSignal,
|
|
116
|
+
): Promise<RestTasks>;
|
|
117
|
+
/** What words find among the open goals of the project, a page at a time, for a picker. */
|
|
118
|
+
findGoals(
|
|
119
|
+
query: string,
|
|
120
|
+
page: { readonly limit: number; readonly offset: number },
|
|
121
|
+
signal?: AbortSignal,
|
|
122
|
+
): Promise<RestGoals>;
|
|
100
123
|
raiseDecision(input: RestDecisionInput): Promise<RestDecision>;
|
|
101
124
|
answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
102
125
|
/** Grows a decision's record: its context, or what followed. */
|
|
@@ -133,6 +156,16 @@ export interface ConsoleActions {
|
|
|
133
156
|
removeListing(id: string): Promise<void>;
|
|
134
157
|
/** Keeps the person's own choice of language (ADR-0012). */
|
|
135
158
|
updateMe(patch: RestMePatch): Promise<RestMe>;
|
|
159
|
+
/** Changes the workspace's settings (ADR-0035); for an administrator. `me` says them from then on. */
|
|
160
|
+
updateWorkspace(patch: RestWorkspacePatch): Promise<RestWorkspace>;
|
|
161
|
+
/** The workspace's SkillCDN token (ADR-0036), for an administrator: set, taken away, or connected with a code. */
|
|
162
|
+
setWorkspacePackToken(token: string): Promise<RestWorkspace>;
|
|
163
|
+
removeWorkspacePackToken(): Promise<RestWorkspace>;
|
|
164
|
+
connectWorkspacePackToken(): Promise<RestPackConnectStarted>;
|
|
165
|
+
workspacePackTokenConnection(signal?: AbortSignal): Promise<RestWorkspacePackConnect>;
|
|
166
|
+
cancelWorkspacePackTokenConnection(): Promise<void>;
|
|
167
|
+
/** One person of the workspace by their exact login, or nobody: what an owner lists someone the People page does not show by. */
|
|
168
|
+
findPerson(login: string, signal?: AbortSignal): Promise<RestPerson | undefined>;
|
|
136
169
|
/** Marks a run that will not come back as abandoned. */
|
|
137
170
|
abandonRun(id: string): Promise<RestRun>;
|
|
138
171
|
/** Everything that happened to one task of the project, oldest first. */
|
|
@@ -245,6 +278,32 @@ async function readTasks(
|
|
|
245
278
|
return { items };
|
|
246
279
|
}
|
|
247
280
|
|
|
281
|
+
/** How many pages of the roadmap are read before it is shown as it stands. */
|
|
282
|
+
const GOALS_MAX_PAGES = 20;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* The goals of a project, whole: page after page while the server says there is more, since
|
|
286
|
+
* the roadmap, the calendar and the filters draw them all.
|
|
287
|
+
*/
|
|
288
|
+
async function readGoals(
|
|
289
|
+
scope: ProjectClient,
|
|
290
|
+
signal: AbortSignal | undefined,
|
|
291
|
+
): Promise<{ readonly items: RestGoal[] }> {
|
|
292
|
+
const items: RestGoal[] = [];
|
|
293
|
+
for (let page = 0; page < GOALS_MAX_PAGES; page += 1) {
|
|
294
|
+
const result = await scope.goals({ limit: LIST_LIMIT, offset: page * LIST_LIMIT }, signal);
|
|
295
|
+
items.push(...result.items);
|
|
296
|
+
if (!result.more) {
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return { items };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** The words of a search as the API takes them: none for a blank. */
|
|
304
|
+
const searchWordsOf = (query: string): { readonly q?: string } =>
|
|
305
|
+
query.trim() === "" ? {} : { q: query.trim() };
|
|
306
|
+
|
|
248
307
|
/** How many pages of the decisions are read before the record is shown as it stands. */
|
|
249
308
|
const DECISIONS_MAX_PAGES = 20;
|
|
250
309
|
|
|
@@ -333,7 +392,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
333
392
|
scope.get(signal),
|
|
334
393
|
readTasks(scope, signal),
|
|
335
394
|
readDecisions(scope, signal),
|
|
336
|
-
scope
|
|
395
|
+
readGoals(scope, signal),
|
|
337
396
|
scope.runs(undefined, signal),
|
|
338
397
|
scope.members(signal),
|
|
339
398
|
]);
|
|
@@ -538,6 +597,15 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
538
597
|
setProject(changed);
|
|
539
598
|
return changed;
|
|
540
599
|
},
|
|
600
|
+
connectPackToken: () => scope().connectPackToken(),
|
|
601
|
+
async packTokenConnection(signal) {
|
|
602
|
+
const state = await scope().packTokenConnection(signal);
|
|
603
|
+
if (state.status === "connected") {
|
|
604
|
+
setProject(state.project);
|
|
605
|
+
}
|
|
606
|
+
return state;
|
|
607
|
+
},
|
|
608
|
+
cancelPackTokenConnection: () => scope().cancelPackTokenConnection(),
|
|
541
609
|
async addMember(input) {
|
|
542
610
|
const member = await scope().addMember(input);
|
|
543
611
|
await refreshProject();
|
|
@@ -579,6 +647,10 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
579
647
|
return archived;
|
|
580
648
|
},
|
|
581
649
|
archivedTasks: (page, signal) => scope().tasks({ archived: true, ...page }, signal),
|
|
650
|
+
findTasks: (query, page, signal) =>
|
|
651
|
+
scope().tasks({ archived: false, ...searchWordsOf(query), ...page }, signal),
|
|
652
|
+
findGoals: (query, page, signal) =>
|
|
653
|
+
scope().goals({ open: true, ...searchWordsOf(query), ...page }, signal),
|
|
582
654
|
async raiseDecision(input) {
|
|
583
655
|
const decision = await scope().raiseDecision(input);
|
|
584
656
|
await refreshProject();
|
|
@@ -649,6 +721,34 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
649
721
|
setMe(answer);
|
|
650
722
|
return answer;
|
|
651
723
|
},
|
|
724
|
+
async updateWorkspace(patch) {
|
|
725
|
+
const workspace = await client.updateWorkspace(patch);
|
|
726
|
+
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
|
727
|
+
// The member visibility may have changed: the people are read again under the new setting.
|
|
728
|
+
await refreshWorkspace();
|
|
729
|
+
return workspace;
|
|
730
|
+
},
|
|
731
|
+
async setWorkspacePackToken(token) {
|
|
732
|
+
const workspace = await client.setWorkspacePackToken({ token });
|
|
733
|
+
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
|
734
|
+
return workspace;
|
|
735
|
+
},
|
|
736
|
+
async removeWorkspacePackToken() {
|
|
737
|
+
const workspace = await client.removeWorkspacePackToken();
|
|
738
|
+
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
|
739
|
+
return workspace;
|
|
740
|
+
},
|
|
741
|
+
connectWorkspacePackToken: () => client.connectWorkspacePackToken(),
|
|
742
|
+
async workspacePackTokenConnection(signal) {
|
|
743
|
+
const state = await client.workspacePackTokenConnection(signal);
|
|
744
|
+
if (state.status === "connected") {
|
|
745
|
+
const { workspace } = state;
|
|
746
|
+
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
|
747
|
+
}
|
|
748
|
+
return state;
|
|
749
|
+
},
|
|
750
|
+
cancelWorkspacePackTokenConnection: () => client.cancelWorkspacePackTokenConnection(),
|
|
751
|
+
findPerson: (login, signal) => client.findPerson(login, signal),
|
|
652
752
|
async abandonRun(id) {
|
|
653
753
|
const run = await scope().endRun(id, { status: "abandoned" });
|
|
654
754
|
await refreshProject();
|
package/src/dates.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { WeekStart } from "./vocabulary.js";
|
|
2
|
+
|
|
1
3
|
// Days, as the plan is made in (ADR-0019): `2026-10-20`, read and written as the strings they
|
|
2
4
|
// are, so that a deadline is the same day wherever the people are. The browser's own clock says
|
|
3
5
|
// which day is today; nothing here looks at it unless asked.
|
|
@@ -62,8 +64,27 @@ export function addMonths(month: string, count: number): string {
|
|
|
62
64
|
return `${String(date.getFullYear()).padStart(4, "0")}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
63
65
|
}
|
|
64
66
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
+
/**
|
|
68
|
+
* The language and region of the device the pages are read on, as the browser says: `ko-KR`,
|
|
69
|
+
* `en-GB`. The fallback where there is no browser to ask. The week begins as this has it
|
|
70
|
+
* where the workspace leaves the choice to the system default (ADR-0035).
|
|
71
|
+
*/
|
|
72
|
+
export function deviceLanguage(fallback: string): string {
|
|
73
|
+
const browser = (globalThis as { readonly navigator?: { readonly language?: string } }).navigator;
|
|
74
|
+
return browser?.language ?? fallback;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The weekday a week begins on: as the workspace says (ADR-0035), Monday or Sunday, else as the
|
|
79
|
+
* language and region have it, the device's by {@link deviceLanguage}. 1 for Monday, 7 for Sunday.
|
|
80
|
+
*/
|
|
81
|
+
export function firstDayOfWeek(language: string, weekStart?: WeekStart | undefined): number {
|
|
82
|
+
if (weekStart === "monday") {
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
if (weekStart === "sunday") {
|
|
86
|
+
return 7;
|
|
87
|
+
}
|
|
67
88
|
try {
|
|
68
89
|
const locale = new Intl.Locale(language) as Intl.Locale & {
|
|
69
90
|
readonly weekInfo?: { readonly firstDay: number };
|