@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/dist/console.js
CHANGED
|
@@ -10,9 +10,10 @@ import { DecisionForm } from "./components/decision-form.js";
|
|
|
10
10
|
import { DecisionCard, DecisionList } from "./components/decision-list.js";
|
|
11
11
|
import { EventFeed } from "./components/event-feed.js";
|
|
12
12
|
import { GoalForm, GoalList, GoalView, goalMonth } from "./components/goals.js";
|
|
13
|
+
import { PackTokenWays } from "./components/pack-token.js";
|
|
13
14
|
import { PAGE_SIZE } from "./components/paged.js";
|
|
14
15
|
import { ListingForm, PeopleList } from "./components/people.js";
|
|
15
|
-
import { MemberList,
|
|
16
|
+
import { MemberList, ProjectForm, ProjectList, ProjectMark, } from "./components/projects.js";
|
|
16
17
|
import { GoalTimeline } from "./components/roadmap.js";
|
|
17
18
|
import { RulesForm, RulesView } from "./components/rules.js";
|
|
18
19
|
import { isPlainClick, Shell, } from "./components/shell.js";
|
|
@@ -24,6 +25,7 @@ import { TaskView } from "./components/task-view.js";
|
|
|
24
25
|
import { NewToken, TokenForm, TokenList } from "./components/tokens.js";
|
|
25
26
|
import { Button, Callout, CategoryBadge, EmptyState, personName, Spinner, StateBadge, Time, } from "./components/ui.js";
|
|
26
27
|
import { WorkList } from "./components/work.js";
|
|
28
|
+
import { peopleVisibilityOf, WorkspaceSettingsForm, WorkspaceSettingsView, weekStartOf, } from "./components/workspace.js";
|
|
27
29
|
import { ConnectPage } from "./connect-page.js";
|
|
28
30
|
import { useConsoleData } from "./data.js";
|
|
29
31
|
import { monthOf, today } from "./dates.js";
|
|
@@ -279,6 +281,26 @@ function TaskPage(props) {
|
|
|
279
281
|
(fetched !== undefined && (fetched.id === route.id || fetched.number === route.number)
|
|
280
282
|
? fetched
|
|
281
283
|
: undefined);
|
|
284
|
+
// The task this one is part of: among the board's, or asked for by itself when it was put away.
|
|
285
|
+
const parentId = task?.parentId ?? null;
|
|
286
|
+
const parentOnBoard = parentId === null ? undefined : data.tasks.find((candidate) => candidate.id === parentId);
|
|
287
|
+
const [parentFetched, setParentFetched] = useState(undefined);
|
|
288
|
+
const parentWanted = parentId !== null && parentOnBoard === undefined && data.projectLoaded ? parentId : undefined;
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
if (parentWanted === undefined) {
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const controller = new AbortController();
|
|
294
|
+
fetchTask(parentWanted, controller.signal)
|
|
295
|
+
.then((found) => {
|
|
296
|
+
if (!controller.signal.aborted) {
|
|
297
|
+
setParentFetched(found);
|
|
298
|
+
}
|
|
299
|
+
})
|
|
300
|
+
.catch(() => undefined);
|
|
301
|
+
return () => controller.abort();
|
|
302
|
+
}, [parentWanted, fetchTask]);
|
|
303
|
+
const parent = parentOnBoard ?? (parentFetched?.id === parentId ? parentFetched : undefined);
|
|
282
304
|
const [history, setHistory] = useState(undefined);
|
|
283
305
|
const [comments, setComments] = useState(undefined);
|
|
284
306
|
const { taskHistory, taskComments } = data.actions;
|
|
@@ -321,7 +343,7 @@ function TaskPage(props) {
|
|
|
321
343
|
return (_jsx(EmptyState, { title: words.task.noSuch.title, body: words.task.noSuch.body, action: _jsx(LinkButton, { href: projectHref(key), children: words.nav.board }) }));
|
|
322
344
|
}
|
|
323
345
|
const here = taskHref(key, task.number);
|
|
324
|
-
return (_jsx(components.TaskView, { task: task, people: data.people.filter(inWorkspace), tasks: data.tasks, decisions: data.decisions, runs: data.runs, comments: comments, history: history, categories: data.categories, goals: data.goals, taskHref: (candidate) => taskHref(key, candidate.number), decisionHref: (decisionId) => decisionHref(key, decisionId), goalHref: (goalId) => goalHref(key, goalId), fileHref: (fileId) => projectPath(key, "files", fileId), docHref: (path) => docHref(key, path), form: route.form, formHref: (form) => taskHref(key, task.number, form), onCancel: () => navigation.back(here), busy: busy, error: error, onChange: (target, patch) => void act(async () => {
|
|
346
|
+
return (_jsx(components.TaskView, { task: task, people: data.people.filter(inWorkspace), tasks: data.tasks, parent: parent, findTasks: data.actions.findTasks, findGoals: data.actions.findGoals, decisions: data.decisions, runs: data.runs, comments: comments, history: history, categories: data.categories, goals: data.goals, taskHref: (candidate) => taskHref(key, candidate.number), decisionHref: (decisionId) => decisionHref(key, decisionId), goalHref: (goalId) => goalHref(key, goalId), fileHref: (fileId) => projectPath(key, "files", fileId), docHref: (path) => docHref(key, path), form: route.form, formHref: (form) => taskHref(key, task.number, form), onCancel: () => navigation.back(here), busy: busy, error: error, onChange: (target, patch) => void act(async () => {
|
|
325
347
|
await data.actions.updateTask(target.id, patch);
|
|
326
348
|
navigation.replace(here);
|
|
327
349
|
}), onMove: (target, state) => void act(() => data.actions.updateTask(target.id, { state })), onRaiseDecision: (input) => void act(async () => {
|
|
@@ -362,7 +384,7 @@ function ProjectPage(props) {
|
|
|
362
384
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsxs("div", { className: "sc-page-heading", children: [_jsx("h1", { className: "sc-page-title", children: project.name }), project.description.length > 0 && (_jsx("p", { className: "sc-page-lead", title: project.description, children: project.description }))] }), _jsxs("div", { className: "sc-page-actions", children: [_jsx(LinkButton, { href: projectHref(key, "archive"), children: words.board.archive }), _jsx(LinkButton, { href: newTaskHref(key), variant: "primary", children: words.board.writeTask })] })] }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), data.tasks.length > 0 && (_jsx(BoardFilters, { filter: route.filter, categories: data.categories, goals: data.goals, people: data.people.filter(inWorkspace), me: me, shown: shown.length, total: data.tasks.length, onChange: (filter) => navigation.replace(boardHref(key, filter)) })), _jsx(components.Board, { tasks: shown, taskHref: href, onOpen: (task) => navigation.go(href(task)), onMove: onMove, editHref: (task) => taskHref(key, task.number, "edit"), onEdit: (task) => navigation.go(taskHref(key, task.number, "edit")), onArchive: (task) => void act(() => data.actions.archiveTask(task.id)), empty: data.tasks.length === 0 ? (_jsx(EmptyState, { title: words.board.empty.title, body: words.board.empty.body, action: _jsx(LinkButton, { href: newTaskHref(key), children: words.board.writeTask }) })) : undefined })] }));
|
|
363
385
|
}
|
|
364
386
|
if (route.name === "new-task") {
|
|
365
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.task.newTitle }) }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.task.newAria, children: _jsx(TaskForm, { people: data.people.filter(inWorkspace),
|
|
387
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: words.task.newTitle }) }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.task.newAria, children: _jsx(TaskForm, { people: data.people.filter(inWorkspace), findTasks: data.actions.findTasks, categories: data.categories, findGoals: data.actions.findGoals, busy: busy, onSubmit: (input) => void act(async () => {
|
|
366
388
|
const made = await data.actions.createTask(input);
|
|
367
389
|
navigation.replace(taskHref(key, made.number));
|
|
368
390
|
}), onCancel: () => navigation.back(projectHref(key)) }) })] }));
|
|
@@ -379,7 +401,7 @@ function ProjectPage(props) {
|
|
|
379
401
|
const overdue = open.filter((task) => task.dueOn !== null && task.dueOn < now).sort(byDue);
|
|
380
402
|
const unscheduled = open.filter((task) => task.dueOn === null).sort(byDue);
|
|
381
403
|
const calendar = words.calendar;
|
|
382
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: calendar.title }), _jsx(LinkButton, { href: newTaskHref(key), variant: "primary", children: words.board.writeTask })] }), data.tasks.length > 0 && (_jsx(BoardFilters, { filter: route.filter, categories: data.categories, goals: data.goals, people: data.people.filter(inWorkspace), me: me, shown: shown.length, total: data.tasks.length, onChange: (filter) => navigation.replace(calendarHref(key, route.month, filter)) })), _jsx(components.Calendar, { tasks: shown, month: month, today: now, categories: data.categories, goals: data.goals, taskHref: href, goalHref: (goal) => goalHref(key, goal.id), monthHref: (wanted) => calendarHref(key, wanted, route.filter) }), _jsxs("div", { className: "sc-calendar-below", children: [_jsxs("section", { className: "sc-task-section", "aria-label": calendar.overdue, children: [_jsxs("h2", { className: "sc-section-title", children: [calendar.overdue, " ", _jsx("span", { className: "sc-column-count", children: overdue.length })] }), _jsx(TaskList, { tasks: overdue, taskHref: href, today: now, showState: true, empty: _jsx("p", { className: "sc-muted", children: calendar.nothingOverdue }) })] }), _jsxs("section", { className: "sc-task-section", "aria-label": calendar.unscheduled, children: [_jsxs("h2", { className: "sc-section-title", children: [calendar.unscheduled, " ", _jsx("span", { className: "sc-column-count", children: unscheduled.length })] }), _jsx(TaskList, { tasks: unscheduled, taskHref: href, today: now, showState: true, empty: _jsx("p", { className: "sc-muted", children: calendar.allScheduled }) })] })] }), open.length > 0 && (_jsxs("section", { className: "sc-task-section", "aria-label": calendar.workload.title, children: [_jsx("h2", { className: "sc-section-title", children: calendar.workload.title }), _jsx(Workload, { tasks: shown, today: now, personHref: (person) => boardHref(key, person === undefined ? {} : { person: person.id }) })] }))] }));
|
|
404
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: calendar.title }), _jsx(LinkButton, { href: newTaskHref(key), variant: "primary", children: words.board.writeTask })] }), data.tasks.length > 0 && (_jsx(BoardFilters, { filter: route.filter, categories: data.categories, goals: data.goals, people: data.people.filter(inWorkspace), me: me, shown: shown.length, total: data.tasks.length, onChange: (filter) => navigation.replace(calendarHref(key, route.month, filter)) })), _jsx(components.Calendar, { tasks: shown, month: month, today: now, weekStart: weekStartOf(data.me?.workspace), categories: data.categories, goals: data.goals, taskHref: href, goalHref: (goal) => goalHref(key, goal.id), monthHref: (wanted) => calendarHref(key, wanted, route.filter) }), _jsxs("div", { className: "sc-calendar-below", children: [_jsxs("section", { className: "sc-task-section", "aria-label": calendar.overdue, children: [_jsxs("h2", { className: "sc-section-title", children: [calendar.overdue, " ", _jsx("span", { className: "sc-column-count", children: overdue.length })] }), _jsx(TaskList, { tasks: overdue, taskHref: href, today: now, showState: true, empty: _jsx("p", { className: "sc-muted", children: calendar.nothingOverdue }) })] }), _jsxs("section", { className: "sc-task-section", "aria-label": calendar.unscheduled, children: [_jsxs("h2", { className: "sc-section-title", children: [calendar.unscheduled, " ", _jsx("span", { className: "sc-column-count", children: unscheduled.length })] }), _jsx(TaskList, { tasks: unscheduled, taskHref: href, today: now, showState: true, empty: _jsx("p", { className: "sc-muted", children: calendar.allScheduled }) })] })] }), open.length > 0 && (_jsxs("section", { className: "sc-task-section", "aria-label": calendar.workload.title, children: [_jsx("h2", { className: "sc-section-title", children: calendar.workload.title }), _jsx(Workload, { tasks: shown, today: now, weekStart: weekStartOf(data.me?.workspace), personHref: (person) => boardHref(key, person === undefined ? {} : { person: person.id }) })] }))] }));
|
|
383
405
|
}
|
|
384
406
|
if (route.name === "roadmap" || route.name === "new-goal") {
|
|
385
407
|
const roadmap = words.roadmap;
|
|
@@ -425,7 +447,7 @@ function ProjectPage(props) {
|
|
|
425
447
|
})
|
|
426
448
|
: undefined, onArchiveTasks: owner && towards.some((task) => !OPEN_TASK_STATES.includes(task.state))
|
|
427
449
|
? () => void act(() => data.actions.archiveGoalTasks(goal.id))
|
|
428
|
-
: undefined, busy: busy, view: route.view, viewHref: viewHref, board: _jsx(components.Board, { tasks: towards, taskHref: href, onOpen: (task) => navigation.go(href(task)), onMove: onMove, today: now, editHref: (task) => taskHref(key, task.number, "edit"), onEdit: (task) => navigation.go(taskHref(key, task.number, "edit")), onArchive: (task) => void act(() => data.actions.archiveTask(task.id)), empty: towards.length === 0 ? _jsx("p", { className: "sc-muted", children: roadmap.noTasks }) : undefined }), calendar: _jsx(components.Calendar, { tasks: towards, month: month, today: now, categories: data.categories, goals: [goal], taskHref: href, goalHref: (target) => goalHref(key, target.id), monthHref: (wanted) => goalHref(key, goal.id, { view: "calendar", month: wanted }) }) })] }));
|
|
450
|
+
: undefined, busy: busy, view: route.view, viewHref: viewHref, board: _jsx(components.Board, { tasks: towards, taskHref: href, onOpen: (task) => navigation.go(href(task)), onMove: onMove, today: now, editHref: (task) => taskHref(key, task.number, "edit"), onEdit: (task) => navigation.go(taskHref(key, task.number, "edit")), onArchive: (task) => void act(() => data.actions.archiveTask(task.id)), empty: towards.length === 0 ? _jsx("p", { className: "sc-muted", children: roadmap.noTasks }) : undefined }), calendar: _jsx(components.Calendar, { tasks: towards, month: month, today: now, weekStart: weekStartOf(data.me?.workspace), categories: data.categories, goals: [goal], taskHref: href, goalHref: (target) => goalHref(key, target.id), monthHref: (wanted) => goalHref(key, goal.id, { view: "calendar", month: wanted }) }) })] }));
|
|
429
451
|
}
|
|
430
452
|
if (route.name === "docs") {
|
|
431
453
|
return (_jsx(DocsPage, { project: project, path: route.path, view: route.view, data: data, navigation: navigation }));
|
|
@@ -472,9 +494,17 @@ function ProjectPage(props) {
|
|
|
472
494
|
const me = data.me?.person ?? undefined;
|
|
473
495
|
const configures = owner;
|
|
474
496
|
const members = words.projects.members;
|
|
475
|
-
|
|
497
|
+
// In a project open to the workspace everyone is a member already (ADR-0035): the list is
|
|
498
|
+
// everyone, nobody is added or removed, and an owner is made from a row. Where the People
|
|
499
|
+
// page shows a member only whom they share a project with, an owner lists someone the list
|
|
500
|
+
// does not show by their login.
|
|
501
|
+
const open = project.visibility === "workspace";
|
|
502
|
+
const narrowed = peopleVisibilityOf(data.me?.workspace) === "shared_projects" && me?.role !== "admin";
|
|
503
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "sc-page-head", children: _jsx("h1", { className: "sc-page-title", children: members.title }) }), open && _jsx("p", { className: "sc-lead", children: members.open }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(components.MemberList, { members: data.members, people: data.people.filter(inWorkspace), me: me, open: open, busy: busy, onAdd: configures && !open
|
|
504
|
+
? (input) => void act(() => data.actions.addMember(input))
|
|
505
|
+
: undefined, lookup: configures && !open && narrowed ? (login) => data.actions.findPerson(login) : undefined, onChangeRole: configures
|
|
476
506
|
? (member, role) => void act(() => data.actions.updateMember(member.person.id, { role }))
|
|
477
|
-
: undefined, onRemove: configures
|
|
507
|
+
: undefined, onRemove: configures && !open
|
|
478
508
|
? (member) => void act(() => data.actions.removeMember(member.person.id))
|
|
479
509
|
: undefined, workHref: owner ? (member) => personWorkHref(member.person.id) : undefined, empty: _jsx(EmptyState, { title: members.empty.title, body: project.visibility === "workspace" ? members.empty.workspace : members.empty.private }) })] }));
|
|
480
510
|
}
|
|
@@ -546,17 +576,17 @@ function ProjectSettingsPage(props) {
|
|
|
546
576
|
if (!owner) {
|
|
547
577
|
return _jsx(NothingHere, {});
|
|
548
578
|
}
|
|
549
|
-
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(
|
|
550
|
-
|
|
579
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(PackTokenWays, { title: settings.packToken, intro: words.projects.packToken.intro, set: project.packToken !== null, setAt: project.packToken?.setAt, busy: busy, onStart: data.actions.connectPackToken, poll: data.actions.packTokenConnection, onCancelConnect: data.actions.cancelPackTokenConnection, onConnected: () => navigation.replace(here), onSubmit: (token) => void act(async () => {
|
|
580
|
+
await data.actions.setPackToken(token);
|
|
581
|
+
navigation.replace(here);
|
|
582
|
+
}), onRemove: project.packToken === null
|
|
583
|
+
? undefined
|
|
584
|
+
: () => void act(async () => {
|
|
585
|
+
await data.actions.removePackToken();
|
|
551
586
|
navigation.replace(here);
|
|
552
|
-
}),
|
|
553
|
-
? undefined
|
|
554
|
-
: () => void act(async () => {
|
|
555
|
-
await data.actions.removePackToken();
|
|
556
|
-
navigation.replace(here);
|
|
557
|
-
}), onCancel: () => navigation.back(here) }) })] }));
|
|
587
|
+
}), onBack: () => navigation.back(here) })] }));
|
|
558
588
|
}
|
|
559
|
-
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel", "aria-label": settings.aria, children: _jsxs("dl", { className: "sc-facts sc-facts-plain", children: [_jsxs("div", { children: [_jsx("dt", { children: form.name }), _jsx("dd", { children: project.name })] }), _jsxs("div", { children: [_jsx("dt", { children: form.key }), _jsx("dd", { children: _jsx("code", { className: "sc-project-key", children: project.key }) })] }), _jsxs("div", { children: [_jsx("dt", { children: form.whoIsMember }), _jsx("dd", { children: words.vocabulary.visibility[project.visibility] })] }), _jsxs("div", { children: [_jsx("dt", { children: form.skillsAddress }), _jsx("dd", { children: project.skillsAddress !== null ? (_jsxs("code", { children: [skills?.source ?? "", project.skillsAddress] })) : skills?.address != null ? (settings.organizationsSkills(skills.address)) : (settings.noSkills) })] }), _jsxs("div", { children: [_jsx("dt", { children: settings.packToken }), _jsxs("dd", { children: [project.packToken === null ? (_jsx("span", { className: "sc-muted", children: settings.packTokenNone })) : (_jsxs(_Fragment, { children: [settings.packTokenSet, " ", _jsx(Time, { iso: project.packToken.setAt })] })), owner && (_jsxs(_Fragment, { children: [" ", _jsx("a", { href: projectSettingsHref(key, "pack-token"), children: settings.packTokenEdit })] }))] })] }), _jsxs("div", { children: [_jsx("dt", { children: form.symbol }), _jsx("dd", { children: _jsx(ProjectMark, { project: project, size: "sm" }) })] }), project.description.length > 0 && (_jsxs("div", { className: "sc-facts-wide", children: [_jsx("dt", { children: form.description }), _jsx("dd", { children: project.description })] }))] }) }), _jsxs("section", { className: "sc-panel", "aria-label": words.rules.projectRules, children: [_jsx("h2", { className: "sc-section-title", children: words.rules.projectRules }), rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesView, { rules: rules.project, scope: "project", docHref: (path) => docHref(key, path), editHref: owner ? projectSettingsHref(key, "rules") : undefined, onArchive: owner
|
|
589
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx("section", { className: "sc-panel", "aria-label": settings.aria, children: _jsxs("dl", { className: "sc-facts sc-facts-plain", children: [_jsxs("div", { children: [_jsx("dt", { children: form.name }), _jsx("dd", { children: project.name })] }), _jsxs("div", { children: [_jsx("dt", { children: form.key }), _jsx("dd", { children: _jsx("code", { className: "sc-project-key", children: project.key }) })] }), _jsxs("div", { children: [_jsx("dt", { children: form.whoIsMember }), _jsx("dd", { children: words.vocabulary.visibility[project.visibility] })] }), _jsxs("div", { children: [_jsx("dt", { children: form.skillsAddress }), _jsx("dd", { children: project.skillsAddress !== null ? (_jsxs("code", { children: [skills?.source ?? "", project.skillsAddress] })) : skills?.address != null ? (settings.organizationsSkills(skills.address)) : (settings.noSkills) })] }), _jsxs("div", { children: [_jsx("dt", { children: settings.packToken }), _jsxs("dd", { children: [project.packToken === null ? (_jsx("span", { className: "sc-muted", children: settings.packTokenNone })) : (_jsxs(_Fragment, { children: [settings.packTokenSet, " ", _jsx(Time, { iso: project.packToken.setAt })] })), owner && (_jsxs(_Fragment, { children: [" ", _jsx("a", { href: projectSettingsHref(key, "pack-token"), children: project.packToken === null ? settings.packTokenAdd : settings.packTokenEdit })] }))] })] }), _jsxs("div", { children: [_jsx("dt", { children: form.symbol }), _jsx("dd", { children: _jsx(ProjectMark, { project: project, size: "sm" }) })] }), project.description.length > 0 && (_jsxs("div", { className: "sc-facts-wide", children: [_jsx("dt", { children: form.description }), _jsx("dd", { children: project.description })] }))] }) }), _jsxs("section", { className: "sc-panel", "aria-label": words.rules.projectRules, children: [_jsx("h2", { className: "sc-section-title", children: words.rules.projectRules }), rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesView, { rules: rules.project, scope: "project", docHref: (path) => docHref(key, path), editHref: owner ? projectSettingsHref(key, "rules") : undefined, onArchive: owner
|
|
560
590
|
? () => void act(async () => {
|
|
561
591
|
await data.actions.archiveDocument(RULES_PATH);
|
|
562
592
|
setRules(await data.actions.projectRules());
|
|
@@ -573,7 +603,8 @@ function WorkspaceSettingsPage(props) {
|
|
|
573
603
|
const { form, data, navigation } = props;
|
|
574
604
|
const { busy, error, act } = useAction();
|
|
575
605
|
const administrator = data.me?.person?.role === "admin";
|
|
576
|
-
const
|
|
606
|
+
const workspace = data.me?.workspace;
|
|
607
|
+
const skills = workspace?.skills;
|
|
577
608
|
const [rules, setRules] = useState(undefined);
|
|
578
609
|
useEffect(() => {
|
|
579
610
|
const controller = new AbortController();
|
|
@@ -605,14 +636,40 @@ function WorkspaceSettingsPage(props) {
|
|
|
605
636
|
navigation.replace(PATHS.settings);
|
|
606
637
|
}) })) })] }));
|
|
607
638
|
}
|
|
608
|
-
|
|
639
|
+
if (form === "pack-token") {
|
|
640
|
+
if (!administrator || workspace === undefined) {
|
|
641
|
+
return _jsx(NothingHere, {});
|
|
642
|
+
}
|
|
643
|
+
const held = workspace.skills.packToken ?? null;
|
|
644
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsx(PackTokenWays, { title: words.settings.skillsToken, intro: words.settings.skillsTokenIntro, set: held !== null, setAt: held?.setAt, busy: busy, onStart: data.actions.connectWorkspacePackToken, poll: data.actions.workspacePackTokenConnection, onCancelConnect: data.actions.cancelWorkspacePackTokenConnection, onConnected: () => navigation.replace(PATHS.settings), onSubmit: (token) => void act(async () => {
|
|
645
|
+
await data.actions.setWorkspacePackToken(token);
|
|
646
|
+
navigation.replace(PATHS.settings);
|
|
647
|
+
}), onRemove: held === null
|
|
648
|
+
? undefined
|
|
649
|
+
: () => void act(async () => {
|
|
650
|
+
await data.actions.removeWorkspacePackToken();
|
|
651
|
+
navigation.replace(PATHS.settings);
|
|
652
|
+
}), onBack: () => navigation.back(PATHS.settings) })] }));
|
|
653
|
+
}
|
|
654
|
+
if (form === "workspace") {
|
|
655
|
+
if (!administrator || workspace === undefined) {
|
|
656
|
+
return _jsx(NothingHere, {});
|
|
657
|
+
}
|
|
658
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), _jsxs("section", { className: "sc-panel sc-panel-form", "aria-label": words.settings.workspace.aria, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.workspace.title }), _jsx(WorkspaceSettingsForm, { workspace: workspace, busy: busy, onSubmit: (patch) => void act(async () => {
|
|
659
|
+
await data.actions.updateWorkspace(patch);
|
|
660
|
+
navigation.replace(PATHS.settings);
|
|
661
|
+
}), onCancel: () => navigation.back(PATHS.settings) })] })] }));
|
|
662
|
+
}
|
|
663
|
+
return (_jsxs(_Fragment, { children: [head, error !== undefined && _jsx(Callout, { tone: "danger", children: error }), workspace !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.workspace.aria, children: [_jsxs("div", { className: "sc-section-head", children: [_jsx("h2", { className: "sc-section-title", children: words.settings.workspace.title }), administrator && (_jsx("a", { className: "sc-button sc-button-secondary sc-button-sm", href: settingsHref("workspace"), children: words.common.edit }))] }), _jsx(WorkspaceSettingsView, { workspace: workspace })] })), _jsxs("section", { className: "sc-panel", "aria-label": words.rules.title, children: [_jsx("h2", { className: "sc-section-title", children: words.rules.title }), rules === undefined ? (_jsx(Spinner, { label: words.rules.loading })) : (_jsx(RulesView, { rules: rules, scope: "workspace", editHref: administrator ? settingsHref("rules") : undefined, onArchive: administrator
|
|
609
664
|
? () => void act(async () => {
|
|
610
665
|
setRules(await data.actions.archiveRules());
|
|
611
666
|
})
|
|
612
|
-
: undefined, busy: busy }))] }), _jsxs("section", { className: "sc-panel", "aria-label": words.settings.categories, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.categories }), data.categories.length > 0 && (_jsx("p", { className: "sc-category-chips", children: data.categories.map((category) => (_jsx(CategoryBadge, { category: category }, category.key))) })), _jsx(LinkButton, { href: PATHS.categories, children: words.settings.manageCategories })] }), skills !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.skills, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.skills }), _jsxs("dl", { className: "sc-facts sc-facts-plain", children: [_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsSource }), _jsx("dd", { children: _jsx("a", { href: skills.source, target: "_blank", rel: "noopener noreferrer", children: skills.source }) })] }), _jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsAddress }), _jsx("dd", { children: skills.address === null ? (words.settings.noSkillsAddress) : (_jsx("a", { href: `${skills.source}${skills.address}`, target: "_blank", rel: "noopener noreferrer", children: _jsx("code", { children: skills.address }) })) })] })
|
|
667
|
+
: undefined, busy: busy }))] }), _jsxs("section", { className: "sc-panel", "aria-label": words.settings.categories, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.categories }), data.categories.length > 0 && (_jsx("p", { className: "sc-category-chips", children: data.categories.map((category) => (_jsx(CategoryBadge, { category: category }, category.key))) })), _jsx(LinkButton, { href: PATHS.categories, children: words.settings.manageCategories })] }), skills !== undefined && (_jsxs("section", { className: "sc-panel", "aria-label": words.settings.skills, children: [_jsx("h2", { className: "sc-section-title", children: words.settings.skills }), _jsxs("dl", { className: "sc-facts sc-facts-plain", children: [_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsSource }), _jsx("dd", { children: _jsx("a", { href: skills.source, target: "_blank", rel: "noopener noreferrer", children: skills.source }) })] }), _jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsAddress }), _jsx("dd", { children: skills.address === null ? (words.settings.noSkillsAddress) : (_jsx("a", { href: `${skills.source}${skills.address}`, target: "_blank", rel: "noopener noreferrer", children: _jsx("code", { children: skills.address }) })) })] }), skills.address !== null && (_jsxs("div", { children: [_jsx("dt", { children: words.settings.skillsToken }), _jsxs("dd", { children: [skills.packToken == null ? (_jsx("span", { className: "sc-muted", children: words.projects.settings.packTokenNone })) : (_jsxs(_Fragment, { children: [words.projects.settings.packTokenSet, " ", _jsx(Time, { iso: skills.packToken.setAt })] })), administrator && (_jsxs(_Fragment, { children: [" ", _jsx("a", { href: settingsHref("pack-token"), children: skills.packToken == null
|
|
668
|
+
? words.projects.settings.packTokenAdd
|
|
669
|
+
: words.projects.settings.packTokenEdit })] }))] })] }))] }), _jsx("p", { className: "sc-muted", children: words.settings.skillsFromConfiguration })] }))] }));
|
|
613
670
|
}
|
|
614
671
|
/** A file a person chose, as the client sends it: its name, its bytes and the type the browser says. */
|
|
615
|
-
/** Whether a person is in: who a picker of
|
|
672
|
+
/** Whether a person is in: who a picker of workers and of members offers (ADR-0022). */
|
|
616
673
|
const inWorkspace = (person) => person.status === "active";
|
|
617
674
|
const uploadOf = (file) => ({
|
|
618
675
|
name: file.name,
|
|
@@ -695,13 +752,13 @@ function PlatePage(props) {
|
|
|
695
752
|
}, [load, couldNotLoad, refused]);
|
|
696
753
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: props.title }), props.aside] }), problem !== undefined && _jsx(Callout, { tone: "danger", children: problem }), forbidden && refused !== undefined ? (_jsx(EmptyState, { title: refused.title, body: refused.body, action: _jsx(LinkButton, { href: PATHS.work, children: words.nav.work }) })) : plate === undefined ? (problem === undefined && (_jsx("div", { className: "sc-loading", children: _jsx(Spinner, { label: props.loading }) }))) : plate.tasks.length === 0 &&
|
|
697
754
|
(plate.closed?.length ?? 0) === 0 &&
|
|
698
|
-
(plate.decisions?.length ?? 0) === 0 ? (_jsx(EmptyState, { title: props.empty.title, body: props.empty.body })) : (_jsx(components.WorkList, { work: plate, week: props.week, taskHref: (projectKey, task) => taskHref(projectKey, task.number), decisionHref: (projectKey, decisionId) => decisionHref(projectKey, decisionId), weekHref: props.weekHref }))] }));
|
|
755
|
+
(plate.decisions?.length ?? 0) === 0 ? (_jsx(EmptyState, { title: props.empty.title, body: props.empty.body })) : (_jsx(components.WorkList, { work: plate, week: props.week, weekStart: props.weekStart, taskHref: (projectKey, task) => taskHref(projectKey, task.number), decisionHref: (projectKey, decisionId) => decisionHref(projectKey, decisionId), weekHref: props.weekHref }))] }));
|
|
699
756
|
}
|
|
700
757
|
/** What is on the person's own plate, across their projects. */
|
|
701
758
|
function WorkPage(props) {
|
|
702
759
|
const words = useWords();
|
|
703
760
|
const { work } = props.data.actions;
|
|
704
|
-
return (_jsx(PlatePage, { components: props.components, load: work, title: words.work.title, week: props.week, weekHref: workHref, loading: words.work.loading, couldNotLoad: words.work.couldNotLoad, empty: words.work.empty }));
|
|
761
|
+
return (_jsx(PlatePage, { components: props.components, load: work, title: words.work.title, week: props.week, weekStart: weekStartOf(props.data.me?.workspace), weekHref: workHref, loading: words.work.loading, couldNotLoad: words.work.couldNotLoad, empty: words.work.empty }));
|
|
705
762
|
}
|
|
706
763
|
/** Another person's plate, for whoever oversees them: an administrator, or an owner of a project they are in. */
|
|
707
764
|
function PersonWorkPage(props) {
|
|
@@ -711,7 +768,7 @@ function PersonWorkPage(props) {
|
|
|
711
768
|
const name = person === undefined ? "?" : personName(person);
|
|
712
769
|
const { personWork } = data.actions;
|
|
713
770
|
const load = useCallback((signal) => personWork(id, signal), [personWork, id]);
|
|
714
|
-
return (_jsx(PlatePage, { components: props.components, load: load, title: words.people.workOf(name), aside: _jsx(LinkButton, { href: PATHS.people, children: words.nav.people }), week: props.week, weekHref: (day) => personWorkHref(id, day), loading: words.people.loadingWork, couldNotLoad: words.people.couldNotLoadWork, empty: { title: words.people.noWork.title(name), body: words.people.noWork.body }, refused: words.people.notOverseen }));
|
|
771
|
+
return (_jsx(PlatePage, { components: props.components, load: load, title: words.people.workOf(name), aside: _jsx(LinkButton, { href: PATHS.people, children: words.nav.people }), week: props.week, weekStart: weekStartOf(data.me?.workspace), weekHref: (day) => personWorkHref(id, day), loading: words.people.loadingWork, couldNotLoad: words.people.couldNotLoadWork, empty: { title: words.people.noWork.title(name), body: words.people.noWork.body }, refused: words.people.notOverseen }));
|
|
715
772
|
}
|
|
716
773
|
function WorkspacePage(props) {
|
|
717
774
|
const words = useWords();
|
|
@@ -745,7 +802,9 @@ function WorkspacePage(props) {
|
|
|
745
802
|
const making = route.name === "new-listing" && administrator;
|
|
746
803
|
// An owner sees the work of the people in their projects; the console refuses the rest.
|
|
747
804
|
const oversees = administrator || data.projects.some((project) => project.role === "owner");
|
|
748
|
-
|
|
805
|
+
// The list is what the policy shows (ADR-0035): everyone, or the people one shares a project with.
|
|
806
|
+
const narrowed = peopleVisibilityOf(data.me?.workspace) === "shared_projects" && !administrator;
|
|
807
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sc-page-head", children: [_jsx("h1", { className: "sc-page-title", children: words.people.title }), administrator && !making && (_jsx(LinkButton, { href: PATHS.newListing, variant: "primary", children: words.people.letIn }))] }), narrowed && _jsx("p", { className: "sc-lead", children: words.people.narrowed }), error !== undefined && _jsx(Callout, { tone: "danger", children: error }), making && (_jsx("section", { className: "sc-panel sc-panel-form", "aria-label": words.people.letInAria, children: _jsx(ListingForm, { providers: data.me?.signIn ?? [], busy: busy, onSubmit: (input) => void act(async () => {
|
|
749
808
|
await data.actions.createListing(input);
|
|
750
809
|
navigation.replace(PATHS.people);
|
|
751
810
|
}), onCancel: () => navigation.back(PATHS.people) }) })), _jsx(components.PeopleList, { people: data.people, listed: data.listed, organization: data.organization, me: me, busy: busy, onChangeRole: administrator
|