@skillcdn/console 0.4.2 → 0.4.4
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 +3 -3
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +3 -3
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +120 -33
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts +4 -2
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +38 -10
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +22 -15
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/plugin-check.d.ts +10 -1
- package/dist/cli/plugin-check.d.ts.map +1 -1
- package/dist/cli/plugin-check.js +45 -4
- package/dist/cli/plugin-check.js.map +1 -1
- package/dist/cli/plugin-cli.d.ts +1 -1
- package/dist/cli/plugin-cli.d.ts.map +1 -1
- package/dist/cli/plugin-cli.js +80 -5
- package/dist/cli/plugin-cli.js.map +1 -1
- package/dist/cli/plugin-init.d.ts +19 -0
- package/dist/cli/plugin-init.d.ts.map +1 -1
- package/dist/cli/plugin-init.js +141 -3
- package/dist/cli/plugin-init.js.map +1 -1
- package/dist/client.d.ts +7 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +18 -5
- package/dist/client.js.map +1 -1
- package/dist/compat.js +1 -1
- package/dist/components/board.d.ts +8 -0
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +3 -3
- package/dist/components/board.js.map +1 -1
- package/dist/components/comments.d.ts.map +1 -1
- package/dist/components/comments.js +1 -1
- package/dist/components/comments.js.map +1 -1
- package/dist/components/decision-form.d.ts +8 -1
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +57 -7
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +9 -1
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +53 -7
- 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 +7 -1
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/goals.d.ts.map +1 -1
- package/dist/components/goals.js +2 -2
- package/dist/components/goals.js.map +1 -1
- package/dist/components/home.d.ts +35 -0
- package/dist/components/home.d.ts.map +1 -0
- package/dist/components/home.js +326 -0
- package/dist/components/home.js.map +1 -0
- package/dist/components/my-decisions.d.ts +2 -0
- package/dist/components/my-decisions.d.ts.map +1 -1
- package/dist/components/my-decisions.js +2 -2
- package/dist/components/my-decisions.js.map +1 -1
- package/dist/components/pack-connect.d.ts.map +1 -1
- package/dist/components/pack-connect.js +5 -4
- package/dist/components/pack-connect.js.map +1 -1
- package/dist/components/people.d.ts +2 -0
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +15 -2
- package/dist/components/people.js.map +1 -1
- package/dist/components/plugin-frame.d.ts +3 -12
- package/dist/components/plugin-frame.d.ts.map +1 -1
- package/dist/components/plugin-frame.js +53 -20
- package/dist/components/plugin-frame.js.map +1 -1
- package/dist/components/plugin-settings.d.ts.map +1 -1
- package/dist/components/plugin-settings.js +151 -26
- package/dist/components/plugin-settings.js.map +1 -1
- package/dist/components/projects.d.ts +0 -8
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +1 -13
- package/dist/components/projects.js.map +1 -1
- package/dist/components/task-view.d.ts +2 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -1
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +5 -2
- package/dist/components/ui.js.map +1 -1
- package/dist/connect-page.d.ts.map +1 -1
- package/dist/connect-page.js +5 -6
- package/dist/connect-page.js.map +1 -1
- package/dist/console.css +764 -192
- package/dist/console.d.ts +10 -3
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +49 -28
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +6 -3
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +31 -7
- package/dist/data.js.map +1 -1
- package/dist/docs-page.d.ts.map +1 -1
- package/dist/docs-page.js +6 -5
- package/dist/docs-page.js.map +1 -1
- package/dist/home-tiles.d.ts +92 -0
- package/dist/home-tiles.d.ts.map +1 -0
- package/dist/home-tiles.js +294 -0
- package/dist/home-tiles.js.map +1 -0
- package/dist/i18n/en.d.ts +180 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +181 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/index.d.ts +4 -2
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/index.js +10 -11
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +176 -3
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/limits.d.ts +8 -0
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +8 -0
- package/dist/limits.js.map +1 -1
- package/dist/plugin/bridge.d.ts +44 -3
- package/dist/plugin/bridge.d.ts.map +1 -1
- package/dist/plugin/bridge.js +26 -0
- package/dist/plugin/bridge.js.map +1 -1
- package/dist/plugin/capabilities.d.ts +7 -1
- package/dist/plugin/capabilities.d.ts.map +1 -1
- package/dist/plugin/capabilities.js +14 -1
- package/dist/plugin/capabilities.js.map +1 -1
- package/dist/plugin/context.d.ts +24 -1
- package/dist/plugin/context.d.ts.map +1 -1
- package/dist/plugin/define.d.ts +1 -0
- package/dist/plugin/define.d.ts.map +1 -1
- package/dist/plugin/define.js +1 -1
- package/dist/plugin/define.js.map +1 -1
- package/dist/plugin/limits.d.ts +5 -2
- package/dist/plugin/limits.d.ts.map +1 -1
- package/dist/plugin/limits.js +5 -2
- package/dist/plugin/limits.js.map +1 -1
- package/dist/plugin/manifest.d.ts +83 -6
- package/dist/plugin/manifest.d.ts.map +1 -1
- package/dist/plugin/manifest.js +111 -7
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin/rest-schemas.d.ts +76 -0
- package/dist/plugin/rest-schemas.d.ts.map +1 -1
- package/dist/plugin/rest-schemas.js +23 -2
- package/dist/plugin/rest-schemas.js.map +1 -1
- package/dist/plugin/sandbox.d.ts +4 -3
- package/dist/plugin/sandbox.d.ts.map +1 -1
- package/dist/plugin/sandbox.js +33 -6
- package/dist/plugin/sandbox.js.map +1 -1
- package/dist/plugin/semver.d.ts +26 -0
- package/dist/plugin/semver.d.ts.map +1 -0
- package/dist/plugin/semver.js +76 -0
- package/dist/plugin/semver.js.map +1 -0
- package/dist/plugin/testing.d.ts.map +1 -1
- package/dist/plugin/testing.js +2 -1
- package/dist/plugin/testing.js.map +1 -1
- package/dist/plugin/web.d.ts +10 -1
- package/dist/plugin/web.d.ts.map +1 -1
- package/dist/plugin/web.js +34 -0
- package/dist/plugin/web.js.map +1 -1
- package/dist/plugin.d.ts +3 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +3 -2
- package/dist/plugin.js.map +1 -1
- package/dist/rules.d.ts +2 -2
- package/dist/rules.js +3 -3
- package/dist/schemas.d.ts +574 -11
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +154 -25
- package/dist/schemas.js.map +1 -1
- package/dist/use-action.d.ts +8 -2
- package/dist/use-action.d.ts.map +1 -1
- package/dist/use-action.js +13 -6
- package/dist/use-action.js.map +1 -1
- package/dist/vocabulary.d.ts +3 -1
- package/dist/vocabulary.d.ts.map +1 -1
- package/dist/vocabulary.js +3 -0
- package/dist/vocabulary.js.map +1 -1
- package/docs/specs/cli.md +4 -3
- package/docs/specs/plugins.md +49 -35
- package/docs/specs/rest.md +20 -18
- package/package.json +7 -7
- package/skills/building-a-plugin/SKILL.md +10 -9
- package/skills/planning-the-board/references/setting-up.md +2 -1
- package/skills/working-the-board/SKILL.md +3 -3
- package/src/api.ts +10 -0
- package/src/cli/cli.ts +162 -39
- package/src/cli/format.ts +54 -10
- package/src/cli/help.ts +23 -14
- package/src/cli/plugin-check.ts +68 -2
- package/src/cli/plugin-cli.ts +104 -6
- package/src/cli/plugin-init.ts +178 -3
- package/src/client.ts +36 -9
- package/src/compat.ts +1 -1
- package/src/components/board.tsx +3 -3
- package/src/components/comments.tsx +1 -0
- package/src/components/decision-form.tsx +166 -29
- package/src/components/decision-list.tsx +116 -15
- package/src/components/event-feed.tsx +7 -1
- package/src/components/goals.tsx +4 -1
- package/src/components/home.tsx +615 -0
- package/src/components/my-decisions.tsx +4 -1
- package/src/components/pack-connect.tsx +5 -4
- package/src/components/people.tsx +136 -54
- package/src/components/plugin-frame.tsx +62 -63
- package/src/components/plugin-settings.tsx +428 -87
- package/src/components/projects.tsx +0 -55
- package/src/components/task-view.tsx +9 -1
- package/src/components/ui.tsx +6 -2
- package/src/connect-page.tsx +5 -6
- package/src/console.tsx +98 -42
- package/src/data.ts +41 -11
- package/src/docs-page.tsx +6 -5
- package/src/home-tiles.ts +438 -0
- package/src/i18n/en.ts +185 -0
- package/src/i18n/index.ts +17 -14
- package/src/i18n/ko.ts +181 -3
- package/src/index.ts +7 -5
- package/src/limits.ts +8 -0
- package/src/plugin/bridge.ts +77 -1
- package/src/plugin/capabilities.ts +17 -1
- package/src/plugin/context.ts +24 -2
- package/src/plugin/define.ts +4 -1
- package/src/plugin/limits.ts +5 -2
- package/src/plugin/manifest.ts +154 -10
- package/src/plugin/rest-schemas.ts +27 -1
- package/src/plugin/sandbox.ts +36 -8
- package/src/plugin/semver.ts +89 -0
- package/src/plugin/testing.ts +2 -1
- package/src/plugin/web.ts +49 -0
- package/src/plugin.ts +18 -0
- package/src/rules.ts +3 -3
- package/src/schemas.ts +190 -28
- package/src/styles/console.css +764 -192
- package/src/use-action.ts +16 -7
- package/src/vocabulary.ts +4 -0
- package/template/plugin/console-plugin.json +3 -1
- package/template/plugin/package.json +3 -3
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { useWords } from "../i18n/index.js";
|
|
15
15
|
import { MAX_LOGIN_LENGTH } from "../limits.js";
|
|
16
16
|
import { PERSON_ROLES } from "../vocabulary.js";
|
|
17
|
-
import { Badge, Button, PersonChip, personName, RoleBadge, Time } from "./ui.js";
|
|
17
|
+
import { Avatar, Badge, Button, PersonChip, personName, RoleBadge, Time, Workers } from "./ui.js";
|
|
18
18
|
|
|
19
19
|
// The people of the board, with what each may do and whether they are in (ADR-0022): those in,
|
|
20
20
|
// the logins let in that nobody has signed in with yet, and those deactivated. An administrator
|
|
@@ -37,6 +37,8 @@ export interface PeopleListProps {
|
|
|
37
37
|
* with it.
|
|
38
38
|
*/
|
|
39
39
|
readonly onChangeStatus?: ((person: RestPerson, status: PersonStatus) => void) | undefined;
|
|
40
|
+
/** Called with the manager an administrator chose for the person, or null for none. Left out, managers are read here, not changed. */
|
|
41
|
+
readonly onChangeManager?: ((person: RestPerson, managerId: string | null) => void) | undefined;
|
|
40
42
|
/** Called when an administrator takes a listing away. Left out, listings are read here. */
|
|
41
43
|
readonly onUnlist?: ((listing: RestListing) => void) | undefined;
|
|
42
44
|
/** Where a person's work is seen; given to an administrator, or an owner of a project. */
|
|
@@ -57,6 +59,12 @@ export function PeopleList(props: PeopleListProps) {
|
|
|
57
59
|
person.source !== "organization" &&
|
|
58
60
|
props.me?.id !== person.id;
|
|
59
61
|
const organization = props.organization ?? undefined;
|
|
62
|
+
const byId = new Map(props.people.map((person) => [person.id, person]));
|
|
63
|
+
/** The person's manager, where they have one that can be named here. */
|
|
64
|
+
const managerOf = (person: RestPerson): RestPerson | undefined =>
|
|
65
|
+
person.managerId === null || person.managerId === undefined
|
|
66
|
+
? undefined
|
|
67
|
+
: byId.get(person.managerId);
|
|
60
68
|
/** What let the person in, by its name: the organization, the domain of their login, or an administrator. */
|
|
61
69
|
const noteOf = (person: RestPerson): string => {
|
|
62
70
|
if (person.source === "organization") {
|
|
@@ -85,59 +93,133 @@ export function PeopleList(props: PeopleListProps) {
|
|
|
85
93
|
</p>
|
|
86
94
|
)}
|
|
87
95
|
<ul className="sc-people" aria-label={people.list}>
|
|
88
|
-
{active.map((person) =>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
className="sc-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
96
|
+
{active.map((person) => {
|
|
97
|
+
const manager = managerOf(person);
|
|
98
|
+
const team = active.filter((other) => other.managerId === person.id);
|
|
99
|
+
const hasWays =
|
|
100
|
+
props.workHref !== undefined || props.agentsHref !== undefined || mayChange(person);
|
|
101
|
+
return (
|
|
102
|
+
<li key={person.id} className="sc-person-card">
|
|
103
|
+
<div className="sc-person-card-head">
|
|
104
|
+
<Avatar person={person} size="lg" />
|
|
105
|
+
<div className="sc-person-card-who">
|
|
106
|
+
<span className="sc-person-card-name">
|
|
107
|
+
{personName(person)}
|
|
108
|
+
{props.me?.id === person.id && (
|
|
109
|
+
<span className="sc-person-you">{words.common.you}</span>
|
|
110
|
+
)}
|
|
111
|
+
</span>
|
|
112
|
+
<span className="sc-person-card-note">
|
|
113
|
+
{person.name !== null && <>{person.login} · </>}
|
|
114
|
+
{noteOf(person)}
|
|
115
|
+
</span>
|
|
116
|
+
</div>
|
|
117
|
+
</div>
|
|
118
|
+
<dl className="sc-person-facts">
|
|
119
|
+
<div className="sc-person-fact">
|
|
120
|
+
<dt>{people.role}</dt>
|
|
121
|
+
<dd>
|
|
122
|
+
{props.onChangeRole === undefined ? (
|
|
123
|
+
<RoleBadge role={person.role} />
|
|
124
|
+
) : (
|
|
125
|
+
<select
|
|
126
|
+
className="sc-input sc-input-quiet"
|
|
127
|
+
aria-label={people.roleOf(personName(person))}
|
|
128
|
+
value={person.role}
|
|
129
|
+
disabled={props.busy === true}
|
|
130
|
+
onChange={(event) =>
|
|
131
|
+
props.onChangeRole?.(person, event.target.value as PersonRole)
|
|
132
|
+
}
|
|
133
|
+
>
|
|
134
|
+
{PERSON_ROLES.map((role) => (
|
|
135
|
+
<option key={role} value={role}>
|
|
136
|
+
{words.vocabulary.role[role]}
|
|
137
|
+
</option>
|
|
138
|
+
))}
|
|
139
|
+
</select>
|
|
140
|
+
)}
|
|
141
|
+
</dd>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="sc-person-fact">
|
|
144
|
+
<dt>{people.manager}</dt>
|
|
145
|
+
<dd>
|
|
146
|
+
{props.onChangeManager === undefined ? (
|
|
147
|
+
manager === undefined ? (
|
|
148
|
+
<span className="sc-muted">{people.nobody}</span>
|
|
149
|
+
) : (
|
|
150
|
+
<PersonChip person={manager} />
|
|
151
|
+
)
|
|
152
|
+
) : (
|
|
153
|
+
<select
|
|
154
|
+
className="sc-input sc-input-quiet"
|
|
155
|
+
aria-label={people.managerOf(personName(person))}
|
|
156
|
+
value={person.managerId ?? ""}
|
|
157
|
+
disabled={props.busy === true}
|
|
158
|
+
onChange={(event) =>
|
|
159
|
+
props.onChangeManager?.(
|
|
160
|
+
person,
|
|
161
|
+
event.target.value === "" ? null : event.target.value,
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
>
|
|
165
|
+
<option value="">{people.nobody}</option>
|
|
166
|
+
{props.people
|
|
167
|
+
.filter(
|
|
168
|
+
(candidate) =>
|
|
169
|
+
candidate.id !== person.id &&
|
|
170
|
+
(candidate.status === "active" || candidate.id === person.managerId),
|
|
171
|
+
)
|
|
172
|
+
.map((candidate) => (
|
|
173
|
+
<option key={candidate.id} value={candidate.id}>
|
|
174
|
+
{personName(candidate)}
|
|
175
|
+
</option>
|
|
176
|
+
))}
|
|
177
|
+
</select>
|
|
178
|
+
)}
|
|
179
|
+
</dd>
|
|
180
|
+
</div>
|
|
181
|
+
{team.length > 0 && (
|
|
182
|
+
<div className="sc-person-fact">
|
|
183
|
+
<dt>{people.team}</dt>
|
|
184
|
+
<dd>
|
|
185
|
+
<Workers workers={team} />
|
|
186
|
+
</dd>
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
</dl>
|
|
190
|
+
{hasWays && (
|
|
191
|
+
<div className="sc-person-card-ways">
|
|
192
|
+
{props.workHref !== undefined && (
|
|
193
|
+
<a
|
|
194
|
+
className="sc-button sc-button-ghost sc-button-sm"
|
|
195
|
+
href={props.workHref(person)}
|
|
196
|
+
>
|
|
197
|
+
{people.work}
|
|
198
|
+
</a>
|
|
199
|
+
)}
|
|
200
|
+
{props.agentsHref !== undefined && (
|
|
201
|
+
<a
|
|
202
|
+
className="sc-button sc-button-ghost sc-button-sm"
|
|
203
|
+
href={props.agentsHref(person)}
|
|
204
|
+
>
|
|
205
|
+
{people.agents}
|
|
206
|
+
</a>
|
|
207
|
+
)}
|
|
208
|
+
{mayChange(person) && (
|
|
209
|
+
<Button
|
|
210
|
+
variant="ghost"
|
|
211
|
+
size="sm"
|
|
212
|
+
disabled={props.busy === true}
|
|
213
|
+
onClick={() => props.onChangeStatus?.(person, "deactivated")}
|
|
214
|
+
>
|
|
215
|
+
{people.deactivate}
|
|
216
|
+
</Button>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
</li>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
141
223
|
</ul>
|
|
142
224
|
{listed.length > 0 && (
|
|
143
225
|
<section className="sc-people-section" aria-label={people.notSignedIn}>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import type { ConsoleClient, PluginClient } from "../client.js";
|
|
3
|
+
import { firstDayOfWeek } from "../dates.js";
|
|
4
|
+
import type { Messages } from "../i18n/en.js";
|
|
5
|
+
import { useWords } from "../i18n/index.js";
|
|
6
|
+
import type { PluginWidgetSizeName } from "../plugin/bridge.js";
|
|
3
7
|
import {
|
|
4
8
|
type BridgeCall,
|
|
5
9
|
type BridgeEventKind,
|
|
@@ -8,10 +12,12 @@ import {
|
|
|
8
12
|
isBridgeToPage,
|
|
9
13
|
PLUGIN_BRIDGE_VERSION,
|
|
10
14
|
} from "../plugin/bridge.js";
|
|
11
|
-
import { MAX_PLUGIN_WIDGET_HEIGHT } from "../plugin/limits.js";
|
|
12
15
|
import type { Navigation } from "../router.js";
|
|
13
16
|
import type { RestEvent, RestMe, RestPlugin, RestProject } from "../schemas.js";
|
|
14
17
|
import { CONSOLE_TOKENS } from "../tokens.js";
|
|
18
|
+
import { wordsForFailure } from "../use-action.js";
|
|
19
|
+
import { CONSOLE_LANGUAGES } from "../vocabulary.js";
|
|
20
|
+
import { weekStartOf } from "./workspace.js";
|
|
15
21
|
|
|
16
22
|
// A plugin's page or widget on the console's page (ADR-0054, docs/specs/plugins.md, "Pages"):
|
|
17
23
|
// a sandboxed frame whose document the console serves, and the console's side of the bridge.
|
|
@@ -29,6 +35,8 @@ export interface PluginFrameProps {
|
|
|
29
35
|
/** The page shown, or the widget; one of the two. */
|
|
30
36
|
readonly page?: string | undefined;
|
|
31
37
|
readonly widget?: string | undefined;
|
|
38
|
+
/** The size the widget has (ADR-0059), told to the frame at the start and when it changes. */
|
|
39
|
+
readonly size?: PluginWidgetSizeName | undefined;
|
|
32
40
|
/** The project the page is on, or nothing on the workspace. */
|
|
33
41
|
readonly project?: RestProject | undefined;
|
|
34
42
|
/** What follows the page in the address, handed to the frame. */
|
|
@@ -127,6 +135,12 @@ export async function performBridgeCall(
|
|
|
127
135
|
transfer: [bytes],
|
|
128
136
|
};
|
|
129
137
|
}
|
|
138
|
+
case "people.picture": {
|
|
139
|
+
const id = text(params.id, "id");
|
|
140
|
+
const picture = await plugin.personPicture(id);
|
|
141
|
+
const bytes = new Uint8Array(picture.bytes).buffer;
|
|
142
|
+
return { value: { type: picture.contentType, bytes }, transfer: [bytes] };
|
|
143
|
+
}
|
|
130
144
|
case "files.put": {
|
|
131
145
|
const name = text(params.name, "name");
|
|
132
146
|
const type = text(params.type, "type");
|
|
@@ -161,8 +175,14 @@ export function readTheme(): Record<string, string> {
|
|
|
161
175
|
return theme;
|
|
162
176
|
}
|
|
163
177
|
|
|
164
|
-
/**
|
|
165
|
-
|
|
178
|
+
/**
|
|
179
|
+
* What a failure of a call says to the frame: the code, with the console's own refusals in the
|
|
180
|
+
* person's language, a plugin's own in its own words; or the console's own failure.
|
|
181
|
+
*/
|
|
182
|
+
function errorOf(
|
|
183
|
+
error: unknown,
|
|
184
|
+
pack: Messages,
|
|
185
|
+
): { readonly code: string; readonly message: string } {
|
|
166
186
|
if (error instanceof BridgeRefusal) {
|
|
167
187
|
return { code: error.code, message: error.message };
|
|
168
188
|
}
|
|
@@ -174,16 +194,20 @@ function errorOf(error: unknown): { readonly code: string; readonly message: str
|
|
|
174
194
|
"message" in error &&
|
|
175
195
|
typeof error.message === "string"
|
|
176
196
|
) {
|
|
177
|
-
return { code: error.code, message: error.message };
|
|
197
|
+
return { code: error.code, message: wordsForFailure(error, pack) ?? error.message };
|
|
178
198
|
}
|
|
179
199
|
return { code: "plugin.failed", message: "the call failed" };
|
|
180
200
|
}
|
|
181
201
|
|
|
182
202
|
export function PluginFrame(props: PluginFrameProps) {
|
|
183
203
|
const { plugin, page, widget, project, me, language, client, navigation, events } = props;
|
|
204
|
+
const pack = useWords();
|
|
205
|
+
const words = pack.plugins;
|
|
184
206
|
const frame = useRef<HTMLIFrameElement>(null);
|
|
185
207
|
const isWidget = widget !== undefined;
|
|
186
|
-
const [height, setHeight] = useState(
|
|
208
|
+
const [height, setHeight] = useState(480);
|
|
209
|
+
/** Whether the console served the frame nothing to show: a plugin turned off since the page read it, say. */
|
|
210
|
+
const [unavailable, setUnavailable] = useState(false);
|
|
187
211
|
const subscribed = useRef<Set<BridgeEventKind>>(new Set());
|
|
188
212
|
const ready = useRef(false);
|
|
189
213
|
const lastPluginEvent = useRef(0);
|
|
@@ -199,6 +223,8 @@ export function PluginFrame(props: PluginFrameProps) {
|
|
|
199
223
|
}),
|
|
200
224
|
[scoped, page, widget, project],
|
|
201
225
|
);
|
|
226
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: another address is another document
|
|
227
|
+
useEffect(() => setUnavailable(false), [src]);
|
|
202
228
|
|
|
203
229
|
const post = useCallback((message: BridgeToFrame, transfer?: Transferable[]) => {
|
|
204
230
|
// The frame's origin is opaque, so the target is any: the frame itself checks the sender.
|
|
@@ -220,6 +246,9 @@ export function PluginFrame(props: PluginFrameProps) {
|
|
|
220
246
|
}
|
|
221
247
|
const message = event.data;
|
|
222
248
|
switch (message.t) {
|
|
249
|
+
case "unavailable":
|
|
250
|
+
setUnavailable(true);
|
|
251
|
+
return;
|
|
223
252
|
case "ready":
|
|
224
253
|
ready.current = true;
|
|
225
254
|
post({
|
|
@@ -228,6 +257,7 @@ export function PluginFrame(props: PluginFrameProps) {
|
|
|
228
257
|
plugin: { key: plugin.key, version: plugin.version },
|
|
229
258
|
...(page === undefined ? {} : { page }),
|
|
230
259
|
...(widget === undefined ? {} : { widget }),
|
|
260
|
+
...(props.size === undefined ? {} : { size: props.size }),
|
|
231
261
|
project: project === undefined ? null : { key: project.key, name: project.name },
|
|
232
262
|
person: {
|
|
233
263
|
id: me.person?.id ?? "",
|
|
@@ -236,6 +266,13 @@ export function PluginFrame(props: PluginFrameProps) {
|
|
|
236
266
|
language,
|
|
237
267
|
admin: me.person?.role === "admin",
|
|
238
268
|
},
|
|
269
|
+
workspace: {
|
|
270
|
+
name: me.workspace.name,
|
|
271
|
+
languages: [...CONSOLE_LANGUAGES],
|
|
272
|
+
timeZone: me.workspace.timeZone ?? "UTC",
|
|
273
|
+
weekStart: weekStartOf(me.workspace),
|
|
274
|
+
firstDay: firstDayOfWeek(language, weekStartOf(me.workspace)),
|
|
275
|
+
},
|
|
239
276
|
roles: plugin.roles,
|
|
240
277
|
path,
|
|
241
278
|
language,
|
|
@@ -256,7 +293,7 @@ export function PluginFrame(props: PluginFrameProps) {
|
|
|
256
293
|
t: "result",
|
|
257
294
|
id: message.id,
|
|
258
295
|
ok: false,
|
|
259
|
-
error: errorOf(error),
|
|
296
|
+
error: errorOf(error, pack),
|
|
260
297
|
}),
|
|
261
298
|
);
|
|
262
299
|
return;
|
|
@@ -266,15 +303,10 @@ export function PluginFrame(props: PluginFrameProps) {
|
|
|
266
303
|
}
|
|
267
304
|
return;
|
|
268
305
|
case "resize":
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
Math.ceil(message.height),
|
|
274
|
-
isWidget ? MAX_PLUGIN_WIDGET_HEIGHT : MAX_PAGE_HEIGHT,
|
|
275
|
-
),
|
|
276
|
-
),
|
|
277
|
-
);
|
|
306
|
+
// A widget fills the cells its size gives it (ADR-0059); a page is as tall as it asks.
|
|
307
|
+
if (!isWidget) {
|
|
308
|
+
setHeight(Math.max(40, Math.min(Math.ceil(message.height), MAX_PAGE_HEIGHT)));
|
|
309
|
+
}
|
|
278
310
|
return;
|
|
279
311
|
case "title":
|
|
280
312
|
if (!isWidget && typeof document !== "undefined") {
|
|
@@ -315,6 +347,14 @@ export function PluginFrame(props: PluginFrameProps) {
|
|
|
315
347
|
}
|
|
316
348
|
}, [path, post]);
|
|
317
349
|
|
|
350
|
+
// The person gave the widget another size: the frame lays itself out for it.
|
|
351
|
+
const size = props.size;
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
if (ready.current && size !== undefined) {
|
|
354
|
+
post({ v: PLUGIN_BRIDGE_VERSION, t: "size", size });
|
|
355
|
+
}
|
|
356
|
+
}, [size, post]);
|
|
357
|
+
|
|
318
358
|
// The plugin's commands, as the project's feed tells of them: a frame that listens hears of each.
|
|
319
359
|
useEffect(() => {
|
|
320
360
|
if (events === undefined) {
|
|
@@ -375,63 +415,22 @@ export function PluginFrame(props: PluginFrameProps) {
|
|
|
375
415
|
.catch(() => undefined);
|
|
376
416
|
}, [scoped]);
|
|
377
417
|
|
|
418
|
+
if (unavailable) {
|
|
419
|
+
return <p className="sc-plugin-frame-unavailable">{words.frameUnavailable}</p>;
|
|
420
|
+
}
|
|
378
421
|
return (
|
|
379
422
|
<iframe
|
|
380
423
|
ref={frame}
|
|
381
|
-
className={
|
|
424
|
+
className={
|
|
425
|
+
isWidget ? "sc-plugin-frame sc-plugin-frame-widget" : "sc-plugin-frame sc-plugin-frame-page"
|
|
426
|
+
}
|
|
382
427
|
src={src}
|
|
383
428
|
title={props.title}
|
|
384
429
|
// As the document's own policy says: scripts, and forms firing their submit event, which
|
|
385
430
|
// the policy's form-action 'none' keeps from going anywhere.
|
|
386
431
|
sandbox="allow-scripts allow-forms"
|
|
387
432
|
referrerPolicy="origin"
|
|
388
|
-
style={{ height: `${height}px` }}
|
|
433
|
+
style={isWidget ? undefined : { height: `${height}px` }}
|
|
389
434
|
/>
|
|
390
435
|
);
|
|
391
436
|
}
|
|
392
|
-
|
|
393
|
-
export interface PluginWidgetsProps {
|
|
394
|
-
readonly plugins: readonly RestPlugin[];
|
|
395
|
-
readonly place: "home:top" | "home:bottom";
|
|
396
|
-
readonly me: RestMe;
|
|
397
|
-
readonly language: string;
|
|
398
|
-
readonly client: ConsoleClient;
|
|
399
|
-
readonly navigation: Navigation;
|
|
400
|
-
/** The widget's name in the person's language. */
|
|
401
|
-
readonly nameOf: (name: Readonly<Record<string, string>>) => string;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/** The widgets at a place of the front page, in a grid of six columns: a third takes two, a half three, the full six. */
|
|
405
|
-
export function PluginWidgets(props: PluginWidgetsProps) {
|
|
406
|
-
const shown = props.plugins.flatMap((plugin) =>
|
|
407
|
-
plugin.widgets
|
|
408
|
-
.filter((widget) => widget.place === props.place)
|
|
409
|
-
.map((widget) => ({ plugin, widget })),
|
|
410
|
-
);
|
|
411
|
-
if (shown.length === 0) {
|
|
412
|
-
return null;
|
|
413
|
-
}
|
|
414
|
-
return (
|
|
415
|
-
<section
|
|
416
|
-
className={`sc-widgets ${props.place === "home:top" ? "sc-widgets-top" : "sc-widgets-bottom"}`}
|
|
417
|
-
>
|
|
418
|
-
{shown.map(({ plugin, widget }) => (
|
|
419
|
-
<article
|
|
420
|
-
key={`${plugin.key}:${widget.key}`}
|
|
421
|
-
className={`sc-widget sc-widget-${widget.size}`}
|
|
422
|
-
>
|
|
423
|
-
<h2 className="sc-widget-title">{props.nameOf(widget.name)}</h2>
|
|
424
|
-
<PluginFrame
|
|
425
|
-
plugin={plugin}
|
|
426
|
-
widget={widget.key}
|
|
427
|
-
me={props.me}
|
|
428
|
-
language={props.language}
|
|
429
|
-
client={props.client}
|
|
430
|
-
navigation={props.navigation}
|
|
431
|
-
title={props.nameOf(widget.name)}
|
|
432
|
-
/>
|
|
433
|
-
</article>
|
|
434
|
-
))}
|
|
435
|
-
</section>
|
|
436
|
-
);
|
|
437
|
-
}
|