@skillcdn/console 0.1.10 → 0.1.12
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 -5
- package/dist/api.d.ts +6 -5
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +5 -4
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +49 -9
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +2 -0
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +7 -6
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +10 -9
- package/dist/cli/help.js.map +1 -1
- package/dist/client.d.ts +14 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +13 -1
- package/dist/client.js.map +1 -1
- package/dist/components/board.d.ts +1 -4
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +6 -13
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.d.ts +21 -1
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +69 -9
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +11 -9
- 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 +11 -7
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/paged.d.ts +14 -0
- package/dist/components/paged.d.ts.map +1 -0
- package/dist/components/paged.js +28 -0
- package/dist/components/paged.js.map +1 -0
- package/dist/components/people.js +2 -2
- package/dist/components/people.js.map +1 -1
- package/dist/components/projects.d.ts +22 -2
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +37 -9
- package/dist/components/projects.js.map +1 -1
- package/dist/components/runs.d.ts.map +1 -1
- package/dist/components/runs.js +3 -3
- package/dist/components/runs.js.map +1 -1
- package/dist/components/shell.d.ts +2 -1
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +3 -3
- package/dist/components/shell.js.map +1 -1
- package/dist/components/task-form.js +2 -2
- package/dist/components/task-form.js.map +1 -1
- package/dist/components/task-list.d.ts +1 -1
- package/dist/components/task-list.d.ts.map +1 -1
- package/dist/components/task-list.js +6 -4
- package/dist/components/task-list.js.map +1 -1
- package/dist/components/task-view.js +1 -1
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +7 -2
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +9 -4
- package/dist/components/ui.js.map +1 -1
- package/dist/components/work.d.ts.map +1 -1
- package/dist/components/work.js +7 -3
- package/dist/components/work.js.map +1 -1
- package/dist/console.css +149 -2
- package/dist/console.d.ts +38 -4
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +84 -23
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +5 -2
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +15 -2
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +17 -11
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +18 -14
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +18 -14
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +6 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +6 -0
- package/dist/limits.js.map +1 -1
- package/dist/marks.d.ts +12 -0
- package/dist/marks.d.ts.map +1 -0
- package/dist/marks.js +31 -0
- package/dist/marks.js.map +1 -0
- package/dist/router.d.ts +19 -3
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +28 -5
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +7 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +8 -0
- package/dist/routes.js.map +1 -1
- package/dist/schemas.d.ts +123 -3
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +42 -10
- package/dist/schemas.js.map +1 -1
- package/dist/vocabulary.d.ts +10 -4
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +8 -3
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +7 -5
- package/docs/specs/rest.md +18 -12
- package/package.json +1 -1
- package/skills/building-a-console/SKILL.md +2 -2
- package/skills/working-the-board/SKILL.md +1 -1
- package/src/api.ts +12 -0
- package/src/cli/cli.ts +51 -8
- package/src/cli/format.ts +8 -6
- package/src/cli/help.ts +10 -10
- package/src/client.ts +29 -6
- package/src/components/board.tsx +6 -30
- package/src/components/calendar.tsx +168 -56
- package/src/components/decision-list.tsx +38 -33
- package/src/components/event-feed.tsx +34 -27
- package/src/components/paged.tsx +39 -0
- package/src/components/people.tsx +5 -5
- package/src/components/projects.tsx +132 -12
- package/src/components/runs.tsx +9 -3
- package/src/components/shell.tsx +8 -5
- package/src/components/task-form.tsx +2 -2
- package/src/components/task-list.tsx +35 -32
- package/src/components/task-view.tsx +1 -1
- package/src/components/ui.tsx +14 -5
- package/src/components/work.tsx +12 -2
- package/src/console.tsx +158 -25
- package/src/data.ts +20 -4
- package/src/i18n/en.ts +19 -14
- package/src/i18n/ko.ts +19 -14
- package/src/index.ts +9 -0
- package/src/limits.ts +6 -0
- package/src/marks.ts +40 -0
- package/src/router.ts +45 -7
- package/src/routes.ts +9 -0
- package/src/schemas.ts +48 -8
- package/src/styles/console.css +149 -2
- package/src/vocabulary.ts +14 -4
package/src/console.tsx
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ComponentType,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
StrictMode,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useState,
|
|
9
|
+
} from "react";
|
|
2
10
|
import { createRoot, type Root } from "react-dom/client";
|
|
3
11
|
import {
|
|
4
12
|
type ConsoleClient,
|
|
5
13
|
createClient,
|
|
14
|
+
type FileUpload,
|
|
6
15
|
OPEN_TASK_STATES,
|
|
7
16
|
type RestAnswerInput,
|
|
8
17
|
type RestDecision,
|
|
@@ -34,6 +43,7 @@ import {
|
|
|
34
43
|
ProjectForm,
|
|
35
44
|
ProjectList,
|
|
36
45
|
type ProjectListProps,
|
|
46
|
+
ProjectMark,
|
|
37
47
|
} from "./components/projects.js";
|
|
38
48
|
import {
|
|
39
49
|
type Brand,
|
|
@@ -48,7 +58,7 @@ import { TaskForm } from "./components/task-form.js";
|
|
|
48
58
|
import { TaskList } from "./components/task-list.js";
|
|
49
59
|
import { TaskView, type TaskViewProps } from "./components/task-view.js";
|
|
50
60
|
import { NewToken, TokenForm, TokenList, type TokenListProps } from "./components/tokens.js";
|
|
51
|
-
import { Button, Callout, EmptyState, Spinner } from "./components/ui.js";
|
|
61
|
+
import { Button, Callout, EmptyState, personName, Spinner } from "./components/ui.js";
|
|
52
62
|
import { WorkList, type WorkListProps } from "./components/work.js";
|
|
53
63
|
import { ConnectPage } from "./connect-page.js";
|
|
54
64
|
import { type ConsoleData, useConsoleData } from "./data.js";
|
|
@@ -59,11 +69,13 @@ import {
|
|
|
59
69
|
DEFAULT_LANGUAGES,
|
|
60
70
|
LanguageContext,
|
|
61
71
|
type LanguagePack,
|
|
72
|
+
type Messages,
|
|
62
73
|
useWords,
|
|
63
74
|
} from "./i18n/index.js";
|
|
64
75
|
import {
|
|
65
76
|
boardHref,
|
|
66
77
|
calendarHref,
|
|
78
|
+
customPageHref,
|
|
67
79
|
decisionHref,
|
|
68
80
|
docHref,
|
|
69
81
|
goalHref,
|
|
@@ -126,6 +138,52 @@ export const DEFAULT_COMPONENTS: ConsoleComponents = {
|
|
|
126
138
|
CategoryList,
|
|
127
139
|
};
|
|
128
140
|
|
|
141
|
+
/**
|
|
142
|
+
* A page a console adds of its own, shown as a tab beside the console's: on the workspace, or
|
|
143
|
+
* on every project. The address is `/pages/<key>`, or `/projects/<project>/pages/<key>`.
|
|
144
|
+
*/
|
|
145
|
+
export interface CustomPage {
|
|
146
|
+
/** What the address says, and what tells the page apart: lowercase letters, digits and hyphens. */
|
|
147
|
+
readonly key: string;
|
|
148
|
+
/** What the tab says: one word for every language, or a word by language tag, `en` standing in for a tag without one. */
|
|
149
|
+
readonly label: string | Readonly<Record<string, string>>;
|
|
150
|
+
/** Where the tab is: among the workspace's pages, or among each project's. */
|
|
151
|
+
readonly level: "workspace" | "project";
|
|
152
|
+
/** Whether the page takes the whole width of the window, as the board does. */
|
|
153
|
+
readonly wide?: boolean | undefined;
|
|
154
|
+
/** What the page shows, given what the default pages are given. */
|
|
155
|
+
readonly render: (context: CustomPageContext) => ReactNode;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** What a page of a console's own is given: the data the default pages read, and the means they use. */
|
|
159
|
+
export interface CustomPageContext {
|
|
160
|
+
/** The workspace and, on a project, the project the page is on, kept current; with the actions. */
|
|
161
|
+
readonly data: ConsoleData;
|
|
162
|
+
/** The project the page is on, or nothing on the workspace. */
|
|
163
|
+
readonly project: RestProject | undefined;
|
|
164
|
+
/** The client, for what the data does not carry. */
|
|
165
|
+
readonly client: ConsoleClient;
|
|
166
|
+
readonly navigation: Navigation;
|
|
167
|
+
/** The words of the language the pages speak, and its tag. */
|
|
168
|
+
readonly words: Messages;
|
|
169
|
+
readonly language: string;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** What the tab of a page of one's own says in a language: its word for the tag, for the language alone, for English, or the first it has. */
|
|
173
|
+
export function customPageLabel(page: CustomPage, tag: string): string {
|
|
174
|
+
if (typeof page.label === "string") {
|
|
175
|
+
return page.label;
|
|
176
|
+
}
|
|
177
|
+
const language = tag.split("-")[0] ?? tag;
|
|
178
|
+
return (
|
|
179
|
+
page.label[tag] ??
|
|
180
|
+
page.label[language] ??
|
|
181
|
+
page.label.en ??
|
|
182
|
+
Object.values(page.label)[0] ??
|
|
183
|
+
page.key
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
129
187
|
export interface ConsoleConfig {
|
|
130
188
|
/** The origin of the console's API. Left out, the page's own origin. */
|
|
131
189
|
readonly baseUrl?: string | undefined;
|
|
@@ -139,6 +197,8 @@ export interface ConsoleConfig {
|
|
|
139
197
|
readonly brand?: Brand | undefined;
|
|
140
198
|
/** The client to talk to the server with; made from `baseUrl` when left out. For tests. */
|
|
141
199
|
readonly client?: ConsoleClient | undefined;
|
|
200
|
+
/** Pages of the console's own, as tabs beside the default ones. */
|
|
201
|
+
readonly pages?: readonly CustomPage[] | undefined;
|
|
142
202
|
}
|
|
143
203
|
|
|
144
204
|
export interface ConsoleApp {
|
|
@@ -354,9 +414,12 @@ function ProjectsPage(props: {
|
|
|
354
414
|
<section className="sc-panel sc-panel-form" aria-label={words.form.aria}>
|
|
355
415
|
<ProjectForm
|
|
356
416
|
busy={busy}
|
|
357
|
-
onSubmit={(input) =>
|
|
417
|
+
onSubmit={(input, icon) =>
|
|
358
418
|
void act(async () => {
|
|
359
|
-
const made = await data.actions.createProject(
|
|
419
|
+
const made = await data.actions.createProject(
|
|
420
|
+
input as RestProjectInput,
|
|
421
|
+
icon instanceof File ? uploadOf(icon) : undefined,
|
|
422
|
+
);
|
|
360
423
|
navigation.replace(projectHref(made.key));
|
|
361
424
|
})
|
|
362
425
|
}
|
|
@@ -532,7 +595,6 @@ function ProjectPage(props: {
|
|
|
532
595
|
)}
|
|
533
596
|
<components.Board
|
|
534
597
|
tasks={shown}
|
|
535
|
-
runs={data.runs}
|
|
536
598
|
taskHref={href}
|
|
537
599
|
onOpen={(task) => navigation.go(href(task))}
|
|
538
600
|
onMove={onMove}
|
|
@@ -592,7 +654,9 @@ function ProjectPage(props: {
|
|
|
592
654
|
if (route.name === "calendar") {
|
|
593
655
|
const now = today();
|
|
594
656
|
const month = route.month ?? monthOf(now);
|
|
595
|
-
const
|
|
657
|
+
const me = data.me?.person ?? undefined;
|
|
658
|
+
const shown = filterTasks(data.tasks, route.filter, me);
|
|
659
|
+
const open = shown.filter((task) => OPEN_TASK_STATES.includes(task.state));
|
|
596
660
|
const overdue = open.filter((task) => task.dueOn !== null && task.dueOn < now).sort(byDue);
|
|
597
661
|
const unscheduled = open.filter((task) => task.dueOn === null).sort(byDue);
|
|
598
662
|
const calendar = words.calendar;
|
|
@@ -605,12 +669,25 @@ function ProjectPage(props: {
|
|
|
605
669
|
</LinkButton>
|
|
606
670
|
</div>
|
|
607
671
|
<p className="sc-lead">{calendar.lead}</p>
|
|
672
|
+
{data.tasks.length > 0 && (
|
|
673
|
+
<BoardFilters
|
|
674
|
+
filter={route.filter}
|
|
675
|
+
categories={data.categories}
|
|
676
|
+
goals={data.goals}
|
|
677
|
+
people={data.people}
|
|
678
|
+
me={me}
|
|
679
|
+
shown={shown.length}
|
|
680
|
+
total={data.tasks.length}
|
|
681
|
+
onChange={(filter) => navigation.replace(calendarHref(key, route.month, filter))}
|
|
682
|
+
/>
|
|
683
|
+
)}
|
|
608
684
|
<components.Calendar
|
|
609
|
-
tasks={
|
|
685
|
+
tasks={shown}
|
|
610
686
|
month={month}
|
|
611
687
|
today={now}
|
|
688
|
+
categories={data.categories}
|
|
612
689
|
taskHref={href}
|
|
613
|
-
monthHref={(wanted) => calendarHref(key, wanted)}
|
|
690
|
+
monthHref={(wanted) => calendarHref(key, wanted, route.filter)}
|
|
614
691
|
/>
|
|
615
692
|
<div className="sc-calendar-below">
|
|
616
693
|
<section className="sc-task-section" aria-label={calendar.overdue}>
|
|
@@ -643,7 +720,7 @@ function ProjectPage(props: {
|
|
|
643
720
|
<h2 className="sc-section-title">{calendar.workload.title}</h2>
|
|
644
721
|
<p className="sc-lead">{calendar.workload.lead}</p>
|
|
645
722
|
<Workload
|
|
646
|
-
tasks={
|
|
723
|
+
tasks={shown}
|
|
647
724
|
today={now}
|
|
648
725
|
personHref={(person) =>
|
|
649
726
|
boardHref(key, person === undefined ? {} : { person: person.id })
|
|
@@ -950,8 +1027,15 @@ function ProjectPage(props: {
|
|
|
950
1027
|
<ProjectForm
|
|
951
1028
|
project={project}
|
|
952
1029
|
busy={busy}
|
|
953
|
-
onSubmit={(input) =>
|
|
954
|
-
void act(() =>
|
|
1030
|
+
onSubmit={(input, icon) =>
|
|
1031
|
+
void act(async () => {
|
|
1032
|
+
await data.actions.updateProject(input as RestProjectPatch);
|
|
1033
|
+
if (icon === null) {
|
|
1034
|
+
await data.actions.removeProjectIcon();
|
|
1035
|
+
} else if (icon !== undefined) {
|
|
1036
|
+
await data.actions.setProjectIcon(uploadOf(icon));
|
|
1037
|
+
}
|
|
1038
|
+
})
|
|
955
1039
|
}
|
|
956
1040
|
/>
|
|
957
1041
|
</section>
|
|
@@ -961,6 +1045,13 @@ function ProjectPage(props: {
|
|
|
961
1045
|
return <NothingHere />;
|
|
962
1046
|
}
|
|
963
1047
|
|
|
1048
|
+
/** A file a person chose, as the client sends it: its name, its bytes and the type the browser says. */
|
|
1049
|
+
const uploadOf = (file: File): FileUpload => ({
|
|
1050
|
+
name: file.name,
|
|
1051
|
+
bytes: file,
|
|
1052
|
+
...(file.type === "" ? {} : { contentType: file.type }),
|
|
1053
|
+
});
|
|
1054
|
+
|
|
964
1055
|
function NothingHere() {
|
|
965
1056
|
const words = useWords();
|
|
966
1057
|
return (
|
|
@@ -1014,7 +1105,9 @@ function PersonAgentsPage(props: {
|
|
|
1014
1105
|
return (
|
|
1015
1106
|
<>
|
|
1016
1107
|
<div className="sc-page-head">
|
|
1017
|
-
<h1 className="sc-page-title">
|
|
1108
|
+
<h1 className="sc-page-title">
|
|
1109
|
+
{words.people.agentsOf(person === undefined ? "?" : personName(person))}
|
|
1110
|
+
</h1>
|
|
1018
1111
|
<LinkButton href={PATHS.people}>{words.nav.people}</LinkButton>
|
|
1019
1112
|
</div>
|
|
1020
1113
|
<p className="sc-lead">
|
|
@@ -1226,6 +1319,7 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1226
1319
|
const client = config.client ?? createClient({ baseUrl: config.baseUrl ?? "" });
|
|
1227
1320
|
const fallbackTitle = config.title ?? "Console";
|
|
1228
1321
|
const languages = config.languages ?? DEFAULT_LANGUAGES;
|
|
1322
|
+
const pages = config.pages ?? [];
|
|
1229
1323
|
|
|
1230
1324
|
function App(props: { readonly initialPath?: string | undefined }) {
|
|
1231
1325
|
const { location, navigation } = useNavigation(props.initialPath);
|
|
@@ -1324,16 +1418,22 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1324
1418
|
current: route.name === "agents" || route.name === "new-token",
|
|
1325
1419
|
},
|
|
1326
1420
|
]),
|
|
1327
|
-
// The categories are read by everyone and kept by an administrator
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1421
|
+
// The categories are read by everyone, for what the colours mean, and kept by an administrator.
|
|
1422
|
+
{
|
|
1423
|
+
href: PATHS.categories,
|
|
1424
|
+
label: words.nav.categories,
|
|
1425
|
+
current: route.name === "categories",
|
|
1426
|
+
},
|
|
1427
|
+
...pages
|
|
1428
|
+
.filter((candidate) => candidate.level === "workspace")
|
|
1429
|
+
.map((candidate) => ({
|
|
1430
|
+
href: customPageHref(candidate.key),
|
|
1431
|
+
label: customPageLabel(candidate, pack.tag),
|
|
1432
|
+
current:
|
|
1433
|
+
route.name === "custom" &&
|
|
1434
|
+
route.level === "workspace" &&
|
|
1435
|
+
route.page === candidate.key,
|
|
1436
|
+
})),
|
|
1337
1437
|
];
|
|
1338
1438
|
const nav: NavItem[] =
|
|
1339
1439
|
projectKey === undefined
|
|
@@ -1394,8 +1494,27 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1394
1494
|
},
|
|
1395
1495
|
]
|
|
1396
1496
|
: []),
|
|
1497
|
+
...pages
|
|
1498
|
+
.filter((candidate) => candidate.level === "project")
|
|
1499
|
+
.map((candidate) => ({
|
|
1500
|
+
href: customPageHref(candidate.key, projectKey),
|
|
1501
|
+
label: customPageLabel(candidate, pack.tag),
|
|
1502
|
+
current:
|
|
1503
|
+
route.name === "custom" &&
|
|
1504
|
+
route.level === "project" &&
|
|
1505
|
+
route.page === candidate.key,
|
|
1506
|
+
})),
|
|
1397
1507
|
];
|
|
1398
|
-
|
|
1508
|
+
/** A page of the console's own at the address, or nothing there. */
|
|
1509
|
+
const customPageOf = (key: string, level: CustomPage["level"]): ReactNode => {
|
|
1510
|
+
const found = pages.find((candidate) => candidate.key === key && candidate.level === level);
|
|
1511
|
+
return found === undefined ? (
|
|
1512
|
+
<NothingHere />
|
|
1513
|
+
) : (
|
|
1514
|
+
found.render({ data, project, client, navigation, words, language: pack.tag })
|
|
1515
|
+
);
|
|
1516
|
+
};
|
|
1517
|
+
let page: ReactNode;
|
|
1399
1518
|
if (!data.loaded || route.name === "moved") {
|
|
1400
1519
|
page = (
|
|
1401
1520
|
<div className="sc-loading">
|
|
@@ -1413,6 +1532,8 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1413
1532
|
making={route.name === "new-project"}
|
|
1414
1533
|
/>
|
|
1415
1534
|
);
|
|
1535
|
+
} else if (route.name === "custom" && route.level === "workspace") {
|
|
1536
|
+
page = customPageOf(route.page, "workspace");
|
|
1416
1537
|
} else if (projectKey === undefined) {
|
|
1417
1538
|
page = (
|
|
1418
1539
|
<WorkspacePage
|
|
@@ -1436,6 +1557,8 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1436
1557
|
<Spinner label={words.common.loadingProject} />
|
|
1437
1558
|
</div>
|
|
1438
1559
|
);
|
|
1560
|
+
} else if (route.name === "custom") {
|
|
1561
|
+
page = customPageOf(route.page, "project");
|
|
1439
1562
|
} else {
|
|
1440
1563
|
page = (
|
|
1441
1564
|
<ProjectPage
|
|
@@ -1454,7 +1577,12 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1454
1577
|
project={
|
|
1455
1578
|
projectKey === undefined
|
|
1456
1579
|
? undefined
|
|
1457
|
-
: {
|
|
1580
|
+
: {
|
|
1581
|
+
name: project?.name ?? projectKey,
|
|
1582
|
+
href: projectHref(projectKey),
|
|
1583
|
+
mark:
|
|
1584
|
+
project === undefined ? undefined : <ProjectMark project={project} size="sm" />,
|
|
1585
|
+
}
|
|
1458
1586
|
}
|
|
1459
1587
|
nav={nav}
|
|
1460
1588
|
menu={workspaceNav}
|
|
@@ -1473,7 +1601,12 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
1473
1601
|
}}
|
|
1474
1602
|
person={data.me.person}
|
|
1475
1603
|
live={projectKey === undefined ? undefined : data.live}
|
|
1476
|
-
wide={
|
|
1604
|
+
wide={
|
|
1605
|
+
route.name === "board" ||
|
|
1606
|
+
route.name === "calendar" ||
|
|
1607
|
+
(route.name === "custom" &&
|
|
1608
|
+
pages.some((candidate) => candidate.key === route.page && candidate.wide === true))
|
|
1609
|
+
}
|
|
1477
1610
|
onNavigate={(href) => navigation.go(href)}
|
|
1478
1611
|
onSignOut={viaToken ? undefined : () => void data.actions.signOut()}
|
|
1479
1612
|
>
|
package/src/data.ts
CHANGED
|
@@ -58,10 +58,13 @@ const FEED_MAX_PAGES = 20;
|
|
|
58
58
|
const RELOAD_DELAY_MS = 150;
|
|
59
59
|
|
|
60
60
|
export interface ConsoleActions {
|
|
61
|
-
/** Makes a project, whose owner the person becomes. */
|
|
62
|
-
createProject(input: RestProjectInput): Promise<RestProject>;
|
|
61
|
+
/** Makes a project, whose owner the person becomes, with an image for its mark when one is given. */
|
|
62
|
+
createProject(input: RestProjectInput, icon?: FileUpload): Promise<RestProject>;
|
|
63
63
|
/** Changes the settings of the project the page is on; for an owner. */
|
|
64
64
|
updateProject(patch: RestProjectPatch): Promise<RestProject>;
|
|
65
|
+
/** Sets the image for the mark of the project the page is on, or takes it away; for an owner. */
|
|
66
|
+
setProjectIcon(input: FileUpload): Promise<RestProject>;
|
|
67
|
+
removeProjectIcon(): Promise<RestProject>;
|
|
65
68
|
addMember(input: RestMemberInput): Promise<RestMember>;
|
|
66
69
|
updateMember(personId: string, patch: RestMemberPatch): Promise<RestMember>;
|
|
67
70
|
removeMember(personId: string): Promise<void>;
|
|
@@ -387,10 +390,11 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
387
390
|
return client.project(projectKey);
|
|
388
391
|
};
|
|
389
392
|
return {
|
|
390
|
-
async createProject(input) {
|
|
393
|
+
async createProject(input, icon) {
|
|
391
394
|
const made = await client.createProject(input);
|
|
395
|
+
const marked = icon === undefined ? made : await client.project(made.key).setIcon(icon);
|
|
392
396
|
await refreshWorkspace();
|
|
393
|
-
return
|
|
397
|
+
return marked;
|
|
394
398
|
},
|
|
395
399
|
async updateProject(patch) {
|
|
396
400
|
const changed = await scope().update(patch);
|
|
@@ -398,6 +402,18 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
398
402
|
await refreshWorkspace();
|
|
399
403
|
return changed;
|
|
400
404
|
},
|
|
405
|
+
async setProjectIcon(input) {
|
|
406
|
+
const changed = await scope().setIcon(input);
|
|
407
|
+
setProject(changed);
|
|
408
|
+
await refreshWorkspace();
|
|
409
|
+
return changed;
|
|
410
|
+
},
|
|
411
|
+
async removeProjectIcon() {
|
|
412
|
+
const changed = await scope().removeIcon();
|
|
413
|
+
setProject(changed);
|
|
414
|
+
await refreshWorkspace();
|
|
415
|
+
return changed;
|
|
416
|
+
},
|
|
401
417
|
async addMember(input) {
|
|
402
418
|
const member = await scope().addMember(input);
|
|
403
419
|
await refreshProject();
|
package/src/i18n/en.ts
CHANGED
|
@@ -43,7 +43,7 @@ export const en = {
|
|
|
43
43
|
private: "Only those listed",
|
|
44
44
|
} satisfies Record<ProjectVisibility, string>,
|
|
45
45
|
runStatus: {
|
|
46
|
-
running: "
|
|
46
|
+
running: "Open",
|
|
47
47
|
waiting: "Waiting for a decision",
|
|
48
48
|
finished: "Finished",
|
|
49
49
|
failed: "Failed",
|
|
@@ -95,6 +95,7 @@ export const en = {
|
|
|
95
95
|
nobody: "Nobody",
|
|
96
96
|
nothing: "Nothing",
|
|
97
97
|
language: "Language",
|
|
98
|
+
showMore: (n: number, of: number) => `Show ${n} more of ${of}`,
|
|
98
99
|
},
|
|
99
100
|
|
|
100
101
|
nav: {
|
|
@@ -121,7 +122,7 @@ export const en = {
|
|
|
121
122
|
live: "Live",
|
|
122
123
|
reconnecting: "Reconnecting",
|
|
123
124
|
signOut: "Sign out",
|
|
124
|
-
menu: (
|
|
125
|
+
menu: (name: string) => `${name}: menu`,
|
|
125
126
|
},
|
|
126
127
|
|
|
127
128
|
signIn: {
|
|
@@ -150,7 +151,6 @@ export const en = {
|
|
|
150
151
|
list: "Projects",
|
|
151
152
|
private: "Private",
|
|
152
153
|
decisionsWaiting: (n: number) => `${count(n, "decision", "decisions")} waiting`,
|
|
153
|
-
agentsAtWork: (n: number) => `${count(n, "agent", "agents")} at work`,
|
|
154
154
|
form: {
|
|
155
155
|
aria: "New project",
|
|
156
156
|
name: "Name",
|
|
@@ -162,6 +162,11 @@ export const en = {
|
|
|
162
162
|
whoIsMember: "Who is a member",
|
|
163
163
|
skillsAddress: "Skills address",
|
|
164
164
|
skillsAddressPlaceholder: "/gh/<owner>/<repo>, or empty for the organization's",
|
|
165
|
+
symbol: "Mark",
|
|
166
|
+
image: "Image",
|
|
167
|
+
imageRemove: "Remove the image",
|
|
168
|
+
color: "Colour",
|
|
169
|
+
colorAuto: "Chosen from the key",
|
|
165
170
|
make: "Make the project",
|
|
166
171
|
},
|
|
167
172
|
members: {
|
|
@@ -173,7 +178,7 @@ export const en = {
|
|
|
173
178
|
leadRoles:
|
|
174
179
|
"An owner configures the project; a member works on it. A workspace administrator is an owner of every project.",
|
|
175
180
|
since: "since",
|
|
176
|
-
roleOf: (
|
|
181
|
+
roleOf: (name: string) => `Role of ${name}`,
|
|
177
182
|
addAria: "Add a member",
|
|
178
183
|
person: "Person",
|
|
179
184
|
chooseSomeone: "Choose someone",
|
|
@@ -207,9 +212,6 @@ export const en = {
|
|
|
207
212
|
moveTo: "Move to",
|
|
208
213
|
decisions: (n: number) => count(n, "decision", "decisions"),
|
|
209
214
|
decisionsTitle: "Decisions waiting for a person",
|
|
210
|
-
agentAtWork: "agent at work",
|
|
211
|
-
agentAtWorkTitle: "An agent is at work on it",
|
|
212
|
-
agentSince: (agent: string, since: string) => `${agent}, at work since ${since}`,
|
|
213
215
|
filters: {
|
|
214
216
|
aria: "Narrow the board",
|
|
215
217
|
everyone: "Everyone",
|
|
@@ -247,7 +249,6 @@ export const en = {
|
|
|
247
249
|
raiseDecision: "Raise a decision",
|
|
248
250
|
nothingMore: "Nothing more was written about it.",
|
|
249
251
|
links: "Links",
|
|
250
|
-
agentsAtWork: "Agents at work",
|
|
251
252
|
runs: "Runs",
|
|
252
253
|
decisions: "Decisions",
|
|
253
254
|
history: "What happened",
|
|
@@ -336,8 +337,10 @@ export const en = {
|
|
|
336
337
|
|
|
337
338
|
calendar: {
|
|
338
339
|
title: "Calendar",
|
|
339
|
-
lead: "The tasks
|
|
340
|
+
lead: "The tasks on their days: planned from a start to a due day, a task lies across them; due on a day alone, it sits on it. One with no day waits at the bottom to be given one.",
|
|
340
341
|
grid: "The month",
|
|
342
|
+
legend: { title: "Category colours" },
|
|
343
|
+
startsOnly: "Starts that day, with no due day yet",
|
|
341
344
|
previous: "The month before",
|
|
342
345
|
next: "The month after",
|
|
343
346
|
thisMonth: "This month",
|
|
@@ -346,7 +349,6 @@ export const en = {
|
|
|
346
349
|
nothingOverdue: "Nothing is overdue.",
|
|
347
350
|
unscheduled: "No day set",
|
|
348
351
|
allScheduled: "Every open task has a day.",
|
|
349
|
-
starts: "starts",
|
|
350
352
|
workload: {
|
|
351
353
|
title: "Who has what",
|
|
352
354
|
lead: "The open tasks of each person, and what of them is late or due within the week.",
|
|
@@ -509,6 +511,7 @@ export const en = {
|
|
|
509
511
|
runReported: (task: string, excerpt: string | undefined) =>
|
|
510
512
|
`reported on ${task}${excerpt === undefined ? "" : `: ${excerpt}`}`,
|
|
511
513
|
runHandedIn: (label: string, task: string) => `handed in ${label} on ${task}`,
|
|
514
|
+
runTakenOver: (task: string, agent: string) => `took over ${task} from ${agent}`,
|
|
512
515
|
runFinished: (task: string) => `finished ${task}`,
|
|
513
516
|
runFailed: (task: string) => `failed on ${task}`,
|
|
514
517
|
runAbandoned: (task: string) => `gave up on ${task}`,
|
|
@@ -524,12 +527,13 @@ export const en = {
|
|
|
524
527
|
for: "for",
|
|
525
528
|
since: "since",
|
|
526
529
|
until: "until",
|
|
530
|
+
lastHeard: "last heard",
|
|
527
531
|
markAbandoned: "Mark abandoned",
|
|
528
532
|
waitingForDecision: "Waiting for a decision",
|
|
529
533
|
answerIt: "answer it",
|
|
530
534
|
reports: "Reports",
|
|
531
535
|
handedIn: "Handed in",
|
|
532
|
-
label: (agent: string,
|
|
536
|
+
label: (agent: string, name: string) => `${agent} for ${name}`,
|
|
533
537
|
},
|
|
534
538
|
|
|
535
539
|
skills: {
|
|
@@ -621,9 +625,9 @@ export const en = {
|
|
|
621
625
|
title: "People",
|
|
622
626
|
list: "People",
|
|
623
627
|
lead: "Everyone who has signed in. An administrator configures the workspace and says what each person is; a member works in the projects they are in. What a person may do, their agents may do.",
|
|
624
|
-
roleOf: (
|
|
628
|
+
roleOf: (name: string) => `Role of ${name}`,
|
|
625
629
|
agents: "Agents",
|
|
626
|
-
agentsOf: (
|
|
630
|
+
agentsOf: (name: string) => `Agents of ${name}`,
|
|
627
631
|
agentsLead: (name: string) =>
|
|
628
632
|
`What works here as ${name}, each with a token of its own. Disconnecting one takes its token away at once.`,
|
|
629
633
|
loadingAgents: "Loading the agents",
|
|
@@ -707,7 +711,8 @@ export const en = {
|
|
|
707
711
|
words: {
|
|
708
712
|
title: "Connecting an agent",
|
|
709
713
|
tellYourAgent: "Tell your agent, in its own chat:",
|
|
710
|
-
quote: (origin: string) =>
|
|
714
|
+
quote: (origin: string) =>
|
|
715
|
+
`Install @skillcdn/console from npm, run console login --url ${origin}, and tell me the address and the code it shows.`,
|
|
711
716
|
doesTheRest:
|
|
712
717
|
"An agent that can run commands does the rest: it installs the command, connects, and shows you an address with a code.",
|
|
713
718
|
thenApprove:
|
package/src/i18n/ko.ts
CHANGED
|
@@ -21,7 +21,7 @@ export const ko: Messages = {
|
|
|
21
21
|
projectRole: { owner: "소유자", member: "멤버" },
|
|
22
22
|
visibility: { workspace: "워크스페이스의 모두", private: "목록에 있는 사람만" },
|
|
23
23
|
runStatus: {
|
|
24
|
-
running: "
|
|
24
|
+
running: "열림",
|
|
25
25
|
waiting: "결정 대기 중",
|
|
26
26
|
finished: "완료",
|
|
27
27
|
failed: "실패",
|
|
@@ -68,6 +68,7 @@ export const ko: Messages = {
|
|
|
68
68
|
nobody: "없음",
|
|
69
69
|
nothing: "없음",
|
|
70
70
|
language: "언어",
|
|
71
|
+
showMore: (n, of) => `${of}개 중 ${n}개 더 보기`,
|
|
71
72
|
},
|
|
72
73
|
|
|
73
74
|
nav: {
|
|
@@ -94,7 +95,7 @@ export const ko: Messages = {
|
|
|
94
95
|
live: "실시간",
|
|
95
96
|
reconnecting: "다시 연결하는 중",
|
|
96
97
|
signOut: "로그아웃",
|
|
97
|
-
menu: (
|
|
98
|
+
menu: (name) => `${name}: 메뉴`,
|
|
98
99
|
},
|
|
99
100
|
|
|
100
101
|
signIn: {
|
|
@@ -123,7 +124,6 @@ export const ko: Messages = {
|
|
|
123
124
|
list: "프로젝트",
|
|
124
125
|
private: "비공개",
|
|
125
126
|
decisionsWaiting: (n) => `결정 ${n}건 대기 중`,
|
|
126
|
-
agentsAtWork: (n) => `에이전트 ${n}개 작업 중`,
|
|
127
127
|
form: {
|
|
128
128
|
aria: "새 프로젝트",
|
|
129
129
|
name: "이름",
|
|
@@ -134,6 +134,11 @@ export const ko: Messages = {
|
|
|
134
134
|
whoIsMember: "멤버 범위",
|
|
135
135
|
skillsAddress: "스킬 주소",
|
|
136
136
|
skillsAddressPlaceholder: "/gh/<owner>/<repo>, 비우면 조직의 스킬",
|
|
137
|
+
symbol: "마크",
|
|
138
|
+
image: "이미지",
|
|
139
|
+
imageRemove: "이미지 지우기",
|
|
140
|
+
color: "색",
|
|
141
|
+
colorAuto: "키에서 정해진 색",
|
|
137
142
|
make: "프로젝트 만들기",
|
|
138
143
|
},
|
|
139
144
|
members: {
|
|
@@ -145,7 +150,7 @@ export const ko: Messages = {
|
|
|
145
150
|
leadRoles:
|
|
146
151
|
"소유자는 프로젝트를 설정하고, 멤버는 프로젝트에서 일합니다. 워크스페이스 관리자는 모든 프로젝트의 소유자입니다.",
|
|
147
152
|
since: "부터",
|
|
148
|
-
roleOf: (
|
|
153
|
+
roleOf: (name) => `${name}의 역할`,
|
|
149
154
|
addAria: "멤버 추가",
|
|
150
155
|
person: "사람",
|
|
151
156
|
chooseSomeone: "선택하세요",
|
|
@@ -179,9 +184,6 @@ export const ko: Messages = {
|
|
|
179
184
|
moveTo: "이동",
|
|
180
185
|
decisions: (n) => `결정 ${n}건`,
|
|
181
186
|
decisionsTitle: "사람을 기다리는 결정",
|
|
182
|
-
agentAtWork: "에이전트 작업 중",
|
|
183
|
-
agentAtWorkTitle: "에이전트가 이 작업을 하고 있습니다",
|
|
184
|
-
agentSince: (agent, since) => `${agent}, ${since}부터 작업 중`,
|
|
185
187
|
filters: {
|
|
186
188
|
aria: "보드 좁혀 보기",
|
|
187
189
|
everyone: "모두",
|
|
@@ -219,7 +221,6 @@ export const ko: Messages = {
|
|
|
219
221
|
raiseDecision: "결정 요청",
|
|
220
222
|
nothingMore: "더 적힌 내용이 없습니다.",
|
|
221
223
|
links: "링크",
|
|
222
|
-
agentsAtWork: "작업 중인 에이전트",
|
|
223
224
|
runs: "실행",
|
|
224
225
|
decisions: "결정",
|
|
225
226
|
history: "일어난 일",
|
|
@@ -308,8 +309,10 @@ export const ko: Messages = {
|
|
|
308
309
|
|
|
309
310
|
calendar: {
|
|
310
311
|
title: "캘린더",
|
|
311
|
-
lead: "작업을
|
|
312
|
+
lead: "작업을 날짜 위에 놓고 봅니다. 시작일과 기한이 있는 작업은 그 기간에 걸쳐 놓이고, 기한만 있는 작업은 그날에 놓입니다. 날짜가 없는 작업은 아래에서 날짜를 기다립니다.",
|
|
312
313
|
grid: "이 달",
|
|
314
|
+
legend: { title: "카테고리 색" },
|
|
315
|
+
startsOnly: "그날 시작, 기한은 아직 없음",
|
|
313
316
|
previous: "이전 달",
|
|
314
317
|
next: "다음 달",
|
|
315
318
|
thisMonth: "이번 달",
|
|
@@ -318,7 +321,6 @@ export const ko: Messages = {
|
|
|
318
321
|
nothingOverdue: "기한이 지난 작업이 없습니다.",
|
|
319
322
|
unscheduled: "날짜 없음",
|
|
320
323
|
allScheduled: "열린 작업마다 날짜가 있습니다.",
|
|
321
|
-
starts: "시작",
|
|
322
324
|
workload: {
|
|
323
325
|
title: "누가 무엇을",
|
|
324
326
|
lead: "사람마다 열린 작업과, 그중 늦은 것과 일주일 안에 마감인 것.",
|
|
@@ -477,6 +479,7 @@ export const ko: Messages = {
|
|
|
477
479
|
runReported: (task, excerpt) =>
|
|
478
480
|
`${task}에 보고했습니다${excerpt === undefined ? "" : `: ${excerpt}`}`,
|
|
479
481
|
runHandedIn: (label, task) => `${task}에 ${label}을(를) 제출했습니다`,
|
|
482
|
+
runTakenOver: (task, agent) => `${agent}에게서 ${task}을(를) 넘겨받았습니다`,
|
|
480
483
|
runFinished: (task) => `${task}을(를) 끝냈습니다`,
|
|
481
484
|
runFailed: (task) => `${task}에 실패했습니다`,
|
|
482
485
|
runAbandoned: (task) => `${task}을(를) 포기했습니다`,
|
|
@@ -492,12 +495,13 @@ export const ko: Messages = {
|
|
|
492
495
|
for: "대신:",
|
|
493
496
|
since: "시작",
|
|
494
497
|
until: "종료",
|
|
498
|
+
lastHeard: "마지막 소식",
|
|
495
499
|
markAbandoned: "중단됨으로 표시",
|
|
496
500
|
waitingForDecision: "결정을 기다리는 중",
|
|
497
501
|
answerIt: "답변하기",
|
|
498
502
|
reports: "보고",
|
|
499
503
|
handedIn: "제출물",
|
|
500
|
-
label: (agent,
|
|
504
|
+
label: (agent, name) => `${name}을(를) 위한 ${agent}`,
|
|
501
505
|
},
|
|
502
506
|
|
|
503
507
|
skills: {
|
|
@@ -589,9 +593,9 @@ export const ko: Messages = {
|
|
|
589
593
|
title: "사람들",
|
|
590
594
|
list: "사람들",
|
|
591
595
|
lead: "로그인한 모든 사람. 관리자는 워크스페이스를 설정하고 각 사람의 역할을 정합니다. 멤버는 속한 프로젝트에서 일합니다. 사람이 할 수 있는 일은 그 사람의 에이전트도 할 수 있습니다.",
|
|
592
|
-
roleOf: (
|
|
596
|
+
roleOf: (name) => `${name}의 역할`,
|
|
593
597
|
agents: "에이전트",
|
|
594
|
-
agentsOf: (
|
|
598
|
+
agentsOf: (name) => `${name}의 에이전트`,
|
|
595
599
|
agentsLead: (name) =>
|
|
596
600
|
`${name}(으)로 여기서 일하는 것들입니다. 각각 고유한 토큰을 가집니다. 연결을 끊으면 토큰이 즉시 사라집니다.`,
|
|
597
601
|
loadingAgents: "에이전트를 불러오는 중",
|
|
@@ -674,7 +678,8 @@ export const ko: Messages = {
|
|
|
674
678
|
words: {
|
|
675
679
|
title: "에이전트 연결하기",
|
|
676
680
|
tellYourAgent: "에이전트의 대화창에서 이렇게 말하세요:",
|
|
677
|
-
quote: (origin) =>
|
|
681
|
+
quote: (origin) =>
|
|
682
|
+
`npm으로 @skillcdn/console을 설치하고, console login --url ${origin} 을 실행한 뒤 나오는 주소와 코드를 알려줘.`,
|
|
678
683
|
doesTheRest:
|
|
679
684
|
"명령을 실행할 수 있는 에이전트는 나머지를 알아서 합니다. 명령을 설치하고, 연결하고, 코드가 있는 주소를 보여 줍니다.",
|
|
680
685
|
thenApprove:
|