@gooddata/sdk-ui-ext 11.52.0-alpha.6 → 11.52.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.
Files changed (125) hide show
  1. package/esm/dataLoaders/InsightDataLoader.d.ts +6 -0
  2. package/esm/dataLoaders/InsightDataLoader.d.ts.map +1 -1
  3. package/esm/dataLoaders/InsightDataLoader.js +4 -1
  4. package/esm/dataLoaders/index.d.ts +13 -0
  5. package/esm/dataLoaders/index.d.ts.map +1 -1
  6. package/esm/dataLoaders/index.js +14 -0
  7. package/esm/index.d.ts +1 -1
  8. package/esm/index.d.ts.map +1 -1
  9. package/esm/index.js +1 -1
  10. package/esm/insightView/InsightRenderer.d.ts +2 -5
  11. package/esm/insightView/InsightRenderer.d.ts.map +1 -1
  12. package/esm/insightView/InsightRenderer.js +1 -2
  13. package/esm/internal/components/pluggableVisualizations/mekko/PluggableMekko.d.ts +6 -1
  14. package/esm/internal/components/pluggableVisualizations/mekko/PluggableMekko.d.ts.map +1 -1
  15. package/esm/internal/components/pluggableVisualizations/mekko/PluggableMekko.js +25 -4
  16. package/esm/internal/components/pluggableVisualizations/pivotTable/PluggablePivotTable.d.ts.map +1 -1
  17. package/esm/internal/components/pluggableVisualizations/pivotTable/PluggablePivotTable.js +0 -1
  18. package/esm/internal/translations/de-DE.localization-bundle.d.ts +2 -0
  19. package/esm/internal/translations/de-DE.localization-bundle.d.ts.map +1 -1
  20. package/esm/internal/translations/de-DE.localization-bundle.js +2 -0
  21. package/esm/internal/translations/en-AU.localization-bundle.d.ts +2 -0
  22. package/esm/internal/translations/en-AU.localization-bundle.d.ts.map +1 -1
  23. package/esm/internal/translations/en-AU.localization-bundle.js +2 -0
  24. package/esm/internal/translations/en-GB.localization-bundle.d.ts +2 -0
  25. package/esm/internal/translations/en-GB.localization-bundle.d.ts.map +1 -1
  26. package/esm/internal/translations/en-GB.localization-bundle.js +2 -0
  27. package/esm/internal/translations/en-US.localization-bundle.d.ts +4 -0
  28. package/esm/internal/translations/en-US.localization-bundle.d.ts.map +1 -1
  29. package/esm/internal/translations/en-US.localization-bundle.js +4 -0
  30. package/esm/internal/translations/es-419.localization-bundle.d.ts +2 -0
  31. package/esm/internal/translations/es-419.localization-bundle.d.ts.map +1 -1
  32. package/esm/internal/translations/es-419.localization-bundle.js +2 -0
  33. package/esm/internal/translations/es-ES.localization-bundle.d.ts +2 -0
  34. package/esm/internal/translations/es-ES.localization-bundle.d.ts.map +1 -1
  35. package/esm/internal/translations/es-ES.localization-bundle.js +2 -0
  36. package/esm/internal/translations/fi-FI.localization-bundle.d.ts +2 -0
  37. package/esm/internal/translations/fi-FI.localization-bundle.d.ts.map +1 -1
  38. package/esm/internal/translations/fi-FI.localization-bundle.js +2 -0
  39. package/esm/internal/translations/fr-CA.localization-bundle.d.ts +2 -0
  40. package/esm/internal/translations/fr-CA.localization-bundle.d.ts.map +1 -1
  41. package/esm/internal/translations/fr-CA.localization-bundle.js +2 -0
  42. package/esm/internal/translations/fr-FR.localization-bundle.d.ts +2 -0
  43. package/esm/internal/translations/fr-FR.localization-bundle.d.ts.map +1 -1
  44. package/esm/internal/translations/fr-FR.localization-bundle.js +2 -0
  45. package/esm/internal/translations/id-ID.localization-bundle.d.ts +2 -0
  46. package/esm/internal/translations/id-ID.localization-bundle.d.ts.map +1 -1
  47. package/esm/internal/translations/id-ID.localization-bundle.js +2 -0
  48. package/esm/internal/translations/it-IT.localization-bundle.d.ts +2 -0
  49. package/esm/internal/translations/it-IT.localization-bundle.d.ts.map +1 -1
  50. package/esm/internal/translations/it-IT.localization-bundle.js +2 -0
  51. package/esm/internal/translations/ja-JP.localization-bundle.d.ts +2 -0
  52. package/esm/internal/translations/ja-JP.localization-bundle.d.ts.map +1 -1
  53. package/esm/internal/translations/ja-JP.localization-bundle.js +2 -0
  54. package/esm/internal/translations/ko-KR.localization-bundle.d.ts +2 -0
  55. package/esm/internal/translations/ko-KR.localization-bundle.d.ts.map +1 -1
  56. package/esm/internal/translations/ko-KR.localization-bundle.js +2 -0
  57. package/esm/internal/translations/nl-NL.localization-bundle.d.ts +2 -0
  58. package/esm/internal/translations/nl-NL.localization-bundle.d.ts.map +1 -1
  59. package/esm/internal/translations/nl-NL.localization-bundle.js +2 -0
  60. package/esm/internal/translations/pl-PL.localization-bundle.d.ts +2 -0
  61. package/esm/internal/translations/pl-PL.localization-bundle.d.ts.map +1 -1
  62. package/esm/internal/translations/pl-PL.localization-bundle.js +2 -0
  63. package/esm/internal/translations/pt-BR.localization-bundle.d.ts +2 -0
  64. package/esm/internal/translations/pt-BR.localization-bundle.d.ts.map +1 -1
  65. package/esm/internal/translations/pt-BR.localization-bundle.js +2 -0
  66. package/esm/internal/translations/pt-PT.localization-bundle.d.ts +2 -0
  67. package/esm/internal/translations/pt-PT.localization-bundle.d.ts.map +1 -1
  68. package/esm/internal/translations/pt-PT.localization-bundle.js +2 -0
  69. package/esm/internal/translations/ru-RU.localization-bundle.d.ts +2 -0
  70. package/esm/internal/translations/ru-RU.localization-bundle.d.ts.map +1 -1
  71. package/esm/internal/translations/ru-RU.localization-bundle.js +2 -0
  72. package/esm/internal/translations/sl-SI.localization-bundle.d.ts +2 -0
  73. package/esm/internal/translations/sl-SI.localization-bundle.d.ts.map +1 -1
  74. package/esm/internal/translations/sl-SI.localization-bundle.js +2 -0
  75. package/esm/internal/translations/th-TH.localization-bundle.d.ts +2 -0
  76. package/esm/internal/translations/th-TH.localization-bundle.d.ts.map +1 -1
  77. package/esm/internal/translations/th-TH.localization-bundle.js +2 -0
  78. package/esm/internal/translations/tr-TR.localization-bundle.d.ts +2 -0
  79. package/esm/internal/translations/tr-TR.localization-bundle.d.ts.map +1 -1
  80. package/esm/internal/translations/tr-TR.localization-bundle.js +2 -0
  81. package/esm/internal/translations/uk-UA.localization-bundle.d.ts +2 -0
  82. package/esm/internal/translations/uk-UA.localization-bundle.d.ts.map +1 -1
  83. package/esm/internal/translations/uk-UA.localization-bundle.js +2 -0
  84. package/esm/internal/translations/vi-VN.localization-bundle.d.ts +2 -0
  85. package/esm/internal/translations/vi-VN.localization-bundle.d.ts.map +1 -1
  86. package/esm/internal/translations/vi-VN.localization-bundle.js +2 -0
  87. package/esm/internal/translations/zh-HK.localization-bundle.d.ts +2 -0
  88. package/esm/internal/translations/zh-HK.localization-bundle.d.ts.map +1 -1
  89. package/esm/internal/translations/zh-HK.localization-bundle.js +2 -0
  90. package/esm/internal/translations/zh-Hans.localization-bundle.d.ts +2 -0
  91. package/esm/internal/translations/zh-Hans.localization-bundle.d.ts.map +1 -1
  92. package/esm/internal/translations/zh-Hans.localization-bundle.js +2 -0
  93. package/esm/internal/translations/zh-Hant.localization-bundle.d.ts +2 -0
  94. package/esm/internal/translations/zh-Hant.localization-bundle.d.ts.map +1 -1
  95. package/esm/internal/translations/zh-Hant.localization-bundle.js +2 -0
  96. package/esm/notificationsPanel/Notification/Notification.d.ts.map +1 -1
  97. package/esm/notificationsPanel/Notification/Notification.js +6 -3
  98. package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetail.d.ts +3 -3
  99. package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetail.d.ts.map +1 -1
  100. package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetail.js +4 -4
  101. package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetailDialog.d.ts +9 -3
  102. package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetailDialog.d.ts.map +1 -1
  103. package/esm/notificationsPanel/NotificationFiltersDetail/NotificationFiltersDetailDialog.js +4 -4
  104. package/esm/sdk-ui-ext.d.ts +13 -0
  105. package/esm/share/ObjectShareDialog.js +22 -6
  106. package/esm/share/messages.d.ts +7 -0
  107. package/esm/share/messages.d.ts.map +1 -1
  108. package/esm/share/messages.js +5 -0
  109. package/esm/share/objectShareController.helpers.d.ts +117 -17
  110. package/esm/share/objectShareController.helpers.d.ts.map +1 -1
  111. package/esm/share/objectShareController.helpers.js +181 -44
  112. package/esm/share/objectShareController.types.d.ts +34 -10
  113. package/esm/share/objectShareController.types.d.ts.map +1 -1
  114. package/esm/share/useAccessList.d.ts +6 -0
  115. package/esm/share/useAccessList.d.ts.map +1 -1
  116. package/esm/share/useAccessList.js +26 -6
  117. package/esm/share/useLabelScope.d.ts +46 -1
  118. package/esm/share/useLabelScope.d.ts.map +1 -1
  119. package/esm/share/useLabelScope.js +98 -6
  120. package/esm/share/useObjectShareController.d.ts.map +1 -1
  121. package/esm/share/useObjectShareController.js +213 -45
  122. package/esm/share/useObjectShareDialog.d.ts +7 -0
  123. package/esm/share/useObjectShareDialog.d.ts.map +1 -1
  124. package/esm/share/useObjectShareDialog.js +22 -2
  125. package/package.json +21 -21
@@ -115,25 +115,117 @@ export function levelsAbove(level) {
115
115
  export function levelsBelow(level) {
116
116
  return LEVELS_STRONGEST_FIRST.filter((l) => LEVEL_RANK[l] < LEVEL_RANK[level]);
117
117
  }
118
+ /** The stronger of two levels; undefined only when both are. */
119
+ export function maxLevel(a, b) {
120
+ if (a === undefined) {
121
+ return b;
122
+ }
123
+ if (b === undefined) {
124
+ return a;
125
+ }
126
+ return LEVEL_RANK[a] >= LEVEL_RANK[b] ? a : b;
127
+ }
118
128
  /**
119
- * The effective permission to surface as a warning, or undefined when the direct
120
- * grant already covers it. Set only when the grantee *inherits* a level (e.g. via
121
- * a group) above the directly-granted one i.e. the effective access is higher
122
- * than what the row's permission control shows.
129
+ * The inherited permission to surface as a warning, or undefined when the direct
130
+ * grant already covers it. Set when the grantee *inherits* a level (via a group or
131
+ * a parent workspace) that the direct grant does not reach including the
132
+ * inherited-only case (`direct` undefined), where the whole displayed level comes
133
+ * from inheritance.
123
134
  */
124
135
  export function effectivePermissionAbove(direct, inheritedLevel) {
125
- return inheritedLevel && LEVEL_RANK[inheritedLevel] > LEVEL_RANK[direct] ? inheritedLevel : undefined;
136
+ if (!inheritedLevel) {
137
+ return undefined;
138
+ }
139
+ return direct === undefined || LEVEL_RANK[inheritedLevel] > LEVEL_RANK[direct]
140
+ ? inheritedLevel
141
+ : undefined;
126
142
  }
127
- /** The permission-derived fields shared by every grantee row, regardless of kind. */
143
+ /**
144
+ * The permission-derived fields of a grantee row, given the raw direct and
145
+ * inherited permission sets.
146
+ *
147
+ * The displayed `level` is the EFFECTIVE one — the stronger of the direct grant and
148
+ * anything inherited (from a group, or from a parent workspace in a hierarchy) —
149
+ * so a grantee who only inherits EDIT reads as "Can edit & share" rather than
150
+ * falling back to VIEW. `directLevel` keeps what THIS workspace grants (undefined
151
+ * when the access is inherited-only): it is what writes re-grade and what decides
152
+ * whether there is anything here to remove.
153
+ */
128
154
  function granteeAccess(permissions, inheritedPermissions) {
129
- const level = directLevel(permissions);
155
+ const direct = strongestLevel(permissions);
130
156
  const inheritedLevel = strongestLevel(inheritedPermissions);
157
+ // A listed grantee with NEITHER set is a revoked-but-still-listed entry. VIEW is the
158
+ // historical placeholder for its level, and it counts as the DIRECT one: leaving
159
+ // `directLevel` undefined would classify the row as inherited-only, disabling Remove
160
+ // and claiming inherited access that does not exist.
161
+ const directLevel = direct ?? (inheritedLevel === undefined ? "VIEW" : undefined);
131
162
  return {
132
- level,
133
- effectivePermission: effectivePermissionAbove(level, inheritedLevel),
163
+ level: maxLevel(directLevel, inheritedLevel) ?? "VIEW",
164
+ directLevel,
165
+ effectivePermission: effectivePermissionAbove(direct, inheritedLevel),
134
166
  inheritedLevel,
135
167
  };
136
168
  }
169
+ /**
170
+ * Whether picking `level` for this grantee would change the level they EFFECTIVELY hold.
171
+ *
172
+ * The permission menu shows the effective level, so a pick is only meaningful if it moves
173
+ * that: under an inherited EDIT, picking SHARE or VIEW cannot lower anything, it would just
174
+ * rewrite the grant made here where nobody can see it. Conversely a direct EDIT under an
175
+ * inherited SHARE CAN be lowered to VIEW, because the effective level then drops to SHARE.
176
+ *
177
+ * The single source of this rule. Everything that reacts to a pick must agree: the
178
+ * controller refuses a pick that changes nothing, so any confirm staged for one would ask
179
+ * the user to approve a change that then silently does not happen.
180
+ *
181
+ * @internal
182
+ */
183
+ export function changesEffectiveLevel(grantee, level) {
184
+ return maxLevel(level, grantee.inheritedLevel) !== grantee.level;
185
+ }
186
+ /**
187
+ * Whether revoking this grantee's direct grant would change the level they EFFECTIVELY
188
+ * hold. False when inheritance alone already covers the displayed level: the revoke still
189
+ * removes the local grant, but restricts nothing the grantee can do — so no
190
+ * "Restrict your access?" confirm may be staged for it ({@link changesEffectiveLevel}'s
191
+ * rule, applied to removal).
192
+ *
193
+ * @internal
194
+ */
195
+ export function removalChangesEffectiveLevel(grantee) {
196
+ return grantee.inheritedLevel !== grantee.level;
197
+ }
198
+ /**
199
+ * The row fields to apply when THIS workspace's direct grant moves to `direct`:
200
+ * the displayed level recomposes against whatever is inherited, so a re-grade below
201
+ * an inherited level cannot make the row understate the grantee's real access.
202
+ *
203
+ * @internal
204
+ */
205
+ export function withDirectLevel(grantee, direct) {
206
+ return {
207
+ ...grantee,
208
+ level: maxLevel(direct, grantee.inheritedLevel) ?? direct,
209
+ directLevel: direct,
210
+ effectivePermission: effectivePermissionAbove(direct, grantee.inheritedLevel),
211
+ };
212
+ }
213
+ /**
214
+ * The row a grantee falls back to once their direct grant is revoked but inherited
215
+ * access remains — the row survives the removal as inherited-only rather than
216
+ * disappearing and reappearing on the next load.
217
+ *
218
+ * @internal
219
+ */
220
+ export function withoutDirectGrant(grantee, inheritedLevel) {
221
+ return {
222
+ ...grantee,
223
+ level: inheritedLevel,
224
+ directLevel: undefined,
225
+ effectivePermission: inheritedLevel,
226
+ pending: undefined,
227
+ };
228
+ }
137
229
  export function granteesFromAccessList(list) {
138
230
  if (!list) {
139
231
  return [];
@@ -185,11 +277,7 @@ export function mergeGrantees(base, edits, selfId, selfIdentity) {
185
277
  return settled.grantee;
186
278
  }
187
279
  if (g && settled?.kind === "level") {
188
- return {
189
- ...g,
190
- level: settled.level,
191
- effectivePermission: effectivePermissionAbove(settled.level, g.inheritedLevel),
192
- };
280
+ return withDirectLevel(g, settled.level);
193
281
  }
194
282
  return g;
195
283
  };
@@ -203,19 +291,29 @@ export function mergeGrantees(base, edits, selfId, selfIdentity) {
203
291
  }
204
292
  if (edit.kind === "removed") {
205
293
  // Visible (muted, at its last committed state) only while the revoke is
206
- // in flight; a settled removal renders nothing — its entry persists to
207
- // keep the base row hidden.
294
+ // in flight.
208
295
  const committed = edit.pending ? committedRow(g, edit.settled) : undefined;
209
- return committed ? { ...committed, pending: "removing" } : undefined;
296
+ if (committed) {
297
+ return { ...committed, pending: "removing" };
298
+ }
299
+ // A settled removal revokes only what THIS workspace granted. When the
300
+ // grantee still inherits access (a group, a parent workspace), the row
301
+ // survives as inherited-only — dropping it would claim the removal took
302
+ // away access it cannot reach. With nothing inherited the row is gone and
303
+ // the entry persists to keep the base row hidden.
304
+ return g?.inheritedLevel ? withoutDirectGrant(g, g.inheritedLevel) : undefined;
305
+ }
306
+ if (edit.kind === "locked") {
307
+ // Access unchanged: render whatever the row had COMMITTED and add the saving
308
+ // marker. Rendering the fetched row instead would undo the committed state
309
+ // for the duration of the write — a removal survivor would reappear with the
310
+ // direct grant it just lost.
311
+ const committed = edit.settled ? renderEntry(g, { ...edit.settled, pending: false }) : g;
312
+ return committed ? { ...committed, pending: edit.pending ? "saving" : undefined } : undefined;
210
313
  }
211
314
  if (edit.kind === "level") {
212
315
  return g
213
- ? {
214
- ...g,
215
- level: edit.level,
216
- effectivePermission: effectivePermissionAbove(edit.level, g.inheritedLevel),
217
- pending: edit.pending ? "saving" : undefined,
218
- }
316
+ ? { ...withDirectLevel(g, edit.level), pending: edit.pending ? "saving" : undefined }
219
317
  : undefined;
220
318
  }
221
319
  return { ...edit.grantee, pending: edit.pending ? "saving" : undefined };
@@ -275,21 +373,51 @@ export function buildLabelMutations(principal, desiredLabelIds, currentLabelIds,
275
373
  if (wanted === had) {
276
374
  continue;
277
375
  }
278
- writes.push({ ref: label.ref, grantee: granularGranteeFor(principal, wanted ? "VIEW" : "none") });
376
+ writes.push({
377
+ ref: label.ref,
378
+ grantee: granularGranteeFor(principal, wanted ? (principal.level ?? "VIEW") : "none"),
379
+ });
380
+ }
381
+ return writes;
382
+ }
383
+ /**
384
+ * Pure: the per-label writes that re-grade `principal`'s EXISTING label scope to
385
+ * `principal.level`. Unlike {@link buildLabelMutations} this is not a set diff —
386
+ * the scope is unchanged and every in-scope label is rewritten — so it is what a
387
+ * permission-level change on the object needs to keep its labels in step. `labels` is
388
+ * the permissionable set, so every write targets a label that can take a grant.
389
+ *
390
+ * The primary label is deliberately EXCLUDED. Its access is implicit — {@link
391
+ * buildLabelMutations} never grants or revokes it — so a grant written here would be
392
+ * one nothing can take away: a later removal diffs the primary as unchanged and leaves
393
+ * the grantee holding the primary label after their object access is gone.
394
+ */
395
+ export function buildLabelRegrades(principals, scopeLabelIds, labels) {
396
+ const writes = [];
397
+ for (const label of labels) {
398
+ if (label.isPrimary || !scopeLabelIds.has(label.id)) {
399
+ continue;
400
+ }
401
+ writes.push({
402
+ id: label.id,
403
+ ref: label.ref,
404
+ grantees: principals.map((p) => granularGranteeFor(p, p.level ?? "VIEW")),
405
+ });
279
406
  }
280
407
  return writes;
281
408
  }
282
409
  /**
283
- * Multi-principal variant of {@link buildLabelMutations}: groups the per-label
284
- * writes so each label is one write carrying every principal that changes on it.
285
- * Keys on `label.id`, not the raw `ObjRef` — a Map keyed on ObjRef would key on
286
- * object identity and fail to merge equal-but-distinct refs.
410
+ * Multi-principal variant of {@link buildLabelMutations}, each principal moving its
411
+ * own scope: groups the per-label writes so each label is one write carrying every
412
+ * principal that changes on it. Keys on `label.id`, not the raw `ObjRef` — a Map
413
+ * keyed on ObjRef would key on object identity and fail to merge
414
+ * equal-but-distinct refs.
287
415
  */
288
- export function buildLabelMutationsForPrincipals(principals, desiredLabelIds, currentLabelIds, labels) {
416
+ export function buildLabelMutationsForScopes(changes, labels) {
289
417
  const byLabel = new Map();
290
418
  for (const label of labels) {
291
- for (const principal of principals) {
292
- const writes = buildLabelMutations(principal, desiredLabelIds, currentLabelIds, [label]);
419
+ for (const change of changes) {
420
+ const writes = buildLabelMutations(change.principal, change.desiredLabelIds, change.currentLabelIds, [label]);
293
421
  if (writes.length === 0) {
294
422
  continue;
295
423
  }
@@ -305,20 +433,29 @@ export const NO_LABELS = [];
305
433
  /** Shared empty id-set for "no labels in scope" diffs. */
306
434
  export const EMPTY_IDS = new Set();
307
435
  /**
308
- * Whether a fetched grant for `granteeId` exists with a non-empty permission set.
309
- * Inspects the raw grants rather than `granteesFromAccessList`, which normalizes
310
- * every listed user/group to a VIEW level even when its `permissions` are empty —
311
- * so a revoked-but-still-listed entry would otherwise read as granted, mis-scoping
312
- * a per-label checkbox to a label the grantee can't actually access.
436
+ * Where `granteeId`'s access to this object comes from, or `undefined` when they hold it
437
+ * at all. The two flags are INDEPENDENT: a grantee can hold a grant made here *and*
438
+ * inherit one from a group or a parent workspace, and both facts matter — `direct` says
439
+ * there is something here to revoke, `inherited` says access survives revoking it.
440
+ * Collapsing them to a single winner is what made a dual-granted label read as
441
+ * inaccessible once its local grant was revoked.
442
+ *
443
+ * Inspects the raw grants rather than `granteesFromAccessList`, which normalizes every
444
+ * listed user/group to a VIEW level even when it carries no permissions — so a
445
+ * revoked-but-still-listed entry would otherwise read as granted, mis-scoping a per-label
446
+ * checkbox to a label the grantee can't actually access.
313
447
  */
314
- export function isGranteeGrantedIn(list, id) {
315
- return list.grants.some((g) => {
316
- if (isGranularUserAccess(g)) {
317
- return id === granteeId("user", g.user.ref) && g.permissions.length > 0;
448
+ export function granteeGrantIn(list, id) {
449
+ const sourceOf = (permissions, inherited) => permissions.length > 0 || inherited.length > 0
450
+ ? { direct: permissions.length > 0, inherited: inherited.length > 0 }
451
+ : undefined;
452
+ for (const g of list.grants) {
453
+ if (isGranularUserAccess(g) && id === granteeId("user", g.user.ref)) {
454
+ return sourceOf(g.permissions, g.inheritedPermissions);
318
455
  }
319
- if (isGranularUserGroupAccess(g)) {
320
- return id === granteeId("group", g.userGroup.ref) && g.permissions.length > 0;
456
+ if (isGranularUserGroupAccess(g) && id === granteeId("group", g.userGroup.ref)) {
457
+ return sourceOf(g.permissions, g.inheritedPermissions);
321
458
  }
322
- return false;
323
- });
459
+ }
460
+ return undefined;
324
461
  }
@@ -46,18 +46,31 @@ export interface IObjectShareGrantee extends IGranteeIdentityFacts {
46
46
  * profile, so it holds for rows of any origin (fetched or optimistic).
47
47
  */
48
48
  isSelf?: boolean;
49
+ /**
50
+ * EFFECTIVE permission level shown on the row — the stronger of {@link directLevel}
51
+ * and {@link inheritedLevel}, so a grantee who only inherits a level is not
52
+ * understated as VIEW.
53
+ */
49
54
  level: ObjectSharePermissionLevel;
50
55
  /**
51
- * Effective permission when it is *higher* than the directly-granted `level`
52
- * because the grantee inherits a stronger permission (e.g. via a group).
53
- * Undefined when the direct grant already is the effective permission. Drives
54
- * the row's "effective permission" warning badge.
56
+ * Permission level granted by THIS workspace, or undefined when the grantee's
57
+ * access is inherited-only (a group, or a parent workspace in a hierarchy).
58
+ * This not {@link level} is what a re-grade writes and what decides whether
59
+ * there is a grant here to remove: inherited access cannot be revoked from here.
60
+ */
61
+ directLevel?: ObjectSharePermissionLevel;
62
+ /**
63
+ * Inherited permission to surface as a warning: set when inheritance carries the
64
+ * grantee above their direct grant (including inherited-only, where the whole
65
+ * displayed level is inherited). Undefined when the direct grant already covers
66
+ * the effective permission. Drives the row's warning badge.
55
67
  */
56
68
  effectivePermission?: ObjectSharePermissionLevel;
57
69
  /**
58
- * Strongest permission the grantee inherits (e.g. via a group), regardless of
59
- * the current direct `level`. Retained from the fetch so `effectivePermission`
60
- * can be recomputed locally when the direct level changes (no refetch).
70
+ * Strongest permission the grantee inherits (via a group, or a parent workspace),
71
+ * regardless of the current direct level. Retained from the fetch so the
72
+ * displayed level and badge can be recomposed locally when the direct level
73
+ * changes (no refetch).
61
74
  */
62
75
  inheritedLevel?: ObjectSharePermissionLevel;
63
76
  /**
@@ -164,11 +177,22 @@ export interface IObjectShareControllerState {
164
177
  */
165
178
  labelsInitializing: boolean;
166
179
  /**
167
- * Per-grantee label scope: grantee id → the label ids that grantee can access.
168
- * The primary label is always included. Empty entry means "all labels" has not
169
- * yet been resolved; consumers should treat a missing entry as all-selected.
180
+ * Per-grantee label scope: grantee id → the label ids that grantee can access,
181
+ * whether granted here or inherited. The primary label is always included.
182
+ *
183
+ * A MISSING entry means the scope is not resolved for that grantee — only then may a
184
+ * consumer fall back to all labels, and only once {@link labelsResolved}. An entry
185
+ * that is an EMPTY array is a resolved answer: no non-primary label is in scope.
170
186
  */
171
187
  selectedLabelIdsByGrantee: Record<string, string[]>;
188
+ /**
189
+ * The subset of {@link selectedLabelIdsByGrantee} a grantee only INHERITS (from a
190
+ * group, or a parent workspace) with no grant on the label in this workspace.
191
+ * Those labels are in scope but cannot be re-scoped here: there is nothing local
192
+ * to revoke, so consumers render them locked. Same rule as a grantee row whose
193
+ * object access is inherited-only, which cannot be removed either.
194
+ */
195
+ inheritedLabelIdsByGrantee: Record<string, string[]>;
172
196
  /** Pending confirm — when set, the confirm dialog is visible. */
173
197
  pendingGeneralAccess?: GeneralAccessValue;
174
198
  /** Grantees staged in the add-grantee dialog before confirmation. */
@@ -1 +1 @@
1
- {"version":3,"file":"objectShareController.types.d.ts","sourceRoot":"","sources":["../../src/share/objectShareController.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"objectShareController.types.d.ts","sourceRoot":"","sources":["../../src/share/objectShareController.types.ts"],"names":[],"mappings":"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"}
@@ -65,6 +65,12 @@ export interface IAccessList {
65
65
  * plain level entry would drop the row from the merged view.
66
66
  */
67
67
  applyGranteeLevel: (id: string, level: ObjectSharePermissionLevel) => void;
68
+ /**
69
+ * Lock a row for a write that leaves its access unchanged (a label-scope edit):
70
+ * adds the pending marker only. Use this rather than re-applying the displayed
71
+ * level, which would persist the EFFECTIVE level as the row's direct grant.
72
+ */
73
+ applyGranteeLock: (id: string) => void;
68
74
  /** Overlay a pending removal; the row renders muted until the write settles. */
69
75
  applyGranteeRemove: (id: string) => void;
70
76
  /** Commit the pending edit for `id` after a successful write. */
@@ -1 +1 @@
1
- {"version":3,"file":"useAccessList.d.ts","sourceRoot":"","sources":["../../src/share/useAccessList.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EACH,KAAK,sBAAsB,EAK9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,gBAAgB,EAKxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAmB,MAAM,sBAAsB,CAAC;AAS7G,OAAO,EAEH,KAAK,SAAS,EASjB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EACR,2BAA2B,EAC3B,mBAAmB,EACnB,aAAa,EACb,0BAA0B,EAC7B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC;;;;;;OAMG;IACH,sBAAsB,EAAE,OAAO,CAAC;IAChC,6EAA6E;IAC7E,YAAY,EAAE,aAAa,GAAG,SAAS,CAAC;IACxC;;;OAGG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B,8DAA8D;IAC9D,aAAa,EAAE,kBAAkB,CAAC;IAClC,oFAAkF;IAClF,cAAc,EAAE,0BAA0B,CAAC;IAC3C;;;OAGG;IACH,uBAAuB,EAAE,0BAA0B,GAAG,SAAS,CAAC;IAChE,wFAAwF;IACxF,oBAAoB,EAAE,OAAO,CAAC;IAC9B,iFAAiF;IACjF,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,qDAAqD;IACrD,MAAM,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IAC9C,mDAAmD;IACnD,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAExC,mFAAmF;IACnF,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,EAAE,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/F,+FAA6F;IAC7F,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAEjE;;;;OAIG;IACH,eAAe,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD;;;;OAIG;IACH,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3E,gFAAgF;IAChF,kBAAkB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,iEAAiE;IACjE,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAEtC,oFAAoF;IACpF,aAAa,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,KAAK,IAAI,CAAC;IAC1D,6DAA6D;IAC7D,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,8FAA8F;IAC9F,YAAY,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,wBAAwB,GAAG,SAAS,GAAG,WAAW,CAyRvF"}
1
+ {"version":3,"file":"useAccessList.d.ts","sourceRoot":"","sources":["../../src/share/useAccessList.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAC1E,OAAO,EACH,KAAK,sBAAsB,EAK9B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,gBAAgB,EAKxB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,kBAAkB,EAAE,KAAK,sBAAsB,EAAmB,MAAM,sBAAsB,CAAC;AAS7G,OAAO,EAEH,KAAK,SAAS,EASjB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EACR,2BAA2B,EAC3B,mBAAmB,EACnB,aAAa,EACb,0BAA0B,EAC7B,MAAM,kCAAkC,CAAC;AAC1C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC;;;;;;OAMG;IACH,sBAAsB,EAAE,OAAO,CAAC;IAChC,6EAA6E;IAC7E,YAAY,EAAE,aAAa,GAAG,SAAS,CAAC;IACxC;;;OAGG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B,8DAA8D;IAC9D,aAAa,EAAE,kBAAkB,CAAC;IAClC,oFAAkF;IAClF,cAAc,EAAE,0BAA0B,CAAC;IAC3C;;;OAGG;IACH,uBAAuB,EAAE,0BAA0B,GAAG,SAAS,CAAC;IAChE,wFAAwF;IACxF,oBAAoB,EAAE,OAAO,CAAC;IAC9B,iFAAiF;IACjF,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAAC;IAC1C,qDAAqD;IACrD,MAAM,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IAC9C,mDAAmD;IACnD,SAAS,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAExC,mFAAmF;IACnF,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,EAAE,cAAc,EAAE;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/F,+FAA6F;IAC7F,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAEjE;;;;OAIG;IACH,eAAe,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACxD;;;;OAIG;IACH,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,0BAA0B,KAAK,IAAI,CAAC;IAC3E;;;;OAIG;IACH,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gFAAgF;IAChF,kBAAkB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,iEAAiE;IACjE,iBAAiB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC;;;OAGG;IACH,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAEtC,oFAAoF;IACpF,aAAa,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,KAAK,IAAI,CAAC;IAC1D,6DAA6D;IAC7D,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,8FAA8F;IAC9F,YAAY,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,wBAAwB,GAAG,SAAS,GAAG,WAAW,CA8SvF"}
@@ -5,7 +5,7 @@ import { convertError, useBackendStrict, useCancelablePromise, useWorkspaceStric
5
5
  import { useToastMessage } from "@gooddata/sdk-ui-kit";
6
6
  import { composeEffectiveWorkspaceAccess, deriveGeneralAccess, deriveInheritedWorkspaceLevel, deriveWorkspacePermissionLevel, } from "./accessSummary.js";
7
7
  import { objectShareMessages } from "./messages.js";
8
- import { assigneeIdentityFacts, assigneeMatchesQuery, effectivePermissionAbove, granteeId, granteesFromAccessList, mergeGrantees, userDisplayPair, userIdentityFacts, } from "./objectShareController.helpers.js";
8
+ import { assigneeIdentityFacts, assigneeMatchesQuery, granteeId, granteesFromAccessList, mergeGrantees, userDisplayPair, userIdentityFacts, withDirectLevel, } from "./objectShareController.helpers.js";
9
9
  /**
10
10
  * Owns the backend access list for ONE dialog session: the hook is mounted while
11
11
  * the dialog is open, for a single target that must not change while mounted
@@ -167,16 +167,25 @@ export function useAccessList(target) {
167
167
  const settled = current ? { ...current, settled: undefined } : undefined;
168
168
  if (current?.kind === "added") {
169
169
  // Overlay-born row: stay an `added` entry, or the merge would drop it.
170
- const grantee = {
171
- ...current.grantee,
172
- level,
173
- effectivePermission: effectivePermissionAbove(level, current.grantee.inheritedLevel),
174
- };
170
+ const grantee = withDirectLevel(current.grantee, level);
175
171
  return { ...prev, [id]: { kind: "added", grantee, pending: true, settled } };
176
172
  }
177
173
  return { ...prev, [id]: { kind: "level", level, pending: true, settled } };
178
174
  });
179
175
  }, []);
176
+ // Lock a row for a write that does NOT change its access (a label-scope edit). The
177
+ // lock WRAPS whatever the row committed rather than modifying it: reusing the
178
+ // existing entry and flipping `pending` would re-arm it, and `pending` means
179
+ // something different per kind — a settled removal would read as a revoke in flight
180
+ // and render the row as removing at its pre-removal level. Both settling and failing
181
+ // unwrap back to the entry underneath, because a label write changes neither.
182
+ const applyGranteeLock = useCallback((id) => {
183
+ setGranteeEdits((prev) => {
184
+ const current = prev[id];
185
+ const settled = current ? { ...current, settled: undefined } : undefined;
186
+ return { ...prev, [id]: { kind: "locked", pending: true, settled } };
187
+ });
188
+ }, []);
180
189
  const applyGranteeRemove = useCallback((id) => {
181
190
  setGranteeEdits((prev) => {
182
191
  const current = prev[id];
@@ -190,6 +199,16 @@ export function useAccessList(target) {
190
199
  if (!current) {
191
200
  return prev;
192
201
  }
202
+ // A lock is a wrapper over the row's committed state, so settling it unwraps —
203
+ // same as failing. Keeping a settled `locked` entry would discard the entry it
204
+ // wrapped and drop the row back to its fetched state.
205
+ if (current.kind === "locked") {
206
+ if (!current.settled) {
207
+ const { [id]: _omit, ...rest } = prev;
208
+ return rest;
209
+ }
210
+ return { ...prev, [id]: current.settled };
211
+ }
193
212
  // A settled removal always keeps its entry: for a base row (including a
194
213
  // removed re-added one) it is what keeps the row hidden; for an
195
214
  // overlay-born row it renders nothing and is harmless.
@@ -238,6 +257,7 @@ export function useAccessList(target) {
238
257
  loadOptions,
239
258
  applyGranteeAdd,
240
259
  applyGranteeLevel,
260
+ applyGranteeLock,
241
261
  applyGranteeRemove,
242
262
  settleGranteeEdit,
243
263
  failGranteeEdit,
@@ -1,5 +1,5 @@
1
1
  import type { IObjectPermissionsObject } from "@gooddata/sdk-backend-spi";
2
- import { type LabelScopePrincipal } from "./objectShareController.helpers.js";
2
+ import { type ILabelScopeChange, type LabelScopePrincipal } from "./objectShareController.helpers.js";
3
3
  import type { IObjectShareLabel } from "./types.js";
4
4
  /**
5
5
  * Per-label access scope for the share dialog: which labels each grantee can
@@ -38,6 +38,30 @@ export interface ILabelScope {
38
38
  /** Per-grantee label scope: grantee id → label ids in scope (primary always in). */
39
39
  selectedLabelIdsByGrantee: Record<string, string[]>;
40
40
  setSelectedLabelIdsByGrantee: React.Dispatch<React.SetStateAction<Record<string, string[]>>>;
41
+ /**
42
+ * In-scope labels whose access the grantee INHERITS, per grantee id — including ones
43
+ * they also hold a local grant on. Callers render these locked: unchecking cannot take
44
+ * the access away. Derived straight from the probe, never optimistic, because only the
45
+ * backend can say where a grant lives.
46
+ */
47
+ inheritedLabelIdsByGrantee: Record<string, string[]>;
48
+ /**
49
+ * Labels THIS workspace grants the grantee, per grantee id — the only ones a write may
50
+ * target, and the complement of "inherited only". An optimistic overlay, not a
51
+ * derivation: the per-label probe runs once per session, so after the dialog revokes or
52
+ * grants a label the probe's answer is stale and acting on it re-creates access that was
53
+ * just removed. Every label write reports its outcome through
54
+ * {@link recordDirectLabelWrites}.
55
+ */
56
+ directLabelIdsByGrantee: Record<string, string[]>;
57
+ /**
58
+ * Record the outcome of the dialog's OWN label writes so the direct set stays true:
59
+ * `granted` ids gain a local grant, `revoked` ids lose theirs. Pass only the writes
60
+ * that actually landed.
61
+ */
62
+ recordDirectLabelWrites: (granteeId: string, granted: readonly string[], revoked: readonly string[]) => void;
63
+ /** Forget a grantee's label bookkeeping once their row is gone for good. */
64
+ forgetGranteeLabels: (granteeId: string) => void;
41
65
  /**
42
66
  * The single per-label write path. Diffs `desired` vs `current` over the
43
67
  * permissionable labels and issues one `manageObjectPermissions` per changed
@@ -49,6 +73,27 @@ export interface ILabelScope {
49
73
  ok: boolean;
50
74
  failedLabelIds: string[];
51
75
  }>;
76
+ /**
77
+ * Per-principal variant of {@link reconcileLabelScope}: each principal moves its
78
+ * OWN scope, for grantees added in one step that each picked a different label
79
+ * scope. Still one write per label carrying every principal that changes on it,
80
+ * so a shared label costs one call regardless of how the scopes differ.
81
+ */
82
+ reconcileLabelScopes: (changes: ILabelScopeChange[]) => Promise<{
83
+ ok: boolean;
84
+ failedLabelIds: string[];
85
+ }>;
86
+ /**
87
+ * Re-grades an UNCHANGED label scope to each principal's `level` — what a
88
+ * permission-level change on the object needs so its labels don't keep the level
89
+ * they were granted at. Not a diff: every in-scope label is rewritten (a diff
90
+ * over an unchanged set produces no writes). Same independent settling and
91
+ * `failedLabelIds` reporting as {@link reconcileLabelScope}.
92
+ */
93
+ regradeLabelScope: (principals: LabelScopePrincipal[], scopeLabelIds: ReadonlySet<string>) => Promise<{
94
+ ok: boolean;
95
+ failedLabelIds: string[];
96
+ }>;
52
97
  }
53
98
  /**
54
99
  * Owns label-scope resolution + writes for {@link useObjectShareController}.
@@ -1 +1 @@
1
- {"version":3,"file":"useLabelScope.d.ts","sourceRoot":"","sources":["../../src/share/useLabelScope.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAK1E,OAAO,EACH,KAAK,mBAAmB,EAG3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA6BpD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB;;;;OAIG;IACH,eAAe,EAAE,iBAAiB,EAAE,CAAC;IACrC;;;;;;;;OAQG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B,sFAAoF;IACpF,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpD,4BAA4B,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7F;;;;;;OAMG;IACH,mBAAmB,EAAE,CACjB,UAAU,EAAE,mBAAmB,EAAE,EACjC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,EACpC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,KACnC,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CACzB,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,MAAM,EAAE,iBAAiB,EAAE,EAC3B,OAAO,EAAE,OAAO,EAChB,mBAAmB,EAAE,MAAM,EAAE,EAC7B,WAAW,EAAE,OAAO,EACpB,aAAa,EAAE,OAAO,GACvB,WAAW,CAmKb"}
1
+ {"version":3,"file":"useLabelScope.d.ts","sourceRoot":"","sources":["../../src/share/useLabelScope.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAK1E,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAI3B,MAAM,oCAAoC,CAAC;AAC5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AA6EpD;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IACxB;;;;OAIG;IACH,eAAe,EAAE,iBAAiB,EAAE,CAAC;IACrC;;;;;;;;OAQG;IACH,cAAc,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B,sFAAoF;IACpF,yBAAyB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpD,4BAA4B,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7F;;;;;OAKG;IACH,0BAA0B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACrD;;;;;;;OAOG;IACH,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAClD;;;;OAIG;IACH,uBAAuB,EAAE,CACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,SAAS,MAAM,EAAE,KACzB,IAAI,CAAC;IACV,4EAA4E;IAC5E,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD;;;;;;OAMG;IACH,mBAAmB,EAAE,CACjB,UAAU,EAAE,mBAAmB,EAAE,EACjC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,EACpC,eAAe,EAAE,WAAW,CAAC,MAAM,CAAC,KACnC,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxD;;;;;OAKG;IACH,oBAAoB,EAAE,CAClB,OAAO,EAAE,iBAAiB,EAAE,KAC3B,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACxD;;;;;;OAMG;IACH,iBAAiB,EAAE,CACf,UAAU,EAAE,mBAAmB,EAAE,EACjC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,KACjC,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CACzB,MAAM,EAAE,wBAAwB,GAAG,SAAS,EAC5C,MAAM,EAAE,iBAAiB,EAAE,EAC3B,OAAO,EAAE,OAAO,EAChB,mBAAmB,EAAE,MAAM,EAAE,EAC7B,WAAW,EAAE,OAAO,EACpB,aAAa,EAAE,OAAO,GACvB,WAAW,CA6Ob"}