@parallelworks/ui 0.55.0 → 0.56.0
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 +52 -22
- package/dist/access/AccessDrawer.d.ts +30 -19
- package/dist/access/AccessDrawer.js +92 -67
- package/dist/access/AccessList.d.ts +17 -16
- package/dist/access/AccessList.js +218 -114
- package/dist/access/GrantView.d.ts +9 -8
- package/dist/access/GrantView.js +34 -36
- package/dist/access/PrincipalPicker.d.ts +38 -0
- package/dist/access/{GroupPicker.js → PrincipalPicker.js} +173 -80
- package/dist/access/index.d.ts +1 -1
- package/dist/access/model.d.ts +67 -24
- package/dist/access/model.js +111 -47
- package/dist/access/strings.d.ts +63 -39
- package/dist/access/strings.js +114 -84
- package/dist/access/types.d.ts +32 -14
- package/dist/ai/adapter/mock/mockAdapter.js +1 -1
- package/dist/ai/core/useSSEStream.d.ts +1 -1
- package/dist/components/Checkbox.js +1 -1
- package/dist/components/Combobox.js +25 -20
- package/dist/components/DatePicker.js +58 -53
- package/dist/components/Drawer.js +3 -1
- package/dist/components/HoverCard.js +15 -10
- package/dist/components/Menu.js +3 -0
- package/dist/components/Modal.js +32 -27
- package/dist/components/Popover.js +30 -25
- package/dist/components/Radio.js +1 -1
- package/dist/components/Select.js +47 -42
- package/dist/components/Slider.js +3 -3
- package/dist/components/Switch.js +1 -1
- package/dist/components/Tooltip.js +16 -11
- package/dist/components/fieldBox.js +1 -1
- package/dist/components/fullscreen.d.ts +4 -0
- package/dist/components/fullscreen.js +18 -0
- package/dist/graph/GraphEditorDialogs.d.ts +1 -1
- package/dist/styles/theme.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/theme/derive.d.ts +2 -0
- package/dist/theme/derive.js +12 -13
- package/package.json +1 -1
- package/dist/access/GroupPicker.d.ts +0 -35
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
|
-
import { AddIcon, ChevronRightIcon, OrganizationIcon, TrashIcon } from "../icons.js";
|
|
2
|
+
import { AddIcon, ChevronRightIcon, LockIcon, OrganizationIcon, TrashIcon } from "../icons.js";
|
|
3
3
|
import { Loader } from "../components/Loader.js";
|
|
4
4
|
import { Button } from "../components/Button.js";
|
|
5
5
|
import { IconButton } from "../components/IconButton.js";
|
|
6
6
|
import { Callout } from "../components/Callout.js";
|
|
7
7
|
import { collapsePanelClassName } from "../components/Disclosure.js";
|
|
8
8
|
import { StatusBadge } from "../components/StatusBadge.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { alsoHas, firstOfEach, isOwner, key, overlapOf, removalOf } from "./model.js";
|
|
10
|
+
import { PrincipalTile } from "./PrincipalPicker.js";
|
|
11
11
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
import { useEffect, useId, useRef } from "react";
|
|
13
13
|
import { Collapsible } from "@base-ui/react/collapsible";
|
|
14
14
|
//#region src/access/AccessList.tsx
|
|
15
|
-
|
|
16
|
-
var
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
/** The id the owner's section has among the roles' sections. */
|
|
16
|
+
var OWNER_SECTION = "__owner";
|
|
17
|
+
var principalLabel = (principal, t) => principal.kind === "everyone" ? t.everyoneInOrganization : principal.name;
|
|
18
|
+
/** The line under a name: who a person is, or how many are in a group. */
|
|
19
|
+
var aboutOf = (principal, t) => principal.kind === "user" ? principal.detail : principal.memberCount === void 0 ? void 0 : principal.kind === "everyone" ? t.people(principal.memberCount) : t.members(principal.memberCount);
|
|
20
|
+
/** Where a role someone has elsewhere comes from, as "through" reads it. */
|
|
21
|
+
var sourceOf = (holding) => holding.through?.name ?? holding.from ?? "";
|
|
22
|
+
/** Stands where a remove button would, on a row that can't be removed here. */
|
|
23
|
+
function Locked() {
|
|
24
|
+
return /* @__PURE__ */ jsx("span", {
|
|
25
|
+
className: "flex size-7 items-center justify-center text-(--theme-muted-text-color)",
|
|
26
|
+
children: /* @__PURE__ */ jsx(LockIcon, {
|
|
27
|
+
"aria-hidden": "true",
|
|
28
|
+
className: "size-4"
|
|
29
|
+
})
|
|
21
30
|
});
|
|
22
31
|
}
|
|
23
|
-
/** A role or
|
|
32
|
+
/** A role or principal whose rows fold under it. */
|
|
24
33
|
function Section({ id, open, onOpenChange, disabled, lead, title, description, aside, narrowAside, notice, children }) {
|
|
25
34
|
return /* @__PURE__ */ jsxs(Collapsible.Root, {
|
|
26
35
|
open: open && !disabled,
|
|
@@ -30,7 +39,7 @@ function Section({ id, open, onOpenChange, disabled, lead, title, description, a
|
|
|
30
39
|
children: [
|
|
31
40
|
/* @__PURE__ */ jsxs(Collapsible.Trigger, {
|
|
32
41
|
id,
|
|
33
|
-
className: cx("group/section flex w-full items-center gap-3 px-4 py-
|
|
42
|
+
className: cx("group/section flex w-full items-center gap-3 px-4 py-2.5 text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--theme-focus)", disabled ? "cursor-default" : "cursor-pointer hover:bg-(--theme-hover)"),
|
|
34
43
|
children: [
|
|
35
44
|
/* @__PURE__ */ jsx(ChevronRightIcon, {
|
|
36
45
|
"aria-hidden": "true",
|
|
@@ -43,22 +52,22 @@ function Section({ id, open, onOpenChange, disabled, lead, title, description, a
|
|
|
43
52
|
/* @__PURE__ */ jsx("span", {
|
|
44
53
|
className: "flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5",
|
|
45
54
|
children: /* @__PURE__ */ jsx("span", {
|
|
46
|
-
className: "font-medium wrap-
|
|
55
|
+
className: "text-sm font-medium wrap-anywhere",
|
|
47
56
|
children: title
|
|
48
57
|
})
|
|
49
58
|
}),
|
|
50
59
|
description && /* @__PURE__ */ jsx("span", {
|
|
51
|
-
className: "hidden text-
|
|
60
|
+
className: "hidden text-xs wrap-anywhere text-(--theme-muted-text-color) @min-[30rem]/access:block",
|
|
52
61
|
children: description
|
|
53
62
|
}),
|
|
54
63
|
narrowAside && /* @__PURE__ */ jsx("span", {
|
|
55
|
-
className: "text-
|
|
64
|
+
className: "text-xs text-(--theme-muted-text-color) @min-[30rem]/access:hidden",
|
|
56
65
|
children: narrowAside
|
|
57
66
|
})
|
|
58
67
|
]
|
|
59
68
|
}),
|
|
60
69
|
aside && /* @__PURE__ */ jsx("span", {
|
|
61
|
-
className: "hidden shrink-0 items-center gap-1 text-
|
|
70
|
+
className: "hidden shrink-0 items-center gap-1 text-xs text-(--theme-muted-text-color) @min-[30rem]/access:flex",
|
|
62
71
|
children: aside
|
|
63
72
|
})
|
|
64
73
|
]
|
|
@@ -77,7 +86,7 @@ function Section({ id, open, onOpenChange, disabled, lead, title, description, a
|
|
|
77
86
|
]
|
|
78
87
|
});
|
|
79
88
|
}
|
|
80
|
-
/** A line under a section: a
|
|
89
|
+
/** A line under a section: a principal under a role, or a role under a principal. */
|
|
81
90
|
function Row({ lead, title, meta, action, muted }) {
|
|
82
91
|
return /* @__PURE__ */ jsxs("div", {
|
|
83
92
|
className: "flex items-center gap-3 py-1.5 ps-11 pe-4 @min-[30rem]/access:ps-11",
|
|
@@ -86,10 +95,10 @@ function Row({ lead, title, meta, action, muted }) {
|
|
|
86
95
|
/* @__PURE__ */ jsxs("div", {
|
|
87
96
|
className: cx("min-w-0 flex-1", muted && "text-(--theme-muted-text-color)"),
|
|
88
97
|
children: [/* @__PURE__ */ jsx("div", {
|
|
89
|
-
className: "flex min-w-0 flex-wrap items-center gap-x-2",
|
|
98
|
+
className: "flex min-w-0 flex-wrap items-center gap-x-2 text-sm wrap-anywhere",
|
|
90
99
|
children: title
|
|
91
100
|
}), meta && /* @__PURE__ */ jsx("div", {
|
|
92
|
-
className: "text-
|
|
101
|
+
className: "text-xs wrap-anywhere text-(--theme-muted-text-color)",
|
|
93
102
|
children: meta
|
|
94
103
|
})]
|
|
95
104
|
}),
|
|
@@ -112,21 +121,38 @@ function RemoveButton({ label, saving, removal, onAsk }) {
|
|
|
112
121
|
onClick: () => onAsk(removal)
|
|
113
122
|
});
|
|
114
123
|
}
|
|
115
|
-
/**
|
|
124
|
+
/** What someone keeps elsewhere, by where it comes from, everyone's apart. */
|
|
125
|
+
function keptElsewhere(holdings) {
|
|
126
|
+
const throughEveryone = [];
|
|
127
|
+
const through = /* @__PURE__ */ new Map();
|
|
128
|
+
for (const holding of holdings) if (holding.through?.kind === "everyone") throughEveryone.push(holding.role.name);
|
|
129
|
+
else {
|
|
130
|
+
const from = sourceOf(holding);
|
|
131
|
+
through.set(from, [...through.get(from) ?? [], holding.role.name]);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
throughEveryone,
|
|
135
|
+
through: [...through].map(([from, roles]) => ({
|
|
136
|
+
from,
|
|
137
|
+
roles
|
|
138
|
+
}))
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/** Asks before a principal loses roles, saying exactly what it loses and what it keeps. */
|
|
116
142
|
function RemoveConfirm({ model, removal, saving, onCancel, onConfirm, t }) {
|
|
117
143
|
const titleId = useId();
|
|
118
144
|
const cancel = useRef(null);
|
|
119
145
|
useEffect(() => cancel.current?.focus(), []);
|
|
120
|
-
const
|
|
121
|
-
if (!
|
|
122
|
-
const everyone = group.kind === "everyone";
|
|
146
|
+
const principal = model.principalById.get(removal.principalId);
|
|
147
|
+
if (!principal) return null;
|
|
123
148
|
const roleNames = removal.roleIds.map((id) => model.roleById.get(id)?.name ?? id);
|
|
124
|
-
const impact = removalOf(model, removal.
|
|
149
|
+
const impact = removalOf(model, removal.principalId, removal.roleIds);
|
|
125
150
|
const own = impact.keep.map((role) => role.name);
|
|
126
|
-
const throughEveryone = impact.
|
|
151
|
+
const { throughEveryone, through } = keptElsewhere(impact.keepElsewhere);
|
|
152
|
+
const kept = t.keeps(own, throughEveryone, through);
|
|
127
153
|
return /* @__PURE__ */ jsx("fieldset", {
|
|
128
154
|
"aria-labelledby": titleId,
|
|
129
|
-
className: "m-0 min-w-0 border-0 px-4 py-2",
|
|
155
|
+
className: "m-0 min-w-0 border-0 px-4 py-2 text-sm",
|
|
130
156
|
onKeyDown: (e) => {
|
|
131
157
|
if (e.key === "Escape" && !e.defaultPrevented) {
|
|
132
158
|
e.stopPropagation();
|
|
@@ -140,26 +166,33 @@ function RemoveConfirm({ model, removal, saving, onCancel, onConfirm, t }) {
|
|
|
140
166
|
/* @__PURE__ */ jsx("p", {
|
|
141
167
|
id: titleId,
|
|
142
168
|
className: "m-0 font-semibold text-(--theme-app)",
|
|
143
|
-
children: removal.all ? t.removeAllTitle(
|
|
169
|
+
children: removal.all ? t.removeAllTitle(principalLabel(principal, t)) : t.removeTitle(principalLabel(principal, t), roleNames)
|
|
144
170
|
}),
|
|
145
171
|
/* @__PURE__ */ jsx("div", {
|
|
146
172
|
className: "mt-1 text-(--theme-app)",
|
|
147
|
-
children: impact.
|
|
173
|
+
children: impact.owner ? /* @__PURE__ */ jsx("p", {
|
|
174
|
+
className: "m-0 text-(--theme-muted-text-color)",
|
|
175
|
+
children: t.ownerLosesNothing
|
|
176
|
+
}) : impact.lost.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
148
177
|
/* @__PURE__ */ jsx("p", {
|
|
149
178
|
className: "m-0 text-(--theme-muted-text-color)",
|
|
150
|
-
children: t.losesIntro(
|
|
179
|
+
children: t.losesIntro(principal.kind, principal.memberCount)
|
|
151
180
|
}),
|
|
152
181
|
/* @__PURE__ */ jsx("ul", {
|
|
153
182
|
className: "my-2 grid list-disc gap-x-6 gap-y-1 ps-5 @min-[30rem]/access:grid-cols-2",
|
|
154
183
|
children: impact.lost.map((permission) => /* @__PURE__ */ jsx("li", { children: model.permissionLabel(permission) }, permission))
|
|
155
184
|
}),
|
|
156
|
-
/* @__PURE__ */ jsx("p", {
|
|
185
|
+
kept && /* @__PURE__ */ jsx("p", {
|
|
157
186
|
className: "m-0 text-(--theme-muted-text-color)",
|
|
158
|
-
children:
|
|
187
|
+
children: kept
|
|
159
188
|
})
|
|
160
189
|
] }) : /* @__PURE__ */ jsx("p", {
|
|
161
190
|
className: "m-0 text-(--theme-muted-text-color)",
|
|
162
|
-
children: t.losesNothing(
|
|
191
|
+
children: t.losesNothing([
|
|
192
|
+
...own,
|
|
193
|
+
...through.flatMap((source) => source.roles),
|
|
194
|
+
...throughEveryone
|
|
195
|
+
])
|
|
163
196
|
})
|
|
164
197
|
}),
|
|
165
198
|
/* @__PURE__ */ jsxs("div", {
|
|
@@ -182,18 +215,51 @@ function RemoveConfirm({ model, removal, saving, onCancel, onConfirm, t }) {
|
|
|
182
215
|
});
|
|
183
216
|
}
|
|
184
217
|
/** Marks the button that asked for a removal, so focus can go back to it. */
|
|
185
|
-
var removeKey = (removal) => `${removal.
|
|
186
|
-
var sameRemoval = (a,
|
|
187
|
-
/**
|
|
218
|
+
var removeKey = (removal) => `${removal.principalId}\u0000${removal.all ? "*" : removal.roleIds.join(",")}`;
|
|
219
|
+
var sameRemoval = (a, principalId, roleId) => a !== void 0 && a.principalId === principalId && (roleId === void 0 ? a.all : !a.all && a.roleIds.includes(roleId));
|
|
220
|
+
/** The hint beside someone holding a role: what else already gives it to them. */
|
|
221
|
+
function overlapHint(overlap, t) {
|
|
222
|
+
if (overlap.owner) return t.ownerHasAll;
|
|
223
|
+
if (overlap.coveredBy.length > 0) return t.coveredBy(overlap.coveredBy.map((other) => other.name));
|
|
224
|
+
const from = [...new Set(overlap.elsewhere.filter((holding) => holding.through?.kind !== "everyone").map((holding) => sourceOf(holding)))];
|
|
225
|
+
if (from.length > 0) return t.throughToo(from);
|
|
226
|
+
return overlap.elsewhere.length > 0 ? t.everyoneHasThisToo : void 0;
|
|
227
|
+
}
|
|
228
|
+
/** Who has each role, role by role, after the owner. */
|
|
188
229
|
function ByRole({ model, shown, order, expanded, onExpandedChange, filtering, actions, t, sectionId }) {
|
|
189
230
|
return order.map((roleId) => {
|
|
231
|
+
if (roleId === "__owner") {
|
|
232
|
+
const owner = model.owner;
|
|
233
|
+
return owner && /* @__PURE__ */ jsx(Section, {
|
|
234
|
+
id: sectionId(roleId),
|
|
235
|
+
open: expanded.has(roleId),
|
|
236
|
+
onOpenChange: (open) => onExpandedChange(roleId, open),
|
|
237
|
+
title: t.owner,
|
|
238
|
+
description: t.ownerDescription,
|
|
239
|
+
aside: principalLabel(owner, t),
|
|
240
|
+
narrowAside: principalLabel(owner, t),
|
|
241
|
+
children: /* @__PURE__ */ jsx(Row, {
|
|
242
|
+
lead: /* @__PURE__ */ jsx(PrincipalTile, {
|
|
243
|
+
principal: owner,
|
|
244
|
+
compact: true,
|
|
245
|
+
className: "hidden @min-[30rem]/access:flex"
|
|
246
|
+
}),
|
|
247
|
+
title: principalLabel(owner, t),
|
|
248
|
+
meta: aboutOf(owner, t),
|
|
249
|
+
action: /* @__PURE__ */ jsx(Locked, {})
|
|
250
|
+
})
|
|
251
|
+
}, roleId);
|
|
252
|
+
}
|
|
190
253
|
const role = model.roleById.get(roleId);
|
|
191
254
|
if (!role) return null;
|
|
192
|
-
const
|
|
193
|
-
const
|
|
194
|
-
const
|
|
195
|
-
const
|
|
196
|
-
const
|
|
255
|
+
const direct = model.holders.get(roleId) ?? [];
|
|
256
|
+
const inherited = model.inheritedHolders.get(roleId) ?? [];
|
|
257
|
+
const all = new Map([...direct, ...inherited.map((holder) => holder.principal)].map((p) => [p.id, p]));
|
|
258
|
+
const principalIds = shown.get(roleId) ?? [];
|
|
259
|
+
const everyone = [...all.values()].some((principal) => principal.kind === "everyone");
|
|
260
|
+
const people = [...all.values()].filter((principal) => principal.kind === "user").length;
|
|
261
|
+
const groups = [...all.values()].filter((principal) => principal.kind === "group").length;
|
|
262
|
+
const count = filtering && principalIds.length !== all.size ? t.holdersShown(principalIds.length, all.size) : people + groups > 0 ? t.holders(people, groups) : void 0;
|
|
197
263
|
const everyoneTag = everyone && /* @__PURE__ */ jsxs(StatusBadge, {
|
|
198
264
|
variant: "info",
|
|
199
265
|
size: "md",
|
|
@@ -207,98 +273,120 @@ function ByRole({ model, shown, order, expanded, onExpandedChange, filtering, ac
|
|
|
207
273
|
id: sectionId(roleId),
|
|
208
274
|
open: expanded.has(roleId),
|
|
209
275
|
onOpenChange: (open) => onExpandedChange(roleId, open),
|
|
210
|
-
disabled:
|
|
276
|
+
disabled: all.size === 0,
|
|
211
277
|
notice: failure,
|
|
212
|
-
title:
|
|
278
|
+
title: role.name,
|
|
213
279
|
description: role.description,
|
|
214
280
|
aside: /* @__PURE__ */ jsxs(Fragment, { children: [everyoneTag, count] }),
|
|
215
281
|
narrowAside: everyoneTag && !count ? /* @__PURE__ */ jsx("span", {
|
|
216
282
|
className: "mt-1 inline-flex",
|
|
217
283
|
children: everyoneTag
|
|
218
284
|
}) : count,
|
|
219
|
-
children:
|
|
220
|
-
const
|
|
221
|
-
if (!
|
|
222
|
-
|
|
285
|
+
children: principalIds.map((principalId) => {
|
|
286
|
+
const principal = all.get(principalId);
|
|
287
|
+
if (!principal) return null;
|
|
288
|
+
const lead = /* @__PURE__ */ jsx(PrincipalTile, {
|
|
289
|
+
principal,
|
|
290
|
+
compact: true,
|
|
291
|
+
className: "hidden @min-[30rem]/access:flex"
|
|
292
|
+
});
|
|
293
|
+
if (!direct.some((holder) => holder.id === principalId)) {
|
|
294
|
+
const from = inherited.find((holder) => holder.principal.id === principalId)?.from;
|
|
295
|
+
return /* @__PURE__ */ jsx(Row, {
|
|
296
|
+
lead,
|
|
297
|
+
title: principalLabel(principal, t),
|
|
298
|
+
meta: [aboutOf(principal, t), from !== void 0 && t.inheritedFrom(from)].filter(Boolean).join(" · "),
|
|
299
|
+
action: /* @__PURE__ */ jsx(Locked, {})
|
|
300
|
+
}, principalId);
|
|
301
|
+
}
|
|
302
|
+
if (sameRemoval(actions.removal, principalId, roleId) && actions.removal) return /* @__PURE__ */ jsx(RemoveConfirm, {
|
|
223
303
|
model,
|
|
224
304
|
removal: actions.removal,
|
|
225
305
|
saving: actions.saving.size > 0,
|
|
226
306
|
onCancel: actions.onCancelRemove,
|
|
227
307
|
onConfirm: () => actions.removal && actions.onRemove(actions.removal),
|
|
228
308
|
t
|
|
229
|
-
},
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
const label = t.removeRole(groupLabel(group, t), role.name);
|
|
309
|
+
}, principalId);
|
|
310
|
+
const hint = overlapHint(overlapOf(model, principalId, role), t);
|
|
311
|
+
const label = t.removeRole(principalLabel(principal, t), role.name);
|
|
233
312
|
return /* @__PURE__ */ jsx(Row, {
|
|
234
|
-
lead
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}),
|
|
238
|
-
title: groupLabel(group, t),
|
|
239
|
-
meta: [sizeOf(group, t), hint].filter(Boolean).join(" · "),
|
|
313
|
+
lead,
|
|
314
|
+
title: principalLabel(principal, t),
|
|
315
|
+
meta: [aboutOf(principal, t), hint].filter(Boolean).join(" · "),
|
|
240
316
|
action: !actions.readOnly && /* @__PURE__ */ jsx(RemoveButton, {
|
|
241
317
|
label,
|
|
242
318
|
saving: actions.saving.has(key({
|
|
243
|
-
|
|
319
|
+
principalId,
|
|
244
320
|
roleId
|
|
245
321
|
})),
|
|
246
322
|
removal: {
|
|
247
|
-
|
|
323
|
+
principalId,
|
|
248
324
|
roleIds: [roleId],
|
|
249
325
|
all: false
|
|
250
326
|
},
|
|
251
327
|
onAsk: actions.onAskRemove
|
|
252
328
|
})
|
|
253
|
-
},
|
|
329
|
+
}, principalId);
|
|
254
330
|
})
|
|
255
331
|
}, roleId);
|
|
256
332
|
});
|
|
257
333
|
}
|
|
258
|
-
/** Which roles each group has,
|
|
259
|
-
function
|
|
260
|
-
return order.map((
|
|
261
|
-
const
|
|
262
|
-
if (!
|
|
263
|
-
const
|
|
264
|
-
const
|
|
334
|
+
/** Which roles each person and group has, one by one. */
|
|
335
|
+
function ByPrincipal({ model, shown, order, expanded, onExpandedChange, actions, t, sectionId }) {
|
|
336
|
+
return order.map((principalId) => {
|
|
337
|
+
const principal = model.principalById.get(principalId);
|
|
338
|
+
if (!principal) return null;
|
|
339
|
+
const owner = isOwner(model, principalId);
|
|
340
|
+
const held = model.held.get(principalId) ?? [];
|
|
341
|
+
const ownInherited = model.inherited.get(principalId) ?? [];
|
|
342
|
+
const roleIds = new Set(shown.get(principalId) ?? [...held, ...ownInherited.map((h) => h.role)].map((r) => r.id));
|
|
265
343
|
const roles = held.filter((role) => roleIds.has(role.id));
|
|
266
|
-
const inherited =
|
|
267
|
-
const
|
|
268
|
-
const failure = actions.failure?.
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
children:
|
|
283
|
-
})
|
|
284
|
-
|
|
344
|
+
const inherited = ownInherited.filter((holding) => roleIds.has(holding.role.id) && !held.some((role) => role.id === holding.role.id));
|
|
345
|
+
const through = firstOfEach(alsoHas(model, principalId)).filter((holding) => holding.through !== void 0 && !held.some((role) => role.id === holding.role.id) && !ownInherited.some((own) => own.role.id === holding.role.id));
|
|
346
|
+
const failure = actions.failure?.principalId === principalId ? actions.failure.node : void 0;
|
|
347
|
+
const everyone = principal.kind === "everyone";
|
|
348
|
+
const badgeRoles = [...held, ...ownInherited.map((holding) => holding.role).filter((role) => !held.includes(role))];
|
|
349
|
+
const room = owner ? 1 : 2;
|
|
350
|
+
const badges = badgeRoles.length <= room ? badgeRoles : badgeRoles.slice(0, room - 1);
|
|
351
|
+
const hidden = badgeRoles.slice(badges.length);
|
|
352
|
+
const roleBadges = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
353
|
+
owner && /* @__PURE__ */ jsx(StatusBadge, {
|
|
354
|
+
size: "md",
|
|
355
|
+
variant: "info",
|
|
356
|
+
children: t.owner
|
|
357
|
+
}),
|
|
358
|
+
badges.map((role) => /* @__PURE__ */ jsx(StatusBadge, {
|
|
359
|
+
size: "md",
|
|
360
|
+
children: role.name
|
|
361
|
+
}, role.id)),
|
|
362
|
+
hidden.length > 0 && /* @__PURE__ */ jsxs(StatusBadge, {
|
|
363
|
+
size: "md",
|
|
364
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
365
|
+
"aria-hidden": "true",
|
|
366
|
+
children: t.moreRoles(hidden.length)
|
|
367
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
368
|
+
className: "sr-only",
|
|
369
|
+
children: t.moreRolesLabel(hidden.map((role) => role.name))
|
|
370
|
+
})]
|
|
371
|
+
})
|
|
372
|
+
] });
|
|
285
373
|
return /* @__PURE__ */ jsx(Section, {
|
|
286
|
-
id: sectionId(
|
|
287
|
-
open: expanded.has(
|
|
288
|
-
onOpenChange: (open) => onExpandedChange(
|
|
289
|
-
lead: /* @__PURE__ */ jsx(
|
|
290
|
-
|
|
374
|
+
id: sectionId(principalId),
|
|
375
|
+
open: expanded.has(principalId),
|
|
376
|
+
onOpenChange: (open) => onExpandedChange(principalId, open),
|
|
377
|
+
lead: /* @__PURE__ */ jsx(PrincipalTile, {
|
|
378
|
+
principal,
|
|
291
379
|
className: "hidden @min-[30rem]/access:flex"
|
|
292
380
|
}),
|
|
293
381
|
notice: failure,
|
|
294
|
-
title:
|
|
295
|
-
description: everyone ? t.everyoneJoinLater :
|
|
382
|
+
title: principalLabel(principal, t),
|
|
383
|
+
description: everyone ? t.everyoneJoinLater : aboutOf(principal, t),
|
|
296
384
|
aside: roleBadges,
|
|
297
385
|
narrowAside: /* @__PURE__ */ jsx("span", {
|
|
298
386
|
className: "mt-1 flex flex-wrap gap-1",
|
|
299
387
|
children: roleBadges
|
|
300
388
|
}),
|
|
301
|
-
children: sameRemoval(actions.removal,
|
|
389
|
+
children: sameRemoval(actions.removal, principalId) && actions.removal ? /* @__PURE__ */ jsx(RemoveConfirm, {
|
|
302
390
|
model,
|
|
303
391
|
removal: actions.removal,
|
|
304
392
|
saving: actions.saving.size > 0,
|
|
@@ -306,7 +394,15 @@ function ByGroup({ model, shown, order, expanded, onExpandedChange, actions, t,
|
|
|
306
394
|
onConfirm: () => actions.removal && actions.onRemove(actions.removal),
|
|
307
395
|
t
|
|
308
396
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
309
|
-
|
|
397
|
+
owner && /* @__PURE__ */ jsx(Row, {
|
|
398
|
+
title: /* @__PURE__ */ jsx("span", {
|
|
399
|
+
className: "font-medium",
|
|
400
|
+
children: t.owner
|
|
401
|
+
}),
|
|
402
|
+
meta: t.ownerDescription,
|
|
403
|
+
action: /* @__PURE__ */ jsx(Locked, {})
|
|
404
|
+
}),
|
|
405
|
+
roles.map((role) => sameRemoval(actions.removal, principalId, role.id) && actions.removal ? /* @__PURE__ */ jsx(RemoveConfirm, {
|
|
310
406
|
model,
|
|
311
407
|
removal: actions.removal,
|
|
312
408
|
saving: actions.saving.size > 0,
|
|
@@ -315,35 +411,43 @@ function ByGroup({ model, shown, order, expanded, onExpandedChange, actions, t,
|
|
|
315
411
|
t
|
|
316
412
|
}, role.id) : /* @__PURE__ */ jsx(RoleRow, {
|
|
317
413
|
model,
|
|
318
|
-
|
|
414
|
+
principal,
|
|
319
415
|
role,
|
|
320
416
|
actions,
|
|
321
417
|
t
|
|
322
418
|
}, role.id)),
|
|
323
|
-
inherited.map((role) => /* @__PURE__ */ jsx(Row, {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
419
|
+
inherited.map(({ role, from }) => /* @__PURE__ */ jsx(Row, {
|
|
420
|
+
title: /* @__PURE__ */ jsx("span", {
|
|
421
|
+
className: "font-medium",
|
|
422
|
+
children: role.name
|
|
423
|
+
}),
|
|
424
|
+
meta: t.inheritedFrom(from),
|
|
425
|
+
action: /* @__PURE__ */ jsx(Locked, {})
|
|
327
426
|
}, role.id)),
|
|
328
|
-
|
|
427
|
+
through.map((holding) => /* @__PURE__ */ jsx(Row, {
|
|
428
|
+
muted: true,
|
|
429
|
+
title: holding.role.name,
|
|
430
|
+
meta: holding.through?.kind === "everyone" ? t.throughEveryone : t.through(sourceOf(holding))
|
|
431
|
+
}, holding.role.id)),
|
|
432
|
+
!actions.readOnly && (!owner || held.length > 0) && /* @__PURE__ */ jsxs("div", {
|
|
329
433
|
className: "flex flex-wrap items-center gap-2 ps-11 pe-4 pt-2",
|
|
330
|
-
children: [/* @__PURE__ */ jsx(Button, {
|
|
434
|
+
children: [!owner && /* @__PURE__ */ jsx(Button, {
|
|
331
435
|
variant: "secondary",
|
|
332
436
|
size: "sm",
|
|
333
437
|
icon: /* @__PURE__ */ jsx(AddIcon, {}),
|
|
334
|
-
onClick: () => actions.onGrantMore(
|
|
438
|
+
onClick: () => actions.onGrantMore(principalId),
|
|
335
439
|
children: t.grantMoreRoles
|
|
336
|
-
}), /* @__PURE__ */ jsx(Button, {
|
|
440
|
+
}), held.length > 0 && /* @__PURE__ */ jsx(Button, {
|
|
337
441
|
variant: "ghost",
|
|
338
442
|
size: "sm",
|
|
339
443
|
"data-remove": removeKey({
|
|
340
|
-
|
|
444
|
+
principalId,
|
|
341
445
|
roleIds: [],
|
|
342
446
|
all: true
|
|
343
447
|
}),
|
|
344
448
|
className: "text-(--theme-error-text)",
|
|
345
449
|
onClick: () => actions.onAskRemove({
|
|
346
|
-
|
|
450
|
+
principalId,
|
|
347
451
|
roleIds: held.map((role) => role.id),
|
|
348
452
|
all: true
|
|
349
453
|
}),
|
|
@@ -351,26 +455,26 @@ function ByGroup({ model, shown, order, expanded, onExpandedChange, actions, t,
|
|
|
351
455
|
})]
|
|
352
456
|
})
|
|
353
457
|
] })
|
|
354
|
-
},
|
|
458
|
+
}, principalId);
|
|
355
459
|
});
|
|
356
460
|
}
|
|
357
|
-
function RoleRow({ model,
|
|
358
|
-
const overlap = overlapOf(model,
|
|
359
|
-
const meta = overlap.coveredBy.length > 0 ? t.redundant(overlap.coveredBy.map((other) => other.name)) : role.description;
|
|
461
|
+
function RoleRow({ model, principal, role, actions, t }) {
|
|
462
|
+
const overlap = overlapOf(model, principal.id, role);
|
|
463
|
+
const meta = overlap.owner ? t.redundantOwner : overlap.coveredBy.length > 0 ? t.redundant(overlap.coveredBy.map((other) => other.name)) : role.description;
|
|
360
464
|
return /* @__PURE__ */ jsx(Row, {
|
|
361
|
-
title: /* @__PURE__ */
|
|
465
|
+
title: /* @__PURE__ */ jsx("span", {
|
|
362
466
|
className: "font-medium",
|
|
363
467
|
children: role.name
|
|
364
|
-
}),
|
|
468
|
+
}),
|
|
365
469
|
meta,
|
|
366
470
|
action: !actions.readOnly && /* @__PURE__ */ jsx(RemoveButton, {
|
|
367
|
-
label: t.removeRole(
|
|
471
|
+
label: t.removeRole(principalLabel(principal, t), role.name),
|
|
368
472
|
saving: actions.saving.has(key({
|
|
369
|
-
|
|
473
|
+
principalId: principal.id,
|
|
370
474
|
roleId: role.id
|
|
371
475
|
})),
|
|
372
476
|
removal: {
|
|
373
|
-
|
|
477
|
+
principalId: principal.id,
|
|
374
478
|
roleIds: [role.id],
|
|
375
479
|
all: false
|
|
376
480
|
},
|
|
@@ -379,4 +483,4 @@ function RoleRow({ model, group, role, actions, t }) {
|
|
|
379
483
|
});
|
|
380
484
|
}
|
|
381
485
|
//#endregion
|
|
382
|
-
export {
|
|
486
|
+
export { ByPrincipal, ByRole, OWNER_SECTION, RemoveConfirm, principalLabel, removeKey };
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type DescribeGroup, type DetailsCache, type SearchGroups } from './GroupPicker.js';
|
|
3
2
|
import { type AccessModel } from './model.js';
|
|
3
|
+
import { type DescribeGroup, type DetailsCache, type SearchPrincipals } from './PrincipalPicker.js';
|
|
4
4
|
import type { AccessStrings } from './strings.js';
|
|
5
|
-
import type { AccessChange,
|
|
5
|
+
import type { AccessChange, AccessPrincipal } from './types.js';
|
|
6
6
|
export interface GrantSelection {
|
|
7
|
-
|
|
7
|
+
principalIds: string[];
|
|
8
8
|
roleIds: string[];
|
|
9
9
|
}
|
|
10
10
|
/** The grant form's body and footer, which the drawer lays out in its own body and footer. */
|
|
11
|
-
export declare function useGrantView({ model, selection, onSelectionChange, resourceName, describeGroup,
|
|
11
|
+
export declare function useGrantView({ model, selection, onSelectionChange, resourceName, describeGroup, searchPrincipals, groupCount, peopleCount, recentPrincipalIds, cache, seen, saving, failure, onGrant, onCancel, focusLater, boundary, t, }: {
|
|
12
12
|
model: AccessModel;
|
|
13
13
|
selection: GrantSelection;
|
|
14
14
|
onSelectionChange: (next: GrantSelection) => void;
|
|
15
15
|
resourceName: string;
|
|
16
16
|
describeGroup?: DescribeGroup | undefined;
|
|
17
|
-
|
|
17
|
+
searchPrincipals?: SearchPrincipals | undefined;
|
|
18
18
|
groupCount?: number | undefined;
|
|
19
|
-
|
|
19
|
+
peopleCount?: number | undefined;
|
|
20
|
+
recentPrincipalIds?: readonly string[] | undefined;
|
|
20
21
|
cache: DetailsCache;
|
|
21
|
-
seen: Map<string,
|
|
22
|
+
seen: Map<string, AccessPrincipal>;
|
|
22
23
|
saving: boolean;
|
|
23
24
|
/** The failed grant's callout, which stays in the form so the choices can be retried. */
|
|
24
25
|
failure: ReactNode;
|
|
@@ -26,7 +27,7 @@ export declare function useGrantView({ model, selection, onSelectionChange, reso
|
|
|
26
27
|
onCancel: () => void;
|
|
27
28
|
/** Sends focus somewhere once the change it goes with has rendered. */
|
|
28
29
|
focusLater: (find: () => HTMLElement | null | undefined) => void;
|
|
29
|
-
/** The drawer's body, above its footer, which the open
|
|
30
|
+
/** The drawer's body, above its footer, which the open list fills. */
|
|
30
31
|
boundary: Element | null;
|
|
31
32
|
t: AccessStrings;
|
|
32
33
|
}): {
|