@memberjunction/ng-dashboard-viewer 6.2.0-edge.2 → 6.2.0-edge.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.
Files changed (99) hide show
  1. package/README.md +165 -17
  2. package/dist/lib/config-dialogs/confirm-dialog.component.d.ts +3 -4
  3. package/dist/lib/config-dialogs/confirm-dialog.component.d.ts.map +1 -1
  4. package/dist/lib/config-dialogs/confirm-dialog.component.js +3 -2
  5. package/dist/lib/config-dialogs/confirm-dialog.component.js.map +1 -1
  6. package/dist/lib/config-panels/artifact-config-panel.component.d.ts.map +1 -1
  7. package/dist/lib/config-panels/artifact-config-panel.component.js +59 -50
  8. package/dist/lib/config-panels/artifact-config-panel.component.js.map +1 -1
  9. package/dist/lib/config-panels/base-config-panel.d.ts +8 -3
  10. package/dist/lib/config-panels/base-config-panel.d.ts.map +1 -1
  11. package/dist/lib/config-panels/base-config-panel.js +10 -3
  12. package/dist/lib/config-panels/base-config-panel.js.map +1 -1
  13. package/dist/lib/config-panels/query-config-panel.component.js +124 -99
  14. package/dist/lib/config-panels/query-config-panel.component.js.map +1 -1
  15. package/dist/lib/config-panels/view-config-panel.component.js +111 -103
  16. package/dist/lib/config-panels/view-config-panel.component.js.map +1 -1
  17. package/dist/lib/config-panels/weburl-config-panel.component.js +89 -80
  18. package/dist/lib/config-panels/weburl-config-panel.component.js.map +1 -1
  19. package/dist/lib/dashboard-browser/dashboard-browser.component.d.ts +79 -28
  20. package/dist/lib/dashboard-browser/dashboard-browser.component.d.ts.map +1 -1
  21. package/dist/lib/dashboard-browser/dashboard-browser.component.js +446 -447
  22. package/dist/lib/dashboard-browser/dashboard-browser.component.js.map +1 -1
  23. package/dist/lib/dashboard-card/dashboard-card.component.d.ts +69 -0
  24. package/dist/lib/dashboard-card/dashboard-card.component.d.ts.map +1 -0
  25. package/dist/lib/dashboard-card/dashboard-card.component.js +281 -0
  26. package/dist/lib/dashboard-card/dashboard-card.component.js.map +1 -0
  27. package/dist/lib/dashboard-card/dashboard-card.helpers.d.ts +18 -0
  28. package/dist/lib/dashboard-card/dashboard-card.helpers.d.ts.map +1 -0
  29. package/dist/lib/dashboard-card/dashboard-card.helpers.js +43 -0
  30. package/dist/lib/dashboard-card/dashboard-card.helpers.js.map +1 -0
  31. package/dist/lib/dashboard-card/layout-preview-cache.d.ts +15 -0
  32. package/dist/lib/dashboard-card/layout-preview-cache.d.ts.map +1 -0
  33. package/dist/lib/dashboard-card/layout-preview-cache.js +22 -0
  34. package/dist/lib/dashboard-card/layout-preview-cache.js.map +1 -0
  35. package/dist/lib/dashboard-viewer/dashboard-viewer.component.d.ts +69 -12
  36. package/dist/lib/dashboard-viewer/dashboard-viewer.component.d.ts.map +1 -1
  37. package/dist/lib/dashboard-viewer/dashboard-viewer.component.js +173 -145
  38. package/dist/lib/dashboard-viewer/dashboard-viewer.component.js.map +1 -1
  39. package/dist/lib/dashboard-viewer.module.d.ts +24 -20
  40. package/dist/lib/dashboard-viewer.module.d.ts.map +1 -1
  41. package/dist/lib/dashboard-viewer.module.js +42 -5
  42. package/dist/lib/dashboard-viewer.module.js.map +1 -1
  43. package/dist/lib/dialogs/add-panel-dialog/add-panel-dialog.component.d.ts +7 -12
  44. package/dist/lib/dialogs/add-panel-dialog/add-panel-dialog.component.d.ts.map +1 -1
  45. package/dist/lib/dialogs/add-panel-dialog/add-panel-dialog.component.js +3 -1
  46. package/dist/lib/dialogs/add-panel-dialog/add-panel-dialog.component.js.map +1 -1
  47. package/dist/lib/dialogs/dashboard-name-dialog/dashboard-name-dialog.component.d.ts +54 -0
  48. package/dist/lib/dialogs/dashboard-name-dialog/dashboard-name-dialog.component.d.ts.map +1 -0
  49. package/dist/lib/dialogs/dashboard-name-dialog/dashboard-name-dialog.component.js +156 -0
  50. package/dist/lib/dialogs/dashboard-name-dialog/dashboard-name-dialog.component.js.map +1 -0
  51. package/dist/lib/dialogs/edit-part-dialog/edit-part-dialog.component.d.ts +5 -11
  52. package/dist/lib/dialogs/edit-part-dialog/edit-part-dialog.component.d.ts.map +1 -1
  53. package/dist/lib/dialogs/edit-part-dialog/edit-part-dialog.component.js +3 -1
  54. package/dist/lib/dialogs/edit-part-dialog/edit-part-dialog.component.js.map +1 -1
  55. package/dist/lib/dialogs/part-dialog/dashboard-part-dialog.component.d.ts +147 -0
  56. package/dist/lib/dialogs/part-dialog/dashboard-part-dialog.component.d.ts.map +1 -0
  57. package/dist/lib/dialogs/part-dialog/dashboard-part-dialog.component.js +536 -0
  58. package/dist/lib/dialogs/part-dialog/dashboard-part-dialog.component.js.map +1 -0
  59. package/dist/lib/layout-preview/dashboard-layout-preview-node.component.d.ts +20 -0
  60. package/dist/lib/layout-preview/dashboard-layout-preview-node.component.d.ts.map +1 -0
  61. package/dist/lib/layout-preview/dashboard-layout-preview-node.component.js +80 -0
  62. package/dist/lib/layout-preview/dashboard-layout-preview-node.component.js.map +1 -0
  63. package/dist/lib/layout-preview/dashboard-layout-preview.component.d.ts +28 -0
  64. package/dist/lib/layout-preview/dashboard-layout-preview.component.d.ts.map +1 -0
  65. package/dist/lib/layout-preview/dashboard-layout-preview.component.js +62 -0
  66. package/dist/lib/layout-preview/dashboard-layout-preview.component.js.map +1 -0
  67. package/dist/lib/layout-preview/dashboard-layout-preview.d.ts +33 -0
  68. package/dist/lib/layout-preview/dashboard-layout-preview.d.ts.map +1 -0
  69. package/dist/lib/layout-preview/dashboard-layout-preview.js +138 -0
  70. package/dist/lib/layout-preview/dashboard-layout-preview.js.map +1 -0
  71. package/dist/lib/models/dashboard-layout-editor.d.ts +78 -0
  72. package/dist/lib/models/dashboard-layout-editor.d.ts.map +1 -0
  73. package/dist/lib/models/dashboard-layout-editor.js +374 -0
  74. package/dist/lib/models/dashboard-layout-editor.js.map +1 -0
  75. package/dist/lib/models/remove-part-confirm.d.ts +8 -0
  76. package/dist/lib/models/remove-part-confirm.d.ts.map +1 -0
  77. package/dist/lib/models/remove-part-confirm.js +23 -0
  78. package/dist/lib/models/remove-part-confirm.js.map +1 -0
  79. package/dist/lib/parts/artifact-part.component.js +2 -2
  80. package/dist/lib/parts/artifact-part.component.js.map +1 -1
  81. package/dist/lib/parts/query-part.component.js +2 -2
  82. package/dist/lib/parts/query-part.component.js.map +1 -1
  83. package/dist/lib/parts/view-part.component.js +2 -2
  84. package/dist/lib/parts/view-part.component.js.map +1 -1
  85. package/dist/lib/parts/weburl-part.component.js +2 -2
  86. package/dist/lib/parts/weburl-part.component.js.map +1 -1
  87. package/dist/lib/services/golden-layout-wrapper.service.d.ts +40 -0
  88. package/dist/lib/services/golden-layout-wrapper.service.d.ts.map +1 -1
  89. package/dist/lib/services/golden-layout-wrapper.service.js +117 -9
  90. package/dist/lib/services/golden-layout-wrapper.service.js.map +1 -1
  91. package/dist/lib/services/stack-part-actions.d.ts +11 -0
  92. package/dist/lib/services/stack-part-actions.d.ts.map +1 -0
  93. package/dist/lib/services/stack-part-actions.js +41 -0
  94. package/dist/lib/services/stack-part-actions.js.map +1 -0
  95. package/dist/public-api.d.ts +9 -0
  96. package/dist/public-api.d.ts.map +1 -1
  97. package/dist/public-api.js +13 -0
  98. package/dist/public-api.js.map +1 -1
  99. package/package.json +13 -13
@@ -0,0 +1,374 @@
1
+ /**
2
+ * @fileoverview Pure edits of a dashboard's saved Golden Layout tree (`ResolvedLayoutConfig`):
3
+ * find a panel, insert, remove, move and resize panels, and describe the layout for an agent.
4
+ * Every edit works on a copy and returns a new layout; the input is never changed.
5
+ */
6
+ import { ResolvedLayoutConfig } from 'golden-layout';
7
+ /** The placements beside another panel, and `tab` to join that panel's stack. */
8
+ export const PANEL_PLACEMENTS = ['left', 'right', 'above', 'below', 'tab'];
9
+ /** A layout edit that cannot be applied: an unknown panel, an invalid position or axis. */
10
+ export class LayoutEditError extends Error {
11
+ constructor(message) {
12
+ super(message);
13
+ this.name = 'LayoutEditError';
14
+ }
15
+ }
16
+ const MIN_PCT = 5;
17
+ const MAX_PCT = 95;
18
+ /** The path from the root to the panel; the last step is the panel's tab in its stack. Null when the panel is not in the layout. */
19
+ export function FindPanelPath(layout, panelId) {
20
+ const root = rootOf(layout);
21
+ return root ? findPath(root, panelId, []) : null;
22
+ }
23
+ /** A readable address for a path, for example `row/1 › column/0 › tab 2`. */
24
+ export function DescribePath(path) {
25
+ return path.map(step => (step.kind === 'stack' ? `tab ${step.index}` : `${step.kind}/${step.index}`)).join(' › ');
26
+ }
27
+ /**
28
+ * The layout with a copy of `panel` added at `position`. Beside another panel, the new panel joins
29
+ * the target's row or column, or the target is wrapped in a new one; the items of that container
30
+ * share it evenly as integer percentages. `tab` adds the panel to the target's stack and shows it;
31
+ * `end` adds it at the right end of the root, and an empty layout gets a root stack.
32
+ * @throws LayoutEditError when the placement is not one of PANEL_PLACEMENTS or `end`, when a placement
33
+ * other than `end` has no `relativeTo`, or when `relativeTo` is not in the layout.
34
+ */
35
+ export function InsertPanel(layout, panel, position) {
36
+ assertValidPosition(position);
37
+ const copy = cloneLayout(layout);
38
+ const item = componentItem(cloneJson(panel));
39
+ if (position.placement === 'end') {
40
+ copy.root = copy.root ? appendToRoot(copy.root, stackItem([item])) : stackItem([item]);
41
+ return toResolved(copy);
42
+ }
43
+ const root = copy.root;
44
+ const location = root ? locate(root, position.relativeTo) : null;
45
+ if (!root || !location)
46
+ throw new LayoutEditError(`Panel "${position.relativeTo}" is not in the layout.`);
47
+ if (position.placement === 'tab') {
48
+ addTab(location.stack, item);
49
+ }
50
+ else {
51
+ copy.root = splitBeside(root, location, stackItem([item]), position.placement);
52
+ }
53
+ return toResolved(copy);
54
+ }
55
+ /**
56
+ * The layout without the panel. Its stack keeps showing the same tab when that tab remains. An
57
+ * empty stack is removed, a container left with one item is replaced by that item, and the items of
58
+ * a container that lost one share it evenly unless their sizes still add up to 100%. A layout left
59
+ * with no panels has no root.
60
+ * @throws LayoutEditError when the panel is not in the layout.
61
+ */
62
+ export function RemovePanelFromLayout(layout, panelId) {
63
+ const copy = cloneLayout(layout);
64
+ const location = copy.root ? locate(copy.root, panelId) : null;
65
+ if (!copy.root || !location)
66
+ throw new LayoutEditError(`Panel "${panelId}" is not in the layout.`);
67
+ removeTab(location.stack, panelId);
68
+ copy.root = prune(copy.root);
69
+ return toResolved(copy);
70
+ }
71
+ /**
72
+ * The layout with the panel removed from where it is and inserted at `position`.
73
+ * @throws LayoutEditError when the position is not valid (as for InsertPanel), when the panel or
74
+ * `relativeTo` is not in the layout, or when `relativeTo` is the panel itself.
75
+ */
76
+ export function MovePanel(layout, panelId, position) {
77
+ assertValidPosition(position);
78
+ if (position.placement !== 'end' && position.relativeTo === panelId) {
79
+ throw new LayoutEditError('A panel cannot be placed relative to itself.');
80
+ }
81
+ const current = findComponent(rootOf(layout), panelId);
82
+ if (!current?.componentState)
83
+ throw new LayoutEditError(`Panel "${panelId}" is not in the layout.`);
84
+ return InsertPanel(RemovePanelFromLayout(layout, panelId), current.componentState, position);
85
+ }
86
+ /**
87
+ * The layout with the panel resized. `widthPct` sets the panel's share of the nearest row that it
88
+ * shares with other items, and `heightPct` its share of the nearest such column; give either or both.
89
+ * The share is kept between 5% and 95%, and low enough that every other item in the container keeps
90
+ * at least 5%. The other items share the rest in proportion to their current shares. Sizes are integer
91
+ * percentages that add up to 100.
92
+ * @throws LayoutEditError when the panel is not in the layout, no size or a non-finite size is given,
93
+ * or the panel shares no row (for a width) or column (for a height) with other items.
94
+ */
95
+ export function ResizePanel(layout, panelId, size) {
96
+ const copy = cloneLayout(layout);
97
+ const path = copy.root ? findPath(copy.root, panelId, []) : null;
98
+ if (!copy.root || !path)
99
+ throw new LayoutEditError(`Panel "${panelId}" is not in the layout.`);
100
+ if (size.widthPct === undefined && size.heightPct === undefined)
101
+ throw new LayoutEditError('Give widthPct, heightPct or both.');
102
+ if (size.widthPct !== undefined)
103
+ resizeAlong(copy.root, path, 'row', size.widthPct, panelId);
104
+ if (size.heightPct !== undefined)
105
+ resizeAlong(copy.root, path, 'column', size.heightPct, panelId);
106
+ return toResolved(copy);
107
+ }
108
+ /** A compact tree of the layout: containers with integer percentage sizes and panels with their IDs. */
109
+ export function SimplifyLayout(layout) {
110
+ const root = rootOf(layout);
111
+ return root ? simplify(root, false) : null;
112
+ }
113
+ // ── positions ───────────────────────────────────────────────────────────────
114
+ /**
115
+ * Throws when the position is not one this editor can apply: a placement that is not one of
116
+ * PANEL_PLACEMENTS or `end`, or a placement other than `end` without a `relativeTo`. Positions can come
117
+ * from agent JSON, so this checks values that the types cannot.
118
+ */
119
+ function assertValidPosition(position) {
120
+ const placement = position.placement;
121
+ if (placement === 'end')
122
+ return;
123
+ if (!PANEL_PLACEMENTS.some(allowed => allowed === placement)) {
124
+ const shown = typeof placement === 'string' ? `"${placement}"` : String(placement);
125
+ throw new LayoutEditError(`position.placement must be one of ${[...PANEL_PLACEMENTS, 'end'].join(', ')}; got ${shown}.`);
126
+ }
127
+ const relativeTo = 'relativeTo' in position ? position.relativeTo : undefined;
128
+ if (typeof relativeTo !== 'string' || !relativeTo.trim()) {
129
+ throw new LayoutEditError(`position.relativeTo is required for placement "${placement}".`);
130
+ }
131
+ }
132
+ // ── layout copies ───────────────────────────────────────────────────────────
133
+ /** A deep copy of JSON data. */
134
+ function cloneJson(value) {
135
+ return JSON.parse(JSON.stringify(value));
136
+ }
137
+ /** A deep copy of the layout to edit, or Golden Layout's default empty layout when there is none. */
138
+ function cloneLayout(layout) {
139
+ return JSON.parse(JSON.stringify(layout ?? ResolvedLayoutConfig.createDefault()));
140
+ }
141
+ /** The root item of a saved layout, read as the item shape that Golden Layout saves. */
142
+ function rootOf(layout) {
143
+ return layout?.root;
144
+ }
145
+ /** The edited copy as Golden Layout's saved-layout type; its items keep the shape that Golden Layout saves. */
146
+ function toResolved(copy) {
147
+ return copy;
148
+ }
149
+ // ── items ───────────────────────────────────────────────────────────────────
150
+ function componentItem(panel) {
151
+ return {
152
+ type: 'component', content: [], size: 1, sizeUnit: 'fr', minSizeUnit: 'px', id: '', maximised: false, isClosable: true,
153
+ reorderEnabled: true, title: panel.title, componentType: 'dashboard-panel', componentState: panel,
154
+ };
155
+ }
156
+ function stackItem(content) {
157
+ return { type: 'stack', content, size: 50, sizeUnit: '%', minSizeUnit: 'px', id: '', isClosable: true, maximised: false, activeItemIndex: 0 };
158
+ }
159
+ function groupItem(type, content) {
160
+ return { type, content, size: 1, sizeUnit: 'fr', minSizeUnit: 'px', id: '', isClosable: true };
161
+ }
162
+ // ── finding panels ──────────────────────────────────────────────────────────
163
+ function findPath(node, panelId, path) {
164
+ if (node.type === 'component')
165
+ return node.componentState?.id === panelId ? path : null;
166
+ for (let i = 0; i < node.content.length; i++) {
167
+ const found = findPath(node.content[i], panelId, [...path, { kind: node.type, index: i }]);
168
+ if (found)
169
+ return found;
170
+ }
171
+ return null;
172
+ }
173
+ function findComponent(node, panelId) {
174
+ if (!node)
175
+ return null;
176
+ if (node.type === 'component')
177
+ return node.componentState?.id === panelId ? node : null;
178
+ for (const child of node.content) {
179
+ const found = findComponent(child, panelId);
180
+ if (found)
181
+ return found;
182
+ }
183
+ return null;
184
+ }
185
+ /** The stack that holds the panel, with its parent container and index. */
186
+ function locate(node, panelId, parent = null, indexInParent = -1) {
187
+ if (node.type === 'stack') {
188
+ return node.content.some(c => c.componentState?.id === panelId) ? { stack: node, parent, indexInParent } : null;
189
+ }
190
+ if (node.type === 'component')
191
+ return null;
192
+ for (let i = 0; i < node.content.length; i++) {
193
+ const found = locate(node.content[i], panelId, node, i);
194
+ if (found)
195
+ return found;
196
+ }
197
+ return null;
198
+ }
199
+ // ── inserting ───────────────────────────────────────────────────────────────
200
+ /** The root with `newStack` at its right end: appended to a root row, or beside any other root in a new row. */
201
+ function appendToRoot(root, newStack) {
202
+ const row = root.type === 'row' ? root : groupItem('row', [root]);
203
+ row.content.push(newStack);
204
+ shareEvenly(row.content);
205
+ return row;
206
+ }
207
+ /** Adds the item to the stack as its last tab and shows it. */
208
+ function addTab(stack, item) {
209
+ stack.content.push(item);
210
+ stack.activeItemIndex = stack.content.length - 1;
211
+ }
212
+ /**
213
+ * Puts `newStack` beside the target stack: into the parent when the parent runs along the needed axis,
214
+ * otherwise into a new row or column that takes the target's place and size. The items of the changed
215
+ * container share it evenly. Returns the layout's root, which is the new container when the target was the root.
216
+ */
217
+ function splitBeside(root, location, newStack, placement) {
218
+ const { stack, parent, indexInParent } = location;
219
+ const axis = placement === 'left' || placement === 'right' ? 'row' : 'column';
220
+ const after = placement === 'right' || placement === 'below';
221
+ if (parent && parent.type === axis) {
222
+ parent.content.splice(indexInParent + (after ? 1 : 0), 0, newStack);
223
+ shareEvenly(parent.content);
224
+ return root;
225
+ }
226
+ const wrapper = groupItem(axis, after ? [stack, newStack] : [newStack, stack]);
227
+ wrapper.size = stack.size;
228
+ wrapper.sizeUnit = stack.sizeUnit;
229
+ shareEvenly(wrapper.content);
230
+ if (!parent)
231
+ return wrapper;
232
+ parent.content[indexInParent] = wrapper;
233
+ return root;
234
+ }
235
+ // ── removing ────────────────────────────────────────────────────────────────
236
+ /** Removes the panel's tab from the stack. The stack keeps showing the same tab when that tab remains. */
237
+ function removeTab(stack, panelId) {
238
+ const index = stack.content.findIndex(c => c.componentState?.id === panelId);
239
+ stack.content.splice(index, 1);
240
+ const active = stack.activeItemIndex ?? 0;
241
+ stack.activeItemIndex = clamp(active > index ? active - 1 : active, 0, Math.max(0, stack.content.length - 1));
242
+ }
243
+ /**
244
+ * Removes empty stacks and containers and lifts single-child rows and columns. A container that lost
245
+ * an item shares its space evenly unless its sizes still add up to 100%. Returns undefined when nothing is left.
246
+ */
247
+ function prune(node) {
248
+ if (node.type === 'component')
249
+ return node;
250
+ if (node.type === 'stack')
251
+ return node.content.length ? node : undefined;
252
+ const kept = node.content.map(prune).filter((c) => !!c);
253
+ if (kept.length === 0)
254
+ return undefined;
255
+ if (kept.length === 1) {
256
+ const only = kept[0];
257
+ only.size = node.size;
258
+ only.sizeUnit = node.sizeUnit;
259
+ return only;
260
+ }
261
+ if (kept.length < node.content.length)
262
+ shareEvenlyIfNeeded(kept);
263
+ node.content = kept;
264
+ return node;
265
+ }
266
+ // ── sizes ───────────────────────────────────────────────────────────────────
267
+ /** Gives each sibling an equal integer percentage; the first child takes the rounding remainder. */
268
+ function shareEvenly(items) {
269
+ const each = Math.floor(100 / items.length);
270
+ items.forEach((item, i) => setPercent(item, i === 0 ? 100 - each * (items.length - 1) : each));
271
+ }
272
+ function shareEvenlyIfNeeded(items) {
273
+ const percentTotal = sum(items.map(item => (item.sizeUnit === '%' ? item.size : 0)));
274
+ if (Math.round(percentTotal) !== 100)
275
+ shareEvenly(items);
276
+ }
277
+ /** Sets the panel's share along `axis` in the nearest container of that kind where it has siblings. */
278
+ function resizeAlong(root, path, axis, pct, panelId) {
279
+ const dimension = axis === 'row' ? 'width' : 'height';
280
+ if (!Number.isFinite(pct))
281
+ throw new LayoutEditError(`The ${dimension} must be a finite percentage.`);
282
+ const slot = nearestSlot(root, path, axis);
283
+ if (!slot)
284
+ throw new LayoutEditError(`Panel "${panelId}" has no ${dimension} to change: it does not share a ${axis} with other panels.`);
285
+ setShare(slot.container.content, slot.index, pct);
286
+ }
287
+ /** The nearest container of kind `axis` on the path that holds more than one item, with the index of the item on the path. */
288
+ function nearestSlot(root, path, axis) {
289
+ let node = root;
290
+ let slot = null;
291
+ for (const step of path) {
292
+ if (node.type === axis && node.content.length > 1)
293
+ slot = { container: node, index: step.index };
294
+ node = node.content[step.index];
295
+ }
296
+ return slot;
297
+ }
298
+ /**
299
+ * Gives `items[index]` `pct` percent, clamped so that every item keeps at least 5% (less only when
300
+ * more than 20 items share the container), and shares the rest among the other items in proportion
301
+ * to their current shares. All sizes become integer percentages that add up to 100.
302
+ */
303
+ function setShare(items, index, pct) {
304
+ const floor = Math.min(MIN_PCT, Math.floor(100 / items.length));
305
+ const target = clamp(Math.round(pct), floor, Math.min(MAX_PCT, 100 - floor * (items.length - 1)));
306
+ const otherShares = currentShares(items).filter((_, i) => i !== index);
307
+ const otherSizes = apportion(100 - target, otherShares, floor);
308
+ items.filter((_, i) => i !== index).forEach((item, i) => setPercent(item, otherSizes[i]));
309
+ setPercent(items[index], target);
310
+ }
311
+ /**
312
+ * Each item's share of its row or column in percent, read the way Golden Layout reads sizes: `%`
313
+ * sizes first, then the `fr` sizes share the space left (50% more when none is left). The shares add up to 100.
314
+ */
315
+ function currentShares(items) {
316
+ const sizes = items.map(readSize);
317
+ const percentTotal = sum(sizes.filter(s => s.unit === '%').map(s => s.value));
318
+ const fractionTotal = sum(sizes.filter(s => s.unit === 'fr').map(s => s.value));
319
+ const fractionSpace = Math.round(percentTotal) < 100 ? 100 - percentTotal : 50;
320
+ const amounts = sizes.map(s => (s.unit === '%' ? s.value : (fractionSpace * s.value) / fractionTotal));
321
+ const total = sum(amounts);
322
+ return amounts.map(amount => (amount * 100) / total);
323
+ }
324
+ /** An item's size. A missing size, one that is not positive, or one in another unit counts as one `fr`. */
325
+ function readSize(item) {
326
+ if (Number.isFinite(item.size) && item.size > 0) {
327
+ if (item.sizeUnit === '%')
328
+ return { unit: '%', value: item.size };
329
+ if (item.sizeUnit === 'fr')
330
+ return { unit: 'fr', value: item.size };
331
+ }
332
+ return { unit: 'fr', value: 1 };
333
+ }
334
+ /**
335
+ * Splits `total` into integers in proportion to `weights`, with every part at least `floor`. Parts that
336
+ * would fall below `floor` get exactly `floor` and the others share what is left. The caller makes sure
337
+ * that `total` is at least `floor` times the number of parts.
338
+ */
339
+ function apportion(total, weights, floor, atFloor = new Set()) {
340
+ const freeTotal = total - floor * atFloor.size;
341
+ const freeWeight = sum(weights.filter((_, i) => !atFloor.has(i)));
342
+ const parts = weights.map((weight, i) => (atFloor.has(i) ? floor : (freeTotal * weight) / freeWeight));
343
+ const below = parts.flatMap((part, i) => (!atFloor.has(i) && part < floor ? [i] : []));
344
+ return below.length > 0 ? apportion(total, weights, floor, new Set([...atFloor, ...below])) : roundToTotal(parts, total);
345
+ }
346
+ /** Rounds parts that add up to `total` down to integers, then gives the units left over to the largest remainders. */
347
+ function roundToTotal(parts, total) {
348
+ const rounded = parts.map(part => Math.floor(part));
349
+ const left = total - sum(rounded);
350
+ const byRemainder = parts.map((_, i) => i).sort((a, b) => parts[b] - rounded[b] - (parts[a] - rounded[a]));
351
+ byRemainder.slice(0, left).forEach(i => rounded[i]++);
352
+ return rounded;
353
+ }
354
+ function setPercent(item, pct) {
355
+ item.size = pct;
356
+ item.sizeUnit = '%';
357
+ }
358
+ function clamp(value, low, high) {
359
+ return Math.max(low, Math.min(high, value));
360
+ }
361
+ function sum(values) {
362
+ return values.reduce((total, value) => total + value, 0);
363
+ }
364
+ // ── describing ──────────────────────────────────────────────────────────────
365
+ function simplify(node, sized) {
366
+ if (node.type === 'component') {
367
+ return { kind: 'panel', panelId: node.componentState?.id, title: node.componentState?.title ?? node.title };
368
+ }
369
+ const out = { kind: node.type, children: node.content.map(c => simplify(c, true)) };
370
+ if (sized && node.sizeUnit === '%')
371
+ out.sizePct = Math.round(node.size);
372
+ return out;
373
+ }
374
+ //# sourceMappingURL=dashboard-layout-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dashboard-layout-editor.js","sourceRoot":"","sources":["../../../src/lib/models/dashboard-layout-editor.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGrD,iFAAiF;AACjF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAU,CAAC;AAyBpF,2FAA2F;AAC3F,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AA4CD,MAAM,OAAO,GAAG,CAAC,CAAC;AAClB,MAAM,OAAO,GAAG,EAAE,CAAC;AAEnB,oIAAoI;AACpI,MAAM,UAAU,aAAa,CAAC,MAAmC,EAAE,OAAe;IAChF,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,YAAY,CAAC,IAAsB;IACjD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACpH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,MAAmC,EAAE,KAAqB,EAAE,QAAuB;IAC7G,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC9B,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IAC7C,IAAI,QAAQ,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QACvF,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,eAAe,CAAC,UAAU,QAAQ,CAAC,UAAU,yBAAyB,CAAC,CAAC;IAC1G,IAAI,QAAQ,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;QACjC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/B,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;IACjF,CAAC;IACD,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAmC,EAAE,OAAe;IACxF,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/D,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,eAAe,CAAC,UAAU,OAAO,yBAAyB,CAAC,CAAC;IACnG,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAmC,EAAE,OAAe,EAAE,QAAuB;IACrG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IAC9B,IAAI,QAAQ,CAAC,SAAS,KAAK,KAAK,IAAI,QAAQ,CAAC,UAAU,KAAK,OAAO,EAAE,CAAC;QACpE,MAAM,IAAI,eAAe,CAAC,8CAA8C,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC;IACvD,IAAI,CAAC,OAAO,EAAE,cAAc;QAAE,MAAM,IAAI,eAAe,CAAC,UAAU,OAAO,yBAAyB,CAAC,CAAC;IACpG,OAAO,WAAW,CAAC,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;AAC/F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CACzB,MAAmC,EACnC,OAAe,EACf,IAA+C;IAE/C,MAAM,IAAI,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjE,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,eAAe,CAAC,UAAU,OAAO,yBAAyB,CAAC,CAAC;IAC/F,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;QAAE,MAAM,IAAI,eAAe,CAAC,mCAAmC,CAAC,CAAC;IAChI,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;QAAE,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC7F,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS;QAAE,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClG,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,cAAc,CAAC,MAAmC;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7C,CAAC;AAED,+EAA+E;AAE/E;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,QAAuB;IAClD,MAAM,SAAS,GAAuB,QAAQ,CAAC,SAAS,CAAC;IACzD,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO;IAChC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,KAAK,SAAS,CAAC,EAAE,CAAC;QAC7D,MAAM,KAAK,GAAG,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,SAAS,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACnF,MAAM,IAAI,eAAe,CAAC,qCAAqC,CAAC,GAAG,gBAAgB,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,KAAK,GAAG,CAAC,CAAC;IAC3H,CAAC;IACD,MAAM,UAAU,GAAuB,YAAY,IAAI,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;IAClG,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;QACzD,MAAM,IAAI,eAAe,CAAC,kDAAkD,SAAS,IAAI,CAAC,CAAC;IAC7F,CAAC;AACH,CAAC;AAED,+EAA+E;AAE/E,gCAAgC;AAChC,SAAS,SAAS,CAAI,KAAQ;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAM,CAAC;AAChD,CAAC;AAED,qGAAqG;AACrG,SAAS,WAAW,CAAC,MAAmC;IACtD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,oBAAoB,CAAC,aAAa,EAAE,CAAC,CAAmB,CAAC;AACtG,CAAC;AAED,wFAAwF;AACxF,SAAS,MAAM,CAAC,MAAmC;IACjD,OAAQ,MAAgC,EAAE,IAAI,CAAC;AACjD,CAAC;AAED,+GAA+G;AAC/G,SAAS,UAAU,CAAC,IAAoB;IACtC,OAAO,IAA4B,CAAC;AACtC,CAAC;AAED,+EAA+E;AAE/E,SAAS,aAAa,CAAC,KAAqB;IAC1C,OAAO;QACL,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI;QACtH,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,KAAK;KAClG,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,OAAqB;IACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,EAAE,CAAC;AAChJ,CAAC;AAED,SAAS,SAAS,CAAC,IAAsB,EAAE,OAAqB;IAC9D,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACjG,CAAC;AAED,+EAA+E;AAE/E,SAAS,QAAQ,CAAC,IAAgB,EAAE,OAAe,EAAE,IAAsB;IACzE,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC,cAAc,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACxF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3F,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,aAAa,CAAC,IAA4B,EAAE,OAAe;IAClE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC,cAAc,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACxF,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC5C,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2EAA2E;AAC3E,SAAS,MAAM,CAAC,IAAgB,EAAE,OAAe,EAAE,SAA4B,IAAI,EAAE,aAAa,GAAG,CAAC,CAAC;IACrG,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAClH,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACxD,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+EAA+E;AAE/E,gHAAgH;AAChH,SAAS,YAAY,CAAC,IAAgB,EAAE,QAAoB;IAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC3B,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACzB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,+DAA+D;AAC/D,SAAS,MAAM,CAAC,KAAiB,EAAE,IAAgB;IACjD,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,IAAgB,EAAE,QAAuB,EAAE,QAAoB,EAAE,SAAwB;IAC5G,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,QAAQ,CAAC;IAClD,MAAM,IAAI,GAAqB,SAAS,KAAK,MAAM,IAAI,SAAS,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;IAChG,MAAM,KAAK,GAAG,SAAS,KAAK,OAAO,IAAI,SAAS,KAAK,OAAO,CAAC;IAC7D,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACnC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;QACpE,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IAC/E,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAC1B,OAAO,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAClC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM;QAAE,OAAO,OAAO,CAAC;IAC5B,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,OAAO,CAAC;IACxC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+EAA+E;AAE/E,0GAA0G;AAC1G,SAAS,SAAS,CAAC,KAAiB,EAAE,OAAe;IACnD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EAAE,KAAK,OAAO,CAAC,CAAC;IAC7E,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,KAAK,CAAC,eAAe,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;AAChH,CAAC;AAED;;;GAGG;AACH,SAAS,KAAK,CAAC,IAAgB;IAC7B,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACzE,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAmB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACxC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;IACpB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+EAA+E;AAE/E,oGAAoG;AACpG,SAAS,WAAW,CAAC,KAAmB;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5C,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACjG,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAmB;IAC9C,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrF,IAAI,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,GAAG;QAAE,WAAW,CAAC,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED,uGAAuG;AACvG,SAAS,WAAW,CAAC,IAAgB,EAAE,IAAsB,EAAE,IAAsB,EAAE,GAAW,EAAE,OAAe;IACjH,MAAM,SAAS,GAAG,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,eAAe,CAAC,OAAO,SAAS,+BAA+B,CAAC,CAAC;IACtG,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,eAAe,CAAC,UAAU,OAAO,YAAY,SAAS,mCAAmC,IAAI,qBAAqB,CAAC,CAAC;IACzI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACpD,CAAC;AAED,8HAA8H;AAC9H,SAAS,WAAW,CAAC,IAAgB,EAAE,IAAsB,EAAE,IAAsB;IACnF,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,IAAI,GAAoB,IAAI,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,IAAI,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QACjG,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,SAAS,QAAQ,CAAC,KAAmB,EAAE,KAAa,EAAE,GAAW;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,GAAG,KAAK,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAClG,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;IACvE,MAAM,UAAU,GAAG,SAAS,CAAC,GAAG,GAAG,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IAC/D,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1F,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,KAAmB;IACxC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAClC,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAC9E,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;IAChF,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IAC/E,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC;IACvG,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IAC3B,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;AACvD,CAAC;AAED,2GAA2G;AAC3G,SAAS,QAAQ,CAAC,IAAgB;IAChC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QAChD,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG;YAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAClE,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IACtE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,KAAa,EAAE,OAAiB,EAAE,KAAa,EAAE,UAA+B,IAAI,GAAG,EAAE;IAC1G,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAC/C,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IACvG,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvF,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC3H,CAAC;AAED,sHAAsH;AACtH,SAAS,YAAY,CAAC,KAAe,EAAE,KAAa;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3G,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,UAAU,CAAC,IAAgB,EAAE,GAAW;IAC/C,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;IAChB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;AACtB,CAAC;AAED,SAAS,KAAK,CAAC,KAAa,EAAE,GAAW,EAAE,IAAY;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,GAAG,CAAC,MAAgB;IAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,+EAA+E;AAE/E,SAAS,QAAQ,CAAC,IAAgB,EAAE,KAAc;IAChD,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;IAC9G,CAAC;IACD,MAAM,GAAG,GAAyB,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC;IAC1G,IAAI,KAAK,IAAI,IAAI,CAAC,QAAQ,KAAK,GAAG;QAAE,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * @fileoverview Pure edits of a dashboard's saved Golden Layout tree (`ResolvedLayoutConfig`):\n * find a panel, insert, remove, move and resize panels, and describe the layout for an agent.\n * Every edit works on a copy and returns a new layout; the input is never changed.\n */\nimport { ResolvedLayoutConfig } from 'golden-layout';\nimport type { DashboardPanel } from './dashboard-types';\n\n/** The placements beside another panel, and `tab` to join that panel's stack. */\nexport const PANEL_PLACEMENTS = ['left', 'right', 'above', 'below', 'tab'] as const;\n\n/** Where a panel goes beside another panel, or `tab` to join that panel's stack. */\nexport type PanelPlacement = (typeof PANEL_PLACEMENTS)[number];\n\n/** Where a panel goes, relative to another panel or at the end of the root. */\nexport type PanelPosition =\n | { relativeTo: string; placement: PanelPlacement } // case-violation-ok-legacy-back-compat: camelCase JSON keys that agent tools send\n | { placement: 'end' }; // case-violation-ok-legacy-back-compat: camelCase JSON keys that agent tools send\n\n/** One step from the root to a panel: the container kind and the child index taken. */\nexport interface LayoutPathStep {\n kind: 'row' | 'column' | 'stack'; // case-violation-ok-legacy-back-compat: camelCase keys, like the other layout shapes in this file\n index: number; // case-violation-ok-legacy-back-compat: camelCase keys, like the other layout shapes in this file\n}\n\n/** A compact description of a layout for an agent. */\nexport interface SimplifiedLayoutNode {\n kind: 'row' | 'column' | 'stack' | 'panel'; // case-violation-ok-legacy-back-compat: camelCase JSON keys that agent tools read\n sizePct?: number; // case-violation-ok-legacy-back-compat: camelCase JSON keys that agent tools read\n panelId?: string; // case-violation-ok-legacy-back-compat: camelCase JSON keys that agent tools read\n title?: string; // case-violation-ok-legacy-back-compat: camelCase JSON keys that agent tools read\n children?: SimplifiedLayoutNode[]; // case-violation-ok-legacy-back-compat: camelCase JSON keys that agent tools read\n}\n\n/** A layout edit that cannot be applied: an unknown panel, an invalid position or axis. */\nexport class LayoutEditError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LayoutEditError';\n }\n}\n\n/** The Golden Layout item shape this editor reads and writes: the JSON that Golden Layout saves. */\ninterface LayoutItem {\n type: 'row' | 'column' | 'stack' | 'component';\n content: LayoutItem[];\n size: number;\n sizeUnit: string;\n minSizeUnit?: string;\n id?: string;\n isClosable?: boolean;\n maximised?: boolean;\n activeItemIndex?: number;\n reorderEnabled?: boolean;\n title?: string;\n componentType?: string;\n componentState?: DashboardPanel;\n}\n\n/** A saved layout as this editor changes it: Golden Layout's saved layout with a root this editor can replace. */\ntype EditableLayout = Omit<ResolvedLayoutConfig, 'root'> & { root?: LayoutItem };\n\n/** The stack that holds a panel, with the stack's parent container and its index there. The root stack has no parent. */\ninterface PanelLocation {\n stack: LayoutItem;\n parent: LayoutItem | null;\n indexInParent: number;\n}\n\n/** An item in a row or column, and the index of that item. */\ninterface ItemSlot {\n container: LayoutItem;\n index: number;\n}\n\n/** An item's size in its row or column. */\ninterface ItemSize {\n unit: '%' | 'fr';\n value: number;\n}\n\n/** A side placement: the new panel goes left, right, above or below the target. */\ntype SidePlacement = Exclude<PanelPlacement, 'tab'>;\n\nconst MIN_PCT = 5;\nconst MAX_PCT = 95;\n\n/** The path from the root to the panel; the last step is the panel's tab in its stack. Null when the panel is not in the layout. */\nexport function FindPanelPath(layout: ResolvedLayoutConfig | null, panelId: string): LayoutPathStep[] | null {\n const root = rootOf(layout);\n return root ? findPath(root, panelId, []) : null;\n}\n\n/** A readable address for a path, for example `row/1 › column/0 › tab 2`. */\nexport function DescribePath(path: LayoutPathStep[]): string {\n return path.map(step => (step.kind === 'stack' ? `tab ${step.index}` : `${step.kind}/${step.index}`)).join(' › ');\n}\n\n/**\n * The layout with a copy of `panel` added at `position`. Beside another panel, the new panel joins\n * the target's row or column, or the target is wrapped in a new one; the items of that container\n * share it evenly as integer percentages. `tab` adds the panel to the target's stack and shows it;\n * `end` adds it at the right end of the root, and an empty layout gets a root stack.\n * @throws LayoutEditError when the placement is not one of PANEL_PLACEMENTS or `end`, when a placement\n * other than `end` has no `relativeTo`, or when `relativeTo` is not in the layout.\n */\nexport function InsertPanel(layout: ResolvedLayoutConfig | null, panel: DashboardPanel, position: PanelPosition): ResolvedLayoutConfig {\n assertValidPosition(position);\n const copy = cloneLayout(layout);\n const item = componentItem(cloneJson(panel));\n if (position.placement === 'end') {\n copy.root = copy.root ? appendToRoot(copy.root, stackItem([item])) : stackItem([item]);\n return toResolved(copy);\n }\n const root = copy.root;\n const location = root ? locate(root, position.relativeTo) : null;\n if (!root || !location) throw new LayoutEditError(`Panel \"${position.relativeTo}\" is not in the layout.`);\n if (position.placement === 'tab') {\n addTab(location.stack, item);\n } else {\n copy.root = splitBeside(root, location, stackItem([item]), position.placement);\n }\n return toResolved(copy);\n}\n\n/**\n * The layout without the panel. Its stack keeps showing the same tab when that tab remains. An\n * empty stack is removed, a container left with one item is replaced by that item, and the items of\n * a container that lost one share it evenly unless their sizes still add up to 100%. A layout left\n * with no panels has no root.\n * @throws LayoutEditError when the panel is not in the layout.\n */\nexport function RemovePanelFromLayout(layout: ResolvedLayoutConfig | null, panelId: string): ResolvedLayoutConfig {\n const copy = cloneLayout(layout);\n const location = copy.root ? locate(copy.root, panelId) : null;\n if (!copy.root || !location) throw new LayoutEditError(`Panel \"${panelId}\" is not in the layout.`);\n removeTab(location.stack, panelId);\n copy.root = prune(copy.root);\n return toResolved(copy);\n}\n\n/**\n * The layout with the panel removed from where it is and inserted at `position`.\n * @throws LayoutEditError when the position is not valid (as for InsertPanel), when the panel or\n * `relativeTo` is not in the layout, or when `relativeTo` is the panel itself.\n */\nexport function MovePanel(layout: ResolvedLayoutConfig | null, panelId: string, position: PanelPosition): ResolvedLayoutConfig {\n assertValidPosition(position);\n if (position.placement !== 'end' && position.relativeTo === panelId) {\n throw new LayoutEditError('A panel cannot be placed relative to itself.');\n }\n const current = findComponent(rootOf(layout), panelId);\n if (!current?.componentState) throw new LayoutEditError(`Panel \"${panelId}\" is not in the layout.`);\n return InsertPanel(RemovePanelFromLayout(layout, panelId), current.componentState, position);\n}\n\n/**\n * The layout with the panel resized. `widthPct` sets the panel's share of the nearest row that it\n * shares with other items, and `heightPct` its share of the nearest such column; give either or both.\n * The share is kept between 5% and 95%, and low enough that every other item in the container keeps\n * at least 5%. The other items share the rest in proportion to their current shares. Sizes are integer\n * percentages that add up to 100.\n * @throws LayoutEditError when the panel is not in the layout, no size or a non-finite size is given,\n * or the panel shares no row (for a width) or column (for a height) with other items.\n */\nexport function ResizePanel(\n layout: ResolvedLayoutConfig | null,\n panelId: string,\n size: { widthPct?: number; heightPct?: number },\n): ResolvedLayoutConfig {\n const copy = cloneLayout(layout);\n const path = copy.root ? findPath(copy.root, panelId, []) : null;\n if (!copy.root || !path) throw new LayoutEditError(`Panel \"${panelId}\" is not in the layout.`);\n if (size.widthPct === undefined && size.heightPct === undefined) throw new LayoutEditError('Give widthPct, heightPct or both.');\n if (size.widthPct !== undefined) resizeAlong(copy.root, path, 'row', size.widthPct, panelId);\n if (size.heightPct !== undefined) resizeAlong(copy.root, path, 'column', size.heightPct, panelId);\n return toResolved(copy);\n}\n\n/** A compact tree of the layout: containers with integer percentage sizes and panels with their IDs. */\nexport function SimplifyLayout(layout: ResolvedLayoutConfig | null): SimplifiedLayoutNode | null {\n const root = rootOf(layout);\n return root ? simplify(root, false) : null;\n}\n\n// ── positions ───────────────────────────────────────────────────────────────\n\n/**\n * Throws when the position is not one this editor can apply: a placement that is not one of\n * PANEL_PLACEMENTS or `end`, or a placement other than `end` without a `relativeTo`. Positions can come\n * from agent JSON, so this checks values that the types cannot.\n */\nfunction assertValidPosition(position: PanelPosition): void {\n const placement: string | undefined = position.placement;\n if (placement === 'end') return;\n if (!PANEL_PLACEMENTS.some(allowed => allowed === placement)) {\n const shown = typeof placement === 'string' ? `\"${placement}\"` : String(placement);\n throw new LayoutEditError(`position.placement must be one of ${[...PANEL_PLACEMENTS, 'end'].join(', ')}; got ${shown}.`);\n }\n const relativeTo: string | undefined = 'relativeTo' in position ? position.relativeTo : undefined;\n if (typeof relativeTo !== 'string' || !relativeTo.trim()) {\n throw new LayoutEditError(`position.relativeTo is required for placement \"${placement}\".`);\n }\n}\n\n// ── layout copies ───────────────────────────────────────────────────────────\n\n/** A deep copy of JSON data. */\nfunction cloneJson<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\n/** A deep copy of the layout to edit, or Golden Layout's default empty layout when there is none. */\nfunction cloneLayout(layout: ResolvedLayoutConfig | null): EditableLayout {\n return JSON.parse(JSON.stringify(layout ?? ResolvedLayoutConfig.createDefault())) as EditableLayout;\n}\n\n/** The root item of a saved layout, read as the item shape that Golden Layout saves. */\nfunction rootOf(layout: ResolvedLayoutConfig | null): LayoutItem | undefined {\n return (layout as EditableLayout | null)?.root;\n}\n\n/** The edited copy as Golden Layout's saved-layout type; its items keep the shape that Golden Layout saves. */\nfunction toResolved(copy: EditableLayout): ResolvedLayoutConfig {\n return copy as ResolvedLayoutConfig;\n}\n\n// ── items ───────────────────────────────────────────────────────────────────\n\nfunction componentItem(panel: DashboardPanel): LayoutItem {\n return {\n type: 'component', content: [], size: 1, sizeUnit: 'fr', minSizeUnit: 'px', id: '', maximised: false, isClosable: true,\n reorderEnabled: true, title: panel.title, componentType: 'dashboard-panel', componentState: panel,\n };\n}\n\nfunction stackItem(content: LayoutItem[]): LayoutItem {\n return { type: 'stack', content, size: 50, sizeUnit: '%', minSizeUnit: 'px', id: '', isClosable: true, maximised: false, activeItemIndex: 0 };\n}\n\nfunction groupItem(type: 'row' | 'column', content: LayoutItem[]): LayoutItem {\n return { type, content, size: 1, sizeUnit: 'fr', minSizeUnit: 'px', id: '', isClosable: true };\n}\n\n// ── finding panels ──────────────────────────────────────────────────────────\n\nfunction findPath(node: LayoutItem, panelId: string, path: LayoutPathStep[]): LayoutPathStep[] | null {\n if (node.type === 'component') return node.componentState?.id === panelId ? path : null;\n for (let i = 0; i < node.content.length; i++) {\n const found = findPath(node.content[i], panelId, [...path, { kind: node.type, index: i }]);\n if (found) return found;\n }\n return null;\n}\n\nfunction findComponent(node: LayoutItem | undefined, panelId: string): LayoutItem | null {\n if (!node) return null;\n if (node.type === 'component') return node.componentState?.id === panelId ? node : null;\n for (const child of node.content) {\n const found = findComponent(child, panelId);\n if (found) return found;\n }\n return null;\n}\n\n/** The stack that holds the panel, with its parent container and index. */\nfunction locate(node: LayoutItem, panelId: string, parent: LayoutItem | null = null, indexInParent = -1): PanelLocation | null {\n if (node.type === 'stack') {\n return node.content.some(c => c.componentState?.id === panelId) ? { stack: node, parent, indexInParent } : null;\n }\n if (node.type === 'component') return null;\n for (let i = 0; i < node.content.length; i++) {\n const found = locate(node.content[i], panelId, node, i);\n if (found) return found;\n }\n return null;\n}\n\n// ── inserting ───────────────────────────────────────────────────────────────\n\n/** The root with `newStack` at its right end: appended to a root row, or beside any other root in a new row. */\nfunction appendToRoot(root: LayoutItem, newStack: LayoutItem): LayoutItem {\n const row = root.type === 'row' ? root : groupItem('row', [root]);\n row.content.push(newStack);\n shareEvenly(row.content);\n return row;\n}\n\n/** Adds the item to the stack as its last tab and shows it. */\nfunction addTab(stack: LayoutItem, item: LayoutItem): void {\n stack.content.push(item);\n stack.activeItemIndex = stack.content.length - 1;\n}\n\n/**\n * Puts `newStack` beside the target stack: into the parent when the parent runs along the needed axis,\n * otherwise into a new row or column that takes the target's place and size. The items of the changed\n * container share it evenly. Returns the layout's root, which is the new container when the target was the root.\n */\nfunction splitBeside(root: LayoutItem, location: PanelLocation, newStack: LayoutItem, placement: SidePlacement): LayoutItem {\n const { stack, parent, indexInParent } = location;\n const axis: 'row' | 'column' = placement === 'left' || placement === 'right' ? 'row' : 'column';\n const after = placement === 'right' || placement === 'below';\n if (parent && parent.type === axis) {\n parent.content.splice(indexInParent + (after ? 1 : 0), 0, newStack);\n shareEvenly(parent.content);\n return root;\n }\n const wrapper = groupItem(axis, after ? [stack, newStack] : [newStack, stack]);\n wrapper.size = stack.size;\n wrapper.sizeUnit = stack.sizeUnit;\n shareEvenly(wrapper.content);\n if (!parent) return wrapper;\n parent.content[indexInParent] = wrapper;\n return root;\n}\n\n// ── removing ────────────────────────────────────────────────────────────────\n\n/** Removes the panel's tab from the stack. The stack keeps showing the same tab when that tab remains. */\nfunction removeTab(stack: LayoutItem, panelId: string): void {\n const index = stack.content.findIndex(c => c.componentState?.id === panelId);\n stack.content.splice(index, 1);\n const active = stack.activeItemIndex ?? 0;\n stack.activeItemIndex = clamp(active > index ? active - 1 : active, 0, Math.max(0, stack.content.length - 1));\n}\n\n/**\n * Removes empty stacks and containers and lifts single-child rows and columns. A container that lost\n * an item shares its space evenly unless its sizes still add up to 100%. Returns undefined when nothing is left.\n */\nfunction prune(node: LayoutItem): LayoutItem | undefined {\n if (node.type === 'component') return node;\n if (node.type === 'stack') return node.content.length ? node : undefined;\n const kept = node.content.map(prune).filter((c): c is LayoutItem => !!c);\n if (kept.length === 0) return undefined;\n if (kept.length === 1) {\n const only = kept[0];\n only.size = node.size;\n only.sizeUnit = node.sizeUnit;\n return only;\n }\n if (kept.length < node.content.length) shareEvenlyIfNeeded(kept);\n node.content = kept;\n return node;\n}\n\n// ── sizes ───────────────────────────────────────────────────────────────────\n\n/** Gives each sibling an equal integer percentage; the first child takes the rounding remainder. */\nfunction shareEvenly(items: LayoutItem[]): void {\n const each = Math.floor(100 / items.length);\n items.forEach((item, i) => setPercent(item, i === 0 ? 100 - each * (items.length - 1) : each));\n}\n\nfunction shareEvenlyIfNeeded(items: LayoutItem[]): void {\n const percentTotal = sum(items.map(item => (item.sizeUnit === '%' ? item.size : 0)));\n if (Math.round(percentTotal) !== 100) shareEvenly(items);\n}\n\n/** Sets the panel's share along `axis` in the nearest container of that kind where it has siblings. */\nfunction resizeAlong(root: LayoutItem, path: LayoutPathStep[], axis: 'row' | 'column', pct: number, panelId: string): void {\n const dimension = axis === 'row' ? 'width' : 'height';\n if (!Number.isFinite(pct)) throw new LayoutEditError(`The ${dimension} must be a finite percentage.`);\n const slot = nearestSlot(root, path, axis);\n if (!slot) throw new LayoutEditError(`Panel \"${panelId}\" has no ${dimension} to change: it does not share a ${axis} with other panels.`);\n setShare(slot.container.content, slot.index, pct);\n}\n\n/** The nearest container of kind `axis` on the path that holds more than one item, with the index of the item on the path. */\nfunction nearestSlot(root: LayoutItem, path: LayoutPathStep[], axis: 'row' | 'column'): ItemSlot | null {\n let node = root;\n let slot: ItemSlot | null = null;\n for (const step of path) {\n if (node.type === axis && node.content.length > 1) slot = { container: node, index: step.index };\n node = node.content[step.index];\n }\n return slot;\n}\n\n/**\n * Gives `items[index]` `pct` percent, clamped so that every item keeps at least 5% (less only when\n * more than 20 items share the container), and shares the rest among the other items in proportion\n * to their current shares. All sizes become integer percentages that add up to 100.\n */\nfunction setShare(items: LayoutItem[], index: number, pct: number): void {\n const floor = Math.min(MIN_PCT, Math.floor(100 / items.length));\n const target = clamp(Math.round(pct), floor, Math.min(MAX_PCT, 100 - floor * (items.length - 1)));\n const otherShares = currentShares(items).filter((_, i) => i !== index);\n const otherSizes = apportion(100 - target, otherShares, floor);\n items.filter((_, i) => i !== index).forEach((item, i) => setPercent(item, otherSizes[i]));\n setPercent(items[index], target);\n}\n\n/**\n * Each item's share of its row or column in percent, read the way Golden Layout reads sizes: `%`\n * sizes first, then the `fr` sizes share the space left (50% more when none is left). The shares add up to 100.\n */\nfunction currentShares(items: LayoutItem[]): number[] {\n const sizes = items.map(readSize);\n const percentTotal = sum(sizes.filter(s => s.unit === '%').map(s => s.value));\n const fractionTotal = sum(sizes.filter(s => s.unit === 'fr').map(s => s.value));\n const fractionSpace = Math.round(percentTotal) < 100 ? 100 - percentTotal : 50;\n const amounts = sizes.map(s => (s.unit === '%' ? s.value : (fractionSpace * s.value) / fractionTotal));\n const total = sum(amounts);\n return amounts.map(amount => (amount * 100) / total);\n}\n\n/** An item's size. A missing size, one that is not positive, or one in another unit counts as one `fr`. */\nfunction readSize(item: LayoutItem): ItemSize {\n if (Number.isFinite(item.size) && item.size > 0) {\n if (item.sizeUnit === '%') return { unit: '%', value: item.size };\n if (item.sizeUnit === 'fr') return { unit: 'fr', value: item.size };\n }\n return { unit: 'fr', value: 1 };\n}\n\n/**\n * Splits `total` into integers in proportion to `weights`, with every part at least `floor`. Parts that\n * would fall below `floor` get exactly `floor` and the others share what is left. The caller makes sure\n * that `total` is at least `floor` times the number of parts.\n */\nfunction apportion(total: number, weights: number[], floor: number, atFloor: ReadonlySet<number> = new Set()): number[] {\n const freeTotal = total - floor * atFloor.size;\n const freeWeight = sum(weights.filter((_, i) => !atFloor.has(i)));\n const parts = weights.map((weight, i) => (atFloor.has(i) ? floor : (freeTotal * weight) / freeWeight));\n const below = parts.flatMap((part, i) => (!atFloor.has(i) && part < floor ? [i] : []));\n return below.length > 0 ? apportion(total, weights, floor, new Set([...atFloor, ...below])) : roundToTotal(parts, total);\n}\n\n/** Rounds parts that add up to `total` down to integers, then gives the units left over to the largest remainders. */\nfunction roundToTotal(parts: number[], total: number): number[] {\n const rounded = parts.map(part => Math.floor(part));\n const left = total - sum(rounded);\n const byRemainder = parts.map((_, i) => i).sort((a, b) => parts[b] - rounded[b] - (parts[a] - rounded[a]));\n byRemainder.slice(0, left).forEach(i => rounded[i]++);\n return rounded;\n}\n\nfunction setPercent(item: LayoutItem, pct: number): void {\n item.size = pct;\n item.sizeUnit = '%';\n}\n\nfunction clamp(value: number, low: number, high: number): number {\n return Math.max(low, Math.min(high, value));\n}\n\nfunction sum(values: number[]): number {\n return values.reduce((total, value) => total + value, 0);\n}\n\n// ── describing ──────────────────────────────────────────────────────────────\n\nfunction simplify(node: LayoutItem, sized: boolean): SimplifiedLayoutNode {\n if (node.type === 'component') {\n return { kind: 'panel', panelId: node.componentState?.id, title: node.componentState?.title ?? node.title };\n }\n const out: SimplifiedLayoutNode = { kind: node.type, children: node.content.map(c => simplify(c, true)) };\n if (sized && node.sizeUnit === '%') out.sizePct = Math.round(node.size);\n return out;\n}\n"]}
@@ -0,0 +1,8 @@
1
+ import type { MJConfirmOptions } from '@memberjunction/ng-ui-components';
2
+ /**
3
+ * The texts of the Remove part confirm, for MJConfirmService.ConfirmDelete. Names the part and says
4
+ * that its source is kept. A part without a title is "this part"; a part type other than the four
5
+ * built-in ones gets no detail line.
6
+ */
7
+ export declare function RemovePartConfirmOptions(partTitle: string | null | undefined, partTypeName: string | null | undefined): Omit<MJConfirmOptions, 'type'>;
8
+ //# sourceMappingURL=remove-part-confirm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-part-confirm.d.ts","sourceRoot":"","sources":["../../../src/lib/models/remove-part-confirm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAUzE;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAStJ"}
@@ -0,0 +1,23 @@
1
+ /** What each built-in part type shows, as the Remove part confirm names it. */
2
+ const PART_SOURCE_NOUNS = new Map([
3
+ ['View', 'view'],
4
+ ['Query', 'query'],
5
+ ['Artifact', 'artifact'],
6
+ ['WebURL', 'page'],
7
+ ]);
8
+ /**
9
+ * The texts of the Remove part confirm, for MJConfirmService.ConfirmDelete. Names the part and says
10
+ * that its source is kept. A part without a title is "this part"; a part type other than the four
11
+ * built-in ones gets no detail line.
12
+ */
13
+ export function RemovePartConfirmOptions(partTitle, partTypeName) {
14
+ const title = partTitle?.trim();
15
+ const noun = partTypeName ? PART_SOURCE_NOUNS.get(partTypeName) : undefined;
16
+ return {
17
+ title: 'Remove part',
18
+ message: title ? `Remove "${title}" from this dashboard?` : 'Remove this part from this dashboard?',
19
+ ...(noun ? { detail: `The ${noun} itself is not deleted.` } : {}),
20
+ confirmText: 'Remove',
21
+ };
22
+ }
23
+ //# sourceMappingURL=remove-part-confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-part-confirm.js","sourceRoot":"","sources":["../../../src/lib/models/remove-part-confirm.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAiB;IAC9C,CAAC,MAAM,EAAE,MAAM,CAAC;IAChB,CAAC,OAAO,EAAE,OAAO,CAAC;IAClB,CAAC,UAAU,EAAE,UAAU,CAAC;IACxB,CAAC,QAAQ,EAAE,MAAM,CAAC;CACrB,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,wBAAwB,CAAC,SAAoC,EAAE,YAAuC;IAClH,MAAM,KAAK,GAAG,SAAS,EAAE,IAAI,EAAE,CAAC;IAChC,MAAM,IAAI,GAAG,YAAY,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5E,OAAO;QACH,KAAK,EAAE,aAAa;QACpB,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,KAAK,wBAAwB,CAAC,CAAC,CAAC,uCAAuC;QACnG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,IAAI,yBAAyB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjE,WAAW,EAAE,QAAQ;KACxB,CAAC;AACN,CAAC","sourcesContent":["import type { MJConfirmOptions } from '@memberjunction/ng-ui-components';\n\n/** What each built-in part type shows, as the Remove part confirm names it. */\nconst PART_SOURCE_NOUNS = new Map<string, string>([\n ['View', 'view'],\n ['Query', 'query'],\n ['Artifact', 'artifact'],\n ['WebURL', 'page'],\n]);\n\n/**\n * The texts of the Remove part confirm, for MJConfirmService.ConfirmDelete. Names the part and says\n * that its source is kept. A part without a title is \"this part\"; a part type other than the four\n * built-in ones gets no detail line.\n */\nexport function RemovePartConfirmOptions(partTitle: string | null | undefined, partTypeName: string | null | undefined): Omit<MJConfirmOptions, 'type'> {\n const title = partTitle?.trim();\n const noun = partTypeName ? PART_SOURCE_NOUNS.get(partTypeName) : undefined;\n return {\n title: 'Remove part',\n message: title ? `Remove \"${title}\" from this dashboard?` : 'Remove this part from this dashboard?',\n ...(noun ? { detail: `The ${noun} itself is not deleted.` } : {}),\n confirmText: 'Remove',\n };\n}\n"]}
@@ -259,7 +259,7 @@ let ArtifactPartComponent = class ArtifactPartComponent extends BaseDashboardPar
259
259
  this.VersionNumber = undefined;
260
260
  }
261
261
  static ɵfac = function ArtifactPartComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || ArtifactPartComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef), i0.ɵɵdirectiveInject(i1.AnalyzeArtifactService)); };
262
- static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ArtifactPartComponent, selectors: [["mj-artifact-part"]], inputs: { CurrentUser: "CurrentUser", EnvironmentId: "EnvironmentId" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 8, consts: [[1, "artifact-part"], [1, "loading-state"], ["Variant", "error", "Icon", "fa-solid fa-triangle-exclamation", "Title", "Couldn't load artifact", "Size", "compact", 1, "part-placeholder", 3, "Message"], ["Icon", "fa-solid fa-palette", "Title", "No Artifact Selected", "Message", "Click the configure button to select an artifact for this part.", "Size", "compact", 1, "part-placeholder"], [3, "artifactId", "currentUser", "environmentId", "versionNumber", "showSaveToCollection", "showHeader", "showTabs", "showCloseButton", "showMaximizeButton", "viewContext", "canShare", "canEdit", "isMaximized", "refreshTrigger"], ["text", "Loading artifact..."], [3, "navigateToLink", "openEntityRecord", "navigationRequest", "analyzeRequested", "artifactId", "currentUser", "environmentId", "versionNumber", "showSaveToCollection", "showHeader", "showTabs", "showCloseButton", "showMaximizeButton", "viewContext", "canShare", "canEdit", "isMaximized", "refreshTrigger"]], template: function ArtifactPartComponent_Template(rf, ctx) { if (rf & 1) {
262
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: ArtifactPartComponent, selectors: [["mj-artifact-part"]], inputs: { CurrentUser: "CurrentUser", EnvironmentId: "EnvironmentId" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 5, vars: 8, consts: [[1, "artifact-part"], [1, "loading-state"], ["Variant", "error", "Icon", "fa-solid fa-triangle-exclamation", "Title", "Couldn't load artifact", "Size", "compact", 1, "part-placeholder", 3, "Message"], ["Icon", "fa-solid fa-palette", "Title", "No Artifact Selected", "Message", "Use Edit part to choose an artifact.", "Size", "compact", 1, "part-placeholder"], [3, "artifactId", "currentUser", "environmentId", "versionNumber", "showSaveToCollection", "showHeader", "showTabs", "showCloseButton", "showMaximizeButton", "viewContext", "canShare", "canEdit", "isMaximized", "refreshTrigger"], ["text", "Loading artifact..."], [3, "navigateToLink", "openEntityRecord", "navigationRequest", "analyzeRequested", "artifactId", "currentUser", "environmentId", "versionNumber", "showSaveToCollection", "showHeader", "showTabs", "showCloseButton", "showMaximizeButton", "viewContext", "canShare", "canEdit", "isMaximized", "refreshTrigger"]], template: function ArtifactPartComponent_Template(rf, ctx) { if (rf & 1) {
263
263
  i0.ɵɵelementStart(0, "div", 0);
264
264
  i0.ɵɵconditionalCreate(1, ArtifactPartComponent_Conditional_1_Template, 2, 0, "div", 1);
265
265
  i0.ɵɵconditionalCreate(2, ArtifactPartComponent_Conditional_2_Template, 1, 1, "mj-empty-state", 2);
@@ -310,7 +310,7 @@ export { ArtifactPartComponent };
310
310
  class="part-placeholder"
311
311
  Icon="fa-solid fa-palette"
312
312
  Title="No Artifact Selected"
313
- Message="Click the configure button to select an artifact for this part."
313
+ Message="Use Edit part to choose an artifact."
314
314
  Size="compact" />
315
315
  }
316
316