@skillcdn/console 0.1.24 → 0.3.1
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 +8 -7
- package/dist/api.d.ts +2 -2
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +2 -2
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +248 -92
- 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 +13 -28
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +74 -274
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +36 -19
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/pack.d.ts +46 -0
- package/dist/cli/pack.d.ts.map +1 -0
- package/dist/cli/pack.js +250 -0
- package/dist/cli/pack.js.map +1 -0
- package/dist/client.d.ts +26 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +14 -1
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/calendar.d.ts +5 -1
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +3 -3
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/decision-list.d.ts +13 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +74 -29
- 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 +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/paged.d.ts +24 -0
- package/dist/components/paged.d.ts.map +1 -1
- package/dist/components/paged.js +40 -4
- package/dist/components/paged.js.map +1 -1
- 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/skills.d.ts.map +1 -1
- package/dist/components/skills.js +8 -4
- package/dist/components/skills.js.map +1 -1
- package/dist/components/ui.d.ts +2 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +7 -2
- 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 +1 -1
- 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 +268 -5
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +62 -23
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +16 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +64 -2
- 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 +66 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +80 -8
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +75 -7
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +10 -5
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +24 -9
- 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 +500 -158
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +169 -165
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +19 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +16 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +19 -14
- package/docs/specs/rest.md +34 -19
- package/package.json +2 -1
- package/skills/building-a-console/SKILL.md +5 -5
- 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/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 +2 -0
- package/skills/working-the-board/references/the-sentence.md +1 -1
- package/src/api.ts +18 -7
- package/src/cli/cli.ts +345 -109
- package/src/cli/credentials.ts +7 -0
- package/src/cli/directory.ts +9 -0
- package/src/cli/format.ts +82 -341
- package/src/cli/help.ts +43 -19
- package/src/cli/pack.ts +356 -0
- package/src/client.ts +84 -0
- package/src/compat.ts +1 -1
- package/src/components/calendar.tsx +8 -2
- package/src/components/decision-list.tsx +227 -94
- 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/paged.tsx +72 -3
- package/src/components/projects.tsx +108 -9
- package/src/components/skills.tsx +51 -33
- package/src/components/ui.tsx +16 -3
- package/src/components/work.tsx +4 -1
- package/src/components/workspace.tsx +298 -0
- package/src/console.tsx +176 -29
- package/src/data.ts +89 -2
- package/src/dates.ts +23 -2
- package/src/i18n/en.ts +94 -8
- package/src/i18n/ko.ts +84 -7
- package/src/index.ts +25 -1
- package/src/router.ts +37 -14
- package/src/routes.ts +4 -0
- package/src/schemas.ts +202 -189
- package/src/styles/console.css +268 -5
- package/src/vocabulary.ts +25 -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";
|
|
@@ -98,6 +105,7 @@ import {
|
|
|
98
105
|
calendarHref,
|
|
99
106
|
customPageHref,
|
|
100
107
|
decisionHref,
|
|
108
|
+
decisionsHref,
|
|
101
109
|
docHref,
|
|
102
110
|
type GoalTaskView,
|
|
103
111
|
goalHref,
|
|
@@ -860,6 +868,7 @@ function ProjectPage(props: {
|
|
|
860
868
|
tasks={shown}
|
|
861
869
|
month={month}
|
|
862
870
|
today={now}
|
|
871
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
863
872
|
categories={data.categories}
|
|
864
873
|
goals={data.goals}
|
|
865
874
|
taskHref={href}
|
|
@@ -1074,6 +1083,7 @@ function ProjectPage(props: {
|
|
|
1074
1083
|
tasks={towards}
|
|
1075
1084
|
month={month}
|
|
1076
1085
|
today={now}
|
|
1086
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
1077
1087
|
categories={data.categories}
|
|
1078
1088
|
goals={[goal]}
|
|
1079
1089
|
taskHref={href}
|
|
@@ -1112,6 +1122,8 @@ function ProjectPage(props: {
|
|
|
1112
1122
|
taskHref={href}
|
|
1113
1123
|
docHref={toDoc}
|
|
1114
1124
|
decisionHref={(decision) => decisionHref(key, decision.id)}
|
|
1125
|
+
page={route.page}
|
|
1126
|
+
pageHref={(page) => decisionsHref(key, page)}
|
|
1115
1127
|
onAnswer={onAnswer}
|
|
1116
1128
|
onUpdate={onUpdate}
|
|
1117
1129
|
busy={busy}
|
|
@@ -1217,19 +1229,34 @@ function ProjectPage(props: {
|
|
|
1217
1229
|
const me = data.me?.person ?? undefined;
|
|
1218
1230
|
const configures = owner;
|
|
1219
1231
|
const members = words.projects.members;
|
|
1232
|
+
// In a project open to the workspace everyone is a member already (ADR-0035): the list is
|
|
1233
|
+
// everyone, nobody is added or removed, and an owner is made from a row. Where the People
|
|
1234
|
+
// page shows a member only whom they share a project with, an owner lists someone the list
|
|
1235
|
+
// does not show by their login.
|
|
1236
|
+
const open = project.visibility === "workspace";
|
|
1237
|
+
const narrowed =
|
|
1238
|
+
peopleVisibilityOf(data.me?.workspace) === "shared_projects" && me?.role !== "admin";
|
|
1220
1239
|
return (
|
|
1221
1240
|
<>
|
|
1222
1241
|
<div className="sc-page-head">
|
|
1223
1242
|
<h1 className="sc-page-title">{members.title}</h1>
|
|
1224
1243
|
</div>
|
|
1225
|
-
<p className="sc-lead">{
|
|
1244
|
+
{open && <p className="sc-lead">{members.open}</p>}
|
|
1226
1245
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1227
1246
|
<components.MemberList
|
|
1228
1247
|
members={data.members}
|
|
1229
1248
|
people={data.people.filter(inWorkspace)}
|
|
1230
1249
|
me={me}
|
|
1250
|
+
open={open}
|
|
1231
1251
|
busy={busy}
|
|
1232
|
-
onAdd={
|
|
1252
|
+
onAdd={
|
|
1253
|
+
configures && !open
|
|
1254
|
+
? (input) => void act(() => data.actions.addMember(input))
|
|
1255
|
+
: undefined
|
|
1256
|
+
}
|
|
1257
|
+
lookup={
|
|
1258
|
+
configures && !open && narrowed ? (login) => data.actions.findPerson(login) : undefined
|
|
1259
|
+
}
|
|
1233
1260
|
onChangeRole={
|
|
1234
1261
|
configures
|
|
1235
1262
|
? (member, role) =>
|
|
@@ -1237,7 +1264,7 @@ function ProjectPage(props: {
|
|
|
1237
1264
|
: undefined
|
|
1238
1265
|
}
|
|
1239
1266
|
onRemove={
|
|
1240
|
-
configures
|
|
1267
|
+
configures && !open
|
|
1241
1268
|
? (member) => void act(() => data.actions.removeMember(member.person.id))
|
|
1242
1269
|
: undefined
|
|
1243
1270
|
}
|
|
@@ -1379,28 +1406,33 @@ function ProjectSettingsPage(props: {
|
|
|
1379
1406
|
<>
|
|
1380
1407
|
{head}
|
|
1381
1408
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1382
|
-
<
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1409
|
+
<PackTokenWays
|
|
1410
|
+
title={settings.packToken}
|
|
1411
|
+
intro={words.projects.packToken.intro}
|
|
1412
|
+
set={project.packToken !== null}
|
|
1413
|
+
setAt={project.packToken?.setAt}
|
|
1414
|
+
busy={busy}
|
|
1415
|
+
onStart={data.actions.connectPackToken}
|
|
1416
|
+
poll={data.actions.packTokenConnection}
|
|
1417
|
+
onCancelConnect={data.actions.cancelPackTokenConnection}
|
|
1418
|
+
onConnected={() => navigation.replace(here)}
|
|
1419
|
+
onSubmit={(token) =>
|
|
1420
|
+
void act(async () => {
|
|
1421
|
+
await data.actions.setPackToken(token);
|
|
1422
|
+
navigation.replace(here);
|
|
1423
|
+
})
|
|
1424
|
+
}
|
|
1425
|
+
onRemove={
|
|
1426
|
+
project.packToken === null
|
|
1427
|
+
? undefined
|
|
1428
|
+
: () =>
|
|
1429
|
+
void act(async () => {
|
|
1430
|
+
await data.actions.removePackToken();
|
|
1431
|
+
navigation.replace(here);
|
|
1432
|
+
})
|
|
1433
|
+
}
|
|
1434
|
+
onBack={() => navigation.back(here)}
|
|
1435
|
+
/>
|
|
1404
1436
|
</>
|
|
1405
1437
|
);
|
|
1406
1438
|
}
|
|
@@ -1452,7 +1484,9 @@ function ProjectSettingsPage(props: {
|
|
|
1452
1484
|
{owner && (
|
|
1453
1485
|
<>
|
|
1454
1486
|
{" "}
|
|
1455
|
-
<a href={projectSettingsHref(key, "pack-token")}>
|
|
1487
|
+
<a href={projectSettingsHref(key, "pack-token")}>
|
|
1488
|
+
{project.packToken === null ? settings.packTokenAdd : settings.packTokenEdit}
|
|
1489
|
+
</a>
|
|
1456
1490
|
</>
|
|
1457
1491
|
)}
|
|
1458
1492
|
</dd>
|
|
@@ -1513,7 +1547,7 @@ function ProjectSettingsPage(props: {
|
|
|
1513
1547
|
* from, which whoever runs the console set. Read by everyone; the rules written by an administrator.
|
|
1514
1548
|
*/
|
|
1515
1549
|
function WorkspaceSettingsPage(props: {
|
|
1516
|
-
readonly form: "rules" | undefined;
|
|
1550
|
+
readonly form: "rules" | "workspace" | "pack-token" | undefined;
|
|
1517
1551
|
readonly data: ConsoleData;
|
|
1518
1552
|
readonly navigation: Navigation;
|
|
1519
1553
|
}) {
|
|
@@ -1521,7 +1555,8 @@ function WorkspaceSettingsPage(props: {
|
|
|
1521
1555
|
const { form, data, navigation } = props;
|
|
1522
1556
|
const { busy, error, act } = useAction();
|
|
1523
1557
|
const administrator = data.me?.person?.role === "admin";
|
|
1524
|
-
const
|
|
1558
|
+
const workspace = data.me?.workspace;
|
|
1559
|
+
const skills = workspace?.skills;
|
|
1525
1560
|
const [rules, setRules] = useState<RestRules | undefined>(undefined);
|
|
1526
1561
|
useEffect(() => {
|
|
1527
1562
|
const controller = new AbortController();
|
|
@@ -1584,10 +1619,90 @@ function WorkspaceSettingsPage(props: {
|
|
|
1584
1619
|
</>
|
|
1585
1620
|
);
|
|
1586
1621
|
}
|
|
1622
|
+
if (form === "pack-token") {
|
|
1623
|
+
if (!administrator || workspace === undefined) {
|
|
1624
|
+
return <NothingHere />;
|
|
1625
|
+
}
|
|
1626
|
+
const held = workspace.skills.packToken ?? null;
|
|
1627
|
+
return (
|
|
1628
|
+
<>
|
|
1629
|
+
{head}
|
|
1630
|
+
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1631
|
+
<PackTokenWays
|
|
1632
|
+
title={words.settings.skillsToken}
|
|
1633
|
+
intro={words.settings.skillsTokenIntro}
|
|
1634
|
+
set={held !== null}
|
|
1635
|
+
setAt={held?.setAt}
|
|
1636
|
+
busy={busy}
|
|
1637
|
+
onStart={data.actions.connectWorkspacePackToken}
|
|
1638
|
+
poll={data.actions.workspacePackTokenConnection}
|
|
1639
|
+
onCancelConnect={data.actions.cancelWorkspacePackTokenConnection}
|
|
1640
|
+
onConnected={() => navigation.replace(PATHS.settings)}
|
|
1641
|
+
onSubmit={(token) =>
|
|
1642
|
+
void act(async () => {
|
|
1643
|
+
await data.actions.setWorkspacePackToken(token);
|
|
1644
|
+
navigation.replace(PATHS.settings);
|
|
1645
|
+
})
|
|
1646
|
+
}
|
|
1647
|
+
onRemove={
|
|
1648
|
+
held === null
|
|
1649
|
+
? undefined
|
|
1650
|
+
: () =>
|
|
1651
|
+
void act(async () => {
|
|
1652
|
+
await data.actions.removeWorkspacePackToken();
|
|
1653
|
+
navigation.replace(PATHS.settings);
|
|
1654
|
+
})
|
|
1655
|
+
}
|
|
1656
|
+
onBack={() => navigation.back(PATHS.settings)}
|
|
1657
|
+
/>
|
|
1658
|
+
</>
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
if (form === "workspace") {
|
|
1662
|
+
if (!administrator || workspace === undefined) {
|
|
1663
|
+
return <NothingHere />;
|
|
1664
|
+
}
|
|
1665
|
+
return (
|
|
1666
|
+
<>
|
|
1667
|
+
{head}
|
|
1668
|
+
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1669
|
+
<section className="sc-panel sc-panel-form" aria-label={words.settings.workspace.aria}>
|
|
1670
|
+
<h2 className="sc-section-title">{words.settings.workspace.title}</h2>
|
|
1671
|
+
<WorkspaceSettingsForm
|
|
1672
|
+
workspace={workspace}
|
|
1673
|
+
busy={busy}
|
|
1674
|
+
onSubmit={(patch) =>
|
|
1675
|
+
void act(async () => {
|
|
1676
|
+
await data.actions.updateWorkspace(patch);
|
|
1677
|
+
navigation.replace(PATHS.settings);
|
|
1678
|
+
})
|
|
1679
|
+
}
|
|
1680
|
+
onCancel={() => navigation.back(PATHS.settings)}
|
|
1681
|
+
/>
|
|
1682
|
+
</section>
|
|
1683
|
+
</>
|
|
1684
|
+
);
|
|
1685
|
+
}
|
|
1587
1686
|
return (
|
|
1588
1687
|
<>
|
|
1589
1688
|
{head}
|
|
1590
1689
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1690
|
+
{workspace !== undefined && (
|
|
1691
|
+
<section className="sc-panel" aria-label={words.settings.workspace.aria}>
|
|
1692
|
+
<div className="sc-section-head">
|
|
1693
|
+
<h2 className="sc-section-title">{words.settings.workspace.title}</h2>
|
|
1694
|
+
{administrator && (
|
|
1695
|
+
<a
|
|
1696
|
+
className="sc-button sc-button-secondary sc-button-sm"
|
|
1697
|
+
href={settingsHref("workspace")}
|
|
1698
|
+
>
|
|
1699
|
+
{words.common.edit}
|
|
1700
|
+
</a>
|
|
1701
|
+
)}
|
|
1702
|
+
</div>
|
|
1703
|
+
<WorkspaceSettingsView workspace={workspace} />
|
|
1704
|
+
</section>
|
|
1705
|
+
)}
|
|
1591
1706
|
<section className="sc-panel" aria-label={words.rules.title}>
|
|
1592
1707
|
<h2 className="sc-section-title">{words.rules.title}</h2>
|
|
1593
1708
|
{rules === undefined ? (
|
|
@@ -1648,6 +1763,30 @@ function WorkspaceSettingsPage(props: {
|
|
|
1648
1763
|
)}
|
|
1649
1764
|
</dd>
|
|
1650
1765
|
</div>
|
|
1766
|
+
{skills.address !== null && (
|
|
1767
|
+
<div>
|
|
1768
|
+
<dt>{words.settings.skillsToken}</dt>
|
|
1769
|
+
<dd>
|
|
1770
|
+
{skills.packToken == null ? (
|
|
1771
|
+
<span className="sc-muted">{words.projects.settings.packTokenNone}</span>
|
|
1772
|
+
) : (
|
|
1773
|
+
<>
|
|
1774
|
+
{words.projects.settings.packTokenSet} <Time iso={skills.packToken.setAt} />
|
|
1775
|
+
</>
|
|
1776
|
+
)}
|
|
1777
|
+
{administrator && (
|
|
1778
|
+
<>
|
|
1779
|
+
{" "}
|
|
1780
|
+
<a href={settingsHref("pack-token")}>
|
|
1781
|
+
{skills.packToken == null
|
|
1782
|
+
? words.projects.settings.packTokenAdd
|
|
1783
|
+
: words.projects.settings.packTokenEdit}
|
|
1784
|
+
</a>
|
|
1785
|
+
</>
|
|
1786
|
+
)}
|
|
1787
|
+
</dd>
|
|
1788
|
+
</div>
|
|
1789
|
+
)}
|
|
1651
1790
|
</dl>
|
|
1652
1791
|
<p className="sc-muted">{words.settings.skillsFromConfiguration}</p>
|
|
1653
1792
|
</section>
|
|
@@ -1774,6 +1913,8 @@ function PlatePage(props: {
|
|
|
1774
1913
|
readonly empty: { readonly title: string; readonly body: string };
|
|
1775
1914
|
/** What a refusal says where the plate may not be the asker's to see; the server's words otherwise. */
|
|
1776
1915
|
readonly refused?: { readonly title: string; readonly body: string } | undefined;
|
|
1916
|
+
/** The day the week begins on, as the workspace says (ADR-0035). */
|
|
1917
|
+
readonly weekStart?: WeekStart | undefined;
|
|
1777
1918
|
}) {
|
|
1778
1919
|
const words = useWords();
|
|
1779
1920
|
const { components, load, couldNotLoad, refused } = props;
|
|
@@ -1834,6 +1975,7 @@ function PlatePage(props: {
|
|
|
1834
1975
|
<components.WorkList
|
|
1835
1976
|
work={plate}
|
|
1836
1977
|
week={props.week}
|
|
1978
|
+
weekStart={props.weekStart}
|
|
1837
1979
|
taskHref={(projectKey, task) => taskHref(projectKey, task.number)}
|
|
1838
1980
|
decisionHref={(projectKey, decisionId) => decisionHref(projectKey, decisionId)}
|
|
1839
1981
|
weekHref={props.weekHref}
|
|
@@ -1857,6 +1999,7 @@ function WorkPage(props: {
|
|
|
1857
1999
|
load={work}
|
|
1858
2000
|
title={words.work.title}
|
|
1859
2001
|
week={props.week}
|
|
2002
|
+
weekStart={weekStartOf(props.data.me?.workspace)}
|
|
1860
2003
|
weekHref={workHref}
|
|
1861
2004
|
loading={words.work.loading}
|
|
1862
2005
|
couldNotLoad={words.work.couldNotLoad}
|
|
@@ -1885,6 +2028,7 @@ function PersonWorkPage(props: {
|
|
|
1885
2028
|
title={words.people.workOf(name)}
|
|
1886
2029
|
aside={<LinkButton href={PATHS.people}>{words.nav.people}</LinkButton>}
|
|
1887
2030
|
week={props.week}
|
|
2031
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
1888
2032
|
weekHref={(day) => personWorkHref(id, day)}
|
|
1889
2033
|
loading={words.people.loadingWork}
|
|
1890
2034
|
couldNotLoad={words.people.couldNotLoadWork}
|
|
@@ -1955,6 +2099,8 @@ function WorkspacePage(props: {
|
|
|
1955
2099
|
const making = route.name === "new-listing" && administrator;
|
|
1956
2100
|
// An owner sees the work of the people in their projects; the console refuses the rest.
|
|
1957
2101
|
const oversees = administrator || data.projects.some((project) => project.role === "owner");
|
|
2102
|
+
// The list is what the policy shows (ADR-0035): everyone, or the people one shares a project with.
|
|
2103
|
+
const narrowed = peopleVisibilityOf(data.me?.workspace) === "shared_projects" && !administrator;
|
|
1958
2104
|
return (
|
|
1959
2105
|
<>
|
|
1960
2106
|
<div className="sc-page-head">
|
|
@@ -1965,6 +2111,7 @@ function WorkspacePage(props: {
|
|
|
1965
2111
|
</LinkButton>
|
|
1966
2112
|
)}
|
|
1967
2113
|
</div>
|
|
2114
|
+
{narrowed && <p className="sc-lead">{words.people.narrowed}</p>}
|
|
1968
2115
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1969
2116
|
{making && (
|
|
1970
2117
|
<section className="sc-panel sc-panel-form" aria-label={words.people.letInAria}>
|
package/src/data.ts
CHANGED
|
@@ -32,6 +32,8 @@ import {
|
|
|
32
32
|
type RestMemberPatch,
|
|
33
33
|
type RestMePatch,
|
|
34
34
|
type RestOrganization,
|
|
35
|
+
type RestPackConnect,
|
|
36
|
+
type RestPackConnectStarted,
|
|
35
37
|
type RestPerson,
|
|
36
38
|
type RestPersonPatch,
|
|
37
39
|
type RestPersonWork,
|
|
@@ -54,6 +56,9 @@ import {
|
|
|
54
56
|
type RestVersion,
|
|
55
57
|
type RestVersions,
|
|
56
58
|
type RestWork,
|
|
59
|
+
type RestWorkspace,
|
|
60
|
+
type RestWorkspacePackConnect,
|
|
61
|
+
type RestWorkspacePatch,
|
|
57
62
|
restEventSchema,
|
|
58
63
|
} from "./api.js";
|
|
59
64
|
|
|
@@ -80,6 +85,11 @@ export interface ConsoleActions {
|
|
|
80
85
|
/** Sets the pack token of the project the page is on, or takes it away; for an owner (ADR-0028). */
|
|
81
86
|
setPackToken(token: string): Promise<RestProject>;
|
|
82
87
|
removePackToken(): Promise<RestProject>;
|
|
88
|
+
/** Asks the deployment to connect the project's pack with a code a person approves (ADR-0036); for an owner. */
|
|
89
|
+
connectPackToken(): Promise<RestPackConnectStarted>;
|
|
90
|
+
/** How that connection stands; once connected, the project is the one answered. */
|
|
91
|
+
packTokenConnection(signal?: AbortSignal): Promise<RestPackConnect>;
|
|
92
|
+
cancelPackTokenConnection(): Promise<void>;
|
|
83
93
|
addMember(input: RestMemberInput): Promise<RestMember>;
|
|
84
94
|
updateMember(personId: string, patch: RestMemberPatch): Promise<RestMember>;
|
|
85
95
|
removeMember(personId: string): Promise<void>;
|
|
@@ -133,6 +143,16 @@ export interface ConsoleActions {
|
|
|
133
143
|
removeListing(id: string): Promise<void>;
|
|
134
144
|
/** Keeps the person's own choice of language (ADR-0012). */
|
|
135
145
|
updateMe(patch: RestMePatch): Promise<RestMe>;
|
|
146
|
+
/** Changes the workspace's settings (ADR-0035); for an administrator. `me` says them from then on. */
|
|
147
|
+
updateWorkspace(patch: RestWorkspacePatch): Promise<RestWorkspace>;
|
|
148
|
+
/** The workspace's SkillCDN token (ADR-0036), for an administrator: set, taken away, or connected with a code. */
|
|
149
|
+
setWorkspacePackToken(token: string): Promise<RestWorkspace>;
|
|
150
|
+
removeWorkspacePackToken(): Promise<RestWorkspace>;
|
|
151
|
+
connectWorkspacePackToken(): Promise<RestPackConnectStarted>;
|
|
152
|
+
workspacePackTokenConnection(signal?: AbortSignal): Promise<RestWorkspacePackConnect>;
|
|
153
|
+
cancelWorkspacePackTokenConnection(): Promise<void>;
|
|
154
|
+
/** One person of the workspace by their exact login, or nobody: what an owner lists someone the People page does not show by. */
|
|
155
|
+
findPerson(login: string, signal?: AbortSignal): Promise<RestPerson | undefined>;
|
|
136
156
|
/** Marks a run that will not come back as abandoned. */
|
|
137
157
|
abandonRun(id: string): Promise<RestRun>;
|
|
138
158
|
/** Everything that happened to one task of the project, oldest first. */
|
|
@@ -245,6 +265,28 @@ async function readTasks(
|
|
|
245
265
|
return { items };
|
|
246
266
|
}
|
|
247
267
|
|
|
268
|
+
/** How many pages of the decisions are read before the record is shown as it stands. */
|
|
269
|
+
const DECISIONS_MAX_PAGES = 20;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* The decisions of a project, whole: page after page while the server says there is more, so
|
|
273
|
+
* that the record is read to its end and the pages turn it without asking again.
|
|
274
|
+
*/
|
|
275
|
+
async function readDecisions(
|
|
276
|
+
scope: ProjectClient,
|
|
277
|
+
signal: AbortSignal | undefined,
|
|
278
|
+
): Promise<{ readonly items: RestDecision[] }> {
|
|
279
|
+
const items: RestDecision[] = [];
|
|
280
|
+
for (let page = 0; page < DECISIONS_MAX_PAGES; page += 1) {
|
|
281
|
+
const result = await scope.decisions({ limit: LIST_LIMIT, offset: page * LIST_LIMIT }, signal);
|
|
282
|
+
items.push(...result.items);
|
|
283
|
+
if (!result.more) {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { items };
|
|
288
|
+
}
|
|
289
|
+
|
|
248
290
|
const wordsOf = (failure: unknown, fallback: string): string =>
|
|
249
291
|
failure instanceof ApiError ? failure.message : fallback;
|
|
250
292
|
|
|
@@ -310,7 +352,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
310
352
|
await Promise.all([
|
|
311
353
|
scope.get(signal),
|
|
312
354
|
readTasks(scope, signal),
|
|
313
|
-
scope
|
|
355
|
+
readDecisions(scope, signal),
|
|
314
356
|
scope.goals({}, signal),
|
|
315
357
|
scope.runs(undefined, signal),
|
|
316
358
|
scope.members(signal),
|
|
@@ -427,7 +469,15 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
427
469
|
// No answer at all is said on the page as the deployment being out of reach,
|
|
428
470
|
// rather than waited for.
|
|
429
471
|
if (!signal.aborted) {
|
|
430
|
-
setSkills({
|
|
472
|
+
setSkills({
|
|
473
|
+
address: null,
|
|
474
|
+
source: "",
|
|
475
|
+
page: null,
|
|
476
|
+
status: "unavailable",
|
|
477
|
+
refusal: null,
|
|
478
|
+
items: [],
|
|
479
|
+
mount: null,
|
|
480
|
+
});
|
|
431
481
|
}
|
|
432
482
|
});
|
|
433
483
|
if (typeof EventSource === "undefined") {
|
|
@@ -508,6 +558,15 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
508
558
|
setProject(changed);
|
|
509
559
|
return changed;
|
|
510
560
|
},
|
|
561
|
+
connectPackToken: () => scope().connectPackToken(),
|
|
562
|
+
async packTokenConnection(signal) {
|
|
563
|
+
const state = await scope().packTokenConnection(signal);
|
|
564
|
+
if (state.status === "connected") {
|
|
565
|
+
setProject(state.project);
|
|
566
|
+
}
|
|
567
|
+
return state;
|
|
568
|
+
},
|
|
569
|
+
cancelPackTokenConnection: () => scope().cancelPackTokenConnection(),
|
|
511
570
|
async addMember(input) {
|
|
512
571
|
const member = await scope().addMember(input);
|
|
513
572
|
await refreshProject();
|
|
@@ -619,6 +678,34 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
619
678
|
setMe(answer);
|
|
620
679
|
return answer;
|
|
621
680
|
},
|
|
681
|
+
async updateWorkspace(patch) {
|
|
682
|
+
const workspace = await client.updateWorkspace(patch);
|
|
683
|
+
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
|
684
|
+
// The member visibility may have changed: the people are read again under the new setting.
|
|
685
|
+
await refreshWorkspace();
|
|
686
|
+
return workspace;
|
|
687
|
+
},
|
|
688
|
+
async setWorkspacePackToken(token) {
|
|
689
|
+
const workspace = await client.setWorkspacePackToken({ token });
|
|
690
|
+
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
|
691
|
+
return workspace;
|
|
692
|
+
},
|
|
693
|
+
async removeWorkspacePackToken() {
|
|
694
|
+
const workspace = await client.removeWorkspacePackToken();
|
|
695
|
+
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
|
696
|
+
return workspace;
|
|
697
|
+
},
|
|
698
|
+
connectWorkspacePackToken: () => client.connectWorkspacePackToken(),
|
|
699
|
+
async workspacePackTokenConnection(signal) {
|
|
700
|
+
const state = await client.workspacePackTokenConnection(signal);
|
|
701
|
+
if (state.status === "connected") {
|
|
702
|
+
const { workspace } = state;
|
|
703
|
+
setMe((current) => (current === undefined ? undefined : { ...current, workspace }));
|
|
704
|
+
}
|
|
705
|
+
return state;
|
|
706
|
+
},
|
|
707
|
+
cancelWorkspacePackTokenConnection: () => client.cancelWorkspacePackTokenConnection(),
|
|
708
|
+
findPerson: (login, signal) => client.findPerson(login, signal),
|
|
622
709
|
async abandonRun(id) {
|
|
623
710
|
const run = await scope().endRun(id, { status: "abandoned" });
|
|
624
711
|
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 };
|