@richardmcquiston01/dashboard-widgets-toolkit 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.cjs +2225 -1904
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +57 -3
- package/dist/core.d.ts +57 -3
- package/dist/core.js +2211 -1905
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +2225 -1904
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2211 -1905
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +381 -23
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +30 -2
- package/dist/react.d.ts +30 -2
- package/dist/react.js +381 -23
- package/dist/react.js.map +1 -1
- package/dist/{storage-DwJ-vGJg.d.cts → storage-CRBfjiVZ.d.cts} +184 -62
- package/dist/{storage-DwJ-vGJg.d.ts → storage-CRBfjiVZ.d.ts} +184 -62
- package/package.json +1 -1
package/dist/core.cjs
CHANGED
|
@@ -1,88 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
// src/core/payload.ts
|
|
4
|
-
var WIDGET_KINDS = [
|
|
5
|
-
"TEXT",
|
|
6
|
-
"KPI",
|
|
7
|
-
"GAUGE",
|
|
8
|
-
"TABLE",
|
|
9
|
-
"BAR_LIST",
|
|
10
|
-
"ALERT_LIST",
|
|
11
|
-
"GRAPH"
|
|
12
|
-
];
|
|
13
|
-
function isWidgetKind(value) {
|
|
14
|
-
return typeof value === "string" && WIDGET_KINDS.includes(value);
|
|
15
|
-
}
|
|
16
|
-
var VALUE_FORMATS = ["number", "currency", "percent"];
|
|
17
|
-
var MAX_GRAPH_SERIES = 8;
|
|
18
|
-
var DEFAULT_EMPTY_TEXT = "Nothing to show yet.";
|
|
19
|
-
function emptyWidget(text = DEFAULT_EMPTY_TEXT) {
|
|
20
|
-
return { empty: true, text };
|
|
21
|
-
}
|
|
22
|
-
function isWidgetEmptyState(payload) {
|
|
23
|
-
return "empty" in payload && payload.empty === true;
|
|
24
|
-
}
|
|
25
|
-
function emptyTextFor(payload) {
|
|
26
|
-
if (isWidgetEmptyState(payload)) {
|
|
27
|
-
return payload.text;
|
|
28
|
-
}
|
|
29
|
-
switch (payload.kind) {
|
|
30
|
-
case "TABLE":
|
|
31
|
-
return payload.rows.length === 0 ? DEFAULT_EMPTY_TEXT : null;
|
|
32
|
-
case "BAR_LIST":
|
|
33
|
-
return payload.items.length === 0 ? DEFAULT_EMPTY_TEXT : null;
|
|
34
|
-
case "ALERT_LIST":
|
|
35
|
-
return payload.items.length === 0 ? payload.emptyText : null;
|
|
36
|
-
case "GRAPH":
|
|
37
|
-
return payload.series.every((series) => series.points.length === 0) ? DEFAULT_EMPTY_TEXT : null;
|
|
38
|
-
case "TEXT":
|
|
39
|
-
case "KPI":
|
|
40
|
-
case "GAUGE":
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// src/core/compat.ts
|
|
46
|
-
function isStringArray(value) {
|
|
47
|
-
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
48
|
-
}
|
|
49
|
-
function upgradeLegacyWidgetData(value) {
|
|
50
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
const record = value;
|
|
54
|
-
if (record["kind"] === "TABLE" && isStringArray(record["columns"]) && Array.isArray(record["rows"]) && record["rows"].every(isStringArray)) {
|
|
55
|
-
const rows = record["rows"];
|
|
56
|
-
return {
|
|
57
|
-
...record,
|
|
58
|
-
columns: record["columns"].map((label) => ({ label })),
|
|
59
|
-
rows: rows.map((row) => row.map((text) => ({ text })))
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
if (record["kind"] === "GRAPH" && Array.isArray(record["points"]) && record["series"] === void 0) {
|
|
63
|
-
const { points, ...rest } = record;
|
|
64
|
-
return {
|
|
65
|
-
...rest,
|
|
66
|
-
series: [{ name: "Value", points }],
|
|
67
|
-
chartType: "bar",
|
|
68
|
-
valueFormat: "number"
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
return value;
|
|
72
|
-
}
|
|
73
|
-
function fromLegacyDefinition(legacy) {
|
|
74
|
-
if (!isWidgetKind(legacy.viewType)) {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
key: legacy.widgetKey,
|
|
79
|
-
title: legacy.title,
|
|
80
|
-
...legacy.description === null ? {} : { description: legacy.description },
|
|
81
|
-
kind: legacy.viewType,
|
|
82
|
-
sortOrder: legacy.sortOrder
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
3
|
// src/core/definition.ts
|
|
87
4
|
var WIDGET_SIZES = ["small", "medium", "large", "full"];
|
|
88
5
|
var WIDGET_FILLS = ["height", "width", "both"];
|
|
@@ -90,13 +7,15 @@ var NOT_LOCKED = Object.freeze({
|
|
|
90
7
|
move: false,
|
|
91
8
|
hide: false,
|
|
92
9
|
minimize: false,
|
|
93
|
-
options: false
|
|
10
|
+
options: false,
|
|
11
|
+
clone: false
|
|
94
12
|
});
|
|
95
13
|
var FULLY_LOCKED = Object.freeze({
|
|
96
14
|
move: true,
|
|
97
15
|
hide: true,
|
|
98
16
|
minimize: true,
|
|
99
|
-
options: true
|
|
17
|
+
options: true,
|
|
18
|
+
clone: true
|
|
100
19
|
});
|
|
101
20
|
function resolveWidgetLock(definition) {
|
|
102
21
|
const setting = definition.locked;
|
|
@@ -106,7 +25,8 @@ function resolveWidgetLock(definition) {
|
|
|
106
25
|
move: setting.move === true,
|
|
107
26
|
hide: setting.hide === true,
|
|
108
27
|
minimize: setting.minimize === true,
|
|
109
|
-
options: setting.options === true
|
|
28
|
+
options: setting.options === true,
|
|
29
|
+
clone: setting.clone === true
|
|
110
30
|
};
|
|
111
31
|
}
|
|
112
32
|
function defineWidget(definition) {
|
|
@@ -130,6 +50,103 @@ function isWidgetVisibleTo(definition, roles) {
|
|
|
130
50
|
return definition.roles.some((role) => roles.includes(role));
|
|
131
51
|
}
|
|
132
52
|
|
|
53
|
+
// src/core/grid.ts
|
|
54
|
+
var WIDTH_COLUMNS = 12;
|
|
55
|
+
var MIN_WIDGET_WIDTH = 2;
|
|
56
|
+
var MAX_WIDGET_WIDTH = 12;
|
|
57
|
+
function isWidgetWidth(value) {
|
|
58
|
+
return typeof value === "number" && Number.isInteger(value) && value >= MIN_WIDGET_WIDTH && value <= MAX_WIDGET_WIDTH;
|
|
59
|
+
}
|
|
60
|
+
function widthForSize(size) {
|
|
61
|
+
switch (size) {
|
|
62
|
+
case "small":
|
|
63
|
+
return 3;
|
|
64
|
+
case "large":
|
|
65
|
+
return 6;
|
|
66
|
+
case "full":
|
|
67
|
+
return 12;
|
|
68
|
+
default:
|
|
69
|
+
return 4;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function itemWidth(item) {
|
|
73
|
+
return isWidgetWidth(item.width) ? item.width : widthForSize(item.defaultSize);
|
|
74
|
+
}
|
|
75
|
+
function usesWidthColumns(items) {
|
|
76
|
+
return items.some((item) => item.width !== void 0);
|
|
77
|
+
}
|
|
78
|
+
function fillsWidth(fill) {
|
|
79
|
+
return fill === "width" || fill === "both";
|
|
80
|
+
}
|
|
81
|
+
function fillsHeight(fill) {
|
|
82
|
+
return fill === "height" || fill === "both";
|
|
83
|
+
}
|
|
84
|
+
function baseColumnSpan(size, columns, largeSpan = 2) {
|
|
85
|
+
if (size === "full") return columns;
|
|
86
|
+
if (size === "large") return Math.min(largeSpan, columns);
|
|
87
|
+
return 1;
|
|
88
|
+
}
|
|
89
|
+
function fillColumnSpans(items, columns, largeSpan = 2) {
|
|
90
|
+
return placeAndFill(
|
|
91
|
+
items,
|
|
92
|
+
columns,
|
|
93
|
+
(item, trackCount) => baseColumnSpan(item.defaultSize, trackCount, largeSpan)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
function fillWidthSpans(items) {
|
|
97
|
+
return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
|
|
98
|
+
}
|
|
99
|
+
function placeRows(items) {
|
|
100
|
+
return placeAndFillRows(items, WIDTH_COLUMNS, itemWidth).rows;
|
|
101
|
+
}
|
|
102
|
+
function fitCount(items, maxRows) {
|
|
103
|
+
const rows = placeRows(items);
|
|
104
|
+
let count = 0;
|
|
105
|
+
for (const row of rows.slice(0, Math.max(0, Math.floor(maxRows)))) {
|
|
106
|
+
count += row.keys.length;
|
|
107
|
+
}
|
|
108
|
+
return count;
|
|
109
|
+
}
|
|
110
|
+
function placeAndFill(items, columns, spanOf) {
|
|
111
|
+
return placeAndFillRows(items, columns, spanOf).spans;
|
|
112
|
+
}
|
|
113
|
+
function placeAndFillRows(items, columns, spanOf) {
|
|
114
|
+
const trackCount = Math.max(1, Math.floor(columns));
|
|
115
|
+
const spans = /* @__PURE__ */ new Map();
|
|
116
|
+
const rows = [];
|
|
117
|
+
let row = [];
|
|
118
|
+
let used = 0;
|
|
119
|
+
function closeRow() {
|
|
120
|
+
if (row.length === 0) return;
|
|
121
|
+
const fillers = row.filter(({ item }) => fillsWidth(item.fill));
|
|
122
|
+
const leftover = trackCount - used;
|
|
123
|
+
const finalSpans = /* @__PURE__ */ new Map();
|
|
124
|
+
if (fillers.length > 0) {
|
|
125
|
+
const share = Math.floor(leftover / fillers.length);
|
|
126
|
+
const remainder = leftover % fillers.length;
|
|
127
|
+
fillers.forEach(({ item, span }, index) => {
|
|
128
|
+
const grown = span + share + (index < remainder ? 1 : 0);
|
|
129
|
+
spans.set(item.key, grown);
|
|
130
|
+
finalSpans.set(item.key, grown);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
rows.push({
|
|
134
|
+
keys: row.map(({ item }) => item.key),
|
|
135
|
+
spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
|
|
136
|
+
});
|
|
137
|
+
row = [];
|
|
138
|
+
used = 0;
|
|
139
|
+
}
|
|
140
|
+
for (const item of items) {
|
|
141
|
+
const span = Math.min(spanOf(item, trackCount), trackCount);
|
|
142
|
+
if (used + span > trackCount && row.length > 0) closeRow();
|
|
143
|
+
row.push({ item, span });
|
|
144
|
+
used += span;
|
|
145
|
+
}
|
|
146
|
+
closeRow();
|
|
147
|
+
return { spans, rows };
|
|
148
|
+
}
|
|
149
|
+
|
|
133
150
|
// src/core/format.ts
|
|
134
151
|
var DEFAULT_CURRENCY = "USD";
|
|
135
152
|
function localeArgument(locale) {
|
|
@@ -215,6 +232,48 @@ function kpiDelta(current, previous, options = {}) {
|
|
|
215
232
|
};
|
|
216
233
|
}
|
|
217
234
|
|
|
235
|
+
// src/core/payload.ts
|
|
236
|
+
var WIDGET_KINDS = [
|
|
237
|
+
"TEXT",
|
|
238
|
+
"KPI",
|
|
239
|
+
"GAUGE",
|
|
240
|
+
"TABLE",
|
|
241
|
+
"BAR_LIST",
|
|
242
|
+
"ALERT_LIST",
|
|
243
|
+
"GRAPH"
|
|
244
|
+
];
|
|
245
|
+
function isWidgetKind(value) {
|
|
246
|
+
return typeof value === "string" && WIDGET_KINDS.includes(value);
|
|
247
|
+
}
|
|
248
|
+
var VALUE_FORMATS = ["number", "currency", "percent"];
|
|
249
|
+
var MAX_GRAPH_SERIES = 8;
|
|
250
|
+
var DEFAULT_EMPTY_TEXT = "Nothing to show yet.";
|
|
251
|
+
function emptyWidget(text = DEFAULT_EMPTY_TEXT) {
|
|
252
|
+
return { empty: true, text };
|
|
253
|
+
}
|
|
254
|
+
function isWidgetEmptyState(payload) {
|
|
255
|
+
return "empty" in payload && payload.empty === true;
|
|
256
|
+
}
|
|
257
|
+
function emptyTextFor(payload) {
|
|
258
|
+
if (isWidgetEmptyState(payload)) {
|
|
259
|
+
return payload.text;
|
|
260
|
+
}
|
|
261
|
+
switch (payload.kind) {
|
|
262
|
+
case "TABLE":
|
|
263
|
+
return payload.rows.length === 0 ? DEFAULT_EMPTY_TEXT : null;
|
|
264
|
+
case "BAR_LIST":
|
|
265
|
+
return payload.items.length === 0 ? DEFAULT_EMPTY_TEXT : null;
|
|
266
|
+
case "ALERT_LIST":
|
|
267
|
+
return payload.items.length === 0 ? payload.emptyText : null;
|
|
268
|
+
case "GRAPH":
|
|
269
|
+
return payload.series.every((series) => series.points.length === 0) ? DEFAULT_EMPTY_TEXT : null;
|
|
270
|
+
case "TEXT":
|
|
271
|
+
case "KPI":
|
|
272
|
+
case "GAUGE":
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
218
277
|
// src/core/result.ts
|
|
219
278
|
function ok(value) {
|
|
220
279
|
return { ok: true, value };
|
|
@@ -237,20 +296,308 @@ function describeUnknownError(cause) {
|
|
|
237
296
|
}
|
|
238
297
|
}
|
|
239
298
|
|
|
240
|
-
// src/core/
|
|
241
|
-
var
|
|
242
|
-
var
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
299
|
+
// src/core/views.ts
|
|
300
|
+
var BASE_VIEW_KEY = "base";
|
|
301
|
+
var MAX_WIDGET_VIEWS = 8;
|
|
302
|
+
var VIEW_CONVERSIONS = {
|
|
303
|
+
tableToBarList: { from: "TABLE", to: "BAR_LIST" },
|
|
304
|
+
tableToGraph: { from: "TABLE", to: "GRAPH" },
|
|
305
|
+
barListToTable: { from: "BAR_LIST", to: "TABLE" },
|
|
306
|
+
graphToTable: { from: "GRAPH", to: "TABLE" },
|
|
307
|
+
barListToGraph: { from: "BAR_LIST", to: "GRAPH" }
|
|
308
|
+
};
|
|
309
|
+
var KIND_LABELS = {
|
|
310
|
+
TEXT: "Text",
|
|
311
|
+
KPI: "Number",
|
|
312
|
+
GAUGE: "Gauge",
|
|
313
|
+
TABLE: "Table",
|
|
314
|
+
BAR_LIST: "Bars",
|
|
315
|
+
ALERT_LIST: "List",
|
|
316
|
+
GRAPH: "Chart"
|
|
317
|
+
};
|
|
318
|
+
function kindLabel(kind) {
|
|
319
|
+
return KIND_LABELS[kind];
|
|
248
320
|
}
|
|
249
|
-
function
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
321
|
+
function hasViews(item) {
|
|
322
|
+
return item.views !== void 0 && item.views.length > 0;
|
|
323
|
+
}
|
|
324
|
+
function findView(item, key) {
|
|
325
|
+
return item.views?.find((view) => view.key === key);
|
|
326
|
+
}
|
|
327
|
+
function viewChoices(item) {
|
|
328
|
+
if (!hasViews(item)) return [];
|
|
329
|
+
return [
|
|
330
|
+
{
|
|
331
|
+
key: BASE_VIEW_KEY,
|
|
332
|
+
label: item.baseViewLabel ?? kindLabel(item.kind),
|
|
333
|
+
kind: item.kind
|
|
334
|
+
},
|
|
335
|
+
...(item.views ?? []).map(({ key, label, kind }) => ({
|
|
336
|
+
key,
|
|
337
|
+
label,
|
|
338
|
+
kind
|
|
339
|
+
}))
|
|
340
|
+
];
|
|
341
|
+
}
|
|
342
|
+
function isProviderView(view) {
|
|
343
|
+
return view.convert === void 0;
|
|
344
|
+
}
|
|
345
|
+
function isKnownView(item, key) {
|
|
346
|
+
return key !== void 0 && (key === BASE_VIEW_KEY || findView(item, key) !== void 0);
|
|
347
|
+
}
|
|
348
|
+
function effectiveViewKey(item, chosen) {
|
|
349
|
+
if (!hasViews(item)) return BASE_VIEW_KEY;
|
|
350
|
+
if (isKnownView(item, chosen)) return chosen;
|
|
351
|
+
if (isKnownView(item, item.defaultView)) return item.defaultView;
|
|
352
|
+
return BASE_VIEW_KEY;
|
|
353
|
+
}
|
|
354
|
+
function viewKind(item, chosen) {
|
|
355
|
+
return findView(item, effectiveViewKey(item, chosen))?.kind ?? item.kind;
|
|
356
|
+
}
|
|
357
|
+
function loadViewKey(item, chosen) {
|
|
358
|
+
const key = effectiveViewKey(item, chosen);
|
|
359
|
+
const view = findView(item, key);
|
|
360
|
+
return view !== void 0 && isProviderView(view) ? key : BASE_VIEW_KEY;
|
|
361
|
+
}
|
|
362
|
+
function loadViewKind(item, chosen) {
|
|
363
|
+
return findView(item, loadViewKey(item, chosen))?.kind ?? item.kind;
|
|
364
|
+
}
|
|
365
|
+
function cellValue(cell) {
|
|
366
|
+
const value = cell?.value;
|
|
367
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
368
|
+
}
|
|
369
|
+
function columnName(table, index) {
|
|
370
|
+
return table.columns[index]?.label ?? `#${index + 1}`;
|
|
371
|
+
}
|
|
372
|
+
function checkColumn(table, index, field, context) {
|
|
373
|
+
if (!Number.isInteger(index) || index < 0 || index >= table.columns.length) {
|
|
374
|
+
return `${context.where}: ${field} column ${index} does not exist (the table has ${table.columns.length} columns, numbered from 0).`;
|
|
375
|
+
}
|
|
376
|
+
return void 0;
|
|
377
|
+
}
|
|
378
|
+
function numericColumn(table, index, context) {
|
|
379
|
+
const numbers = [];
|
|
380
|
+
for (const [rowIndex, row] of table.rows.entries()) {
|
|
381
|
+
const value = cellValue(row[index]);
|
|
382
|
+
if (value === void 0) {
|
|
383
|
+
return err(
|
|
384
|
+
`${context.where}: row ${rowIndex + 1}, column ${index + 1} ("${columnName(table, index)}") has no numeric value. Add value to the cells.`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
numbers.push(value);
|
|
388
|
+
}
|
|
389
|
+
return ok(numbers);
|
|
390
|
+
}
|
|
391
|
+
function tableToBarList(table, conversion, context) {
|
|
392
|
+
const bad = checkColumn(table, conversion.label, "label", context) ?? checkColumn(table, conversion.value, "value", context);
|
|
393
|
+
if (bad !== void 0) return err(bad);
|
|
394
|
+
const numbers = numericColumn(table, conversion.value, context);
|
|
395
|
+
if (!numbers.ok) return numbers;
|
|
396
|
+
const items = table.rows.map((row, index) => ({
|
|
397
|
+
label: row[conversion.label]?.text ?? "",
|
|
398
|
+
value: numbers.value[index],
|
|
399
|
+
display: row[conversion.value]?.text ?? String(numbers.value[index])
|
|
400
|
+
}));
|
|
401
|
+
return ok({ kind: "BAR_LIST", items });
|
|
402
|
+
}
|
|
403
|
+
function tableToGraph(table, conversion, context) {
|
|
404
|
+
if (conversion.values.length === 0 || conversion.values.length > MAX_GRAPH_SERIES) {
|
|
405
|
+
return err(
|
|
406
|
+
`${context.where}: a chart needs 1 to ${MAX_GRAPH_SERIES} series columns, got ${conversion.values.length}.`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
let bad = checkColumn(
|
|
410
|
+
table,
|
|
411
|
+
conversion.label,
|
|
412
|
+
"label",
|
|
413
|
+
context
|
|
414
|
+
);
|
|
415
|
+
for (const index of conversion.values) {
|
|
416
|
+
bad ??= checkColumn(table, index, "series", context);
|
|
417
|
+
}
|
|
418
|
+
if (bad !== void 0) return err(bad);
|
|
419
|
+
const series = [];
|
|
420
|
+
for (const index of conversion.values) {
|
|
421
|
+
const numbers = numericColumn(table, index, context);
|
|
422
|
+
if (!numbers.ok) return numbers;
|
|
423
|
+
series.push({
|
|
424
|
+
name: columnName(table, index),
|
|
425
|
+
points: table.rows.map((row, rowIndex) => ({
|
|
426
|
+
label: row[conversion.label]?.text ?? "",
|
|
427
|
+
value: numbers.value[rowIndex]
|
|
428
|
+
}))
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
return ok({
|
|
432
|
+
kind: "GRAPH",
|
|
433
|
+
series,
|
|
434
|
+
chartType: conversion.chartType,
|
|
435
|
+
valueFormat: conversion.valueFormat ?? "number",
|
|
436
|
+
...conversion.currency === void 0 ? {} : { currency: conversion.currency },
|
|
437
|
+
xLabel: columnName(table, conversion.label)
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function barListToTable(list, conversion) {
|
|
441
|
+
return ok({
|
|
442
|
+
kind: "TABLE",
|
|
443
|
+
columns: [
|
|
444
|
+
{ label: conversion.labelHeading ?? "Name" },
|
|
445
|
+
{ label: conversion.valueHeading ?? "Value", numeric: true }
|
|
446
|
+
],
|
|
447
|
+
rows: list.items.map((item) => [
|
|
448
|
+
{ text: item.label },
|
|
449
|
+
{ text: item.display ?? String(item.value), value: item.value }
|
|
450
|
+
])
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
function graphToTable(graph, context) {
|
|
454
|
+
const first = graph.series[0];
|
|
455
|
+
if (first === void 0)
|
|
456
|
+
return err(`${context.where}: the chart has no series.`);
|
|
457
|
+
const format = {
|
|
458
|
+
...graph.currency === void 0 ? {} : { currency: graph.currency },
|
|
459
|
+
...context.locale === void 0 ? {} : { locale: context.locale }
|
|
460
|
+
};
|
|
461
|
+
return ok({
|
|
462
|
+
kind: "TABLE",
|
|
463
|
+
columns: [
|
|
464
|
+
{ label: graph.xLabel ?? "Label" },
|
|
465
|
+
...graph.series.map((series) => ({ label: series.name, numeric: true }))
|
|
466
|
+
],
|
|
467
|
+
rows: first.points.map((point, index) => [
|
|
468
|
+
{ text: point.label },
|
|
469
|
+
...graph.series.map((series) => {
|
|
470
|
+
const value = series.points[index]?.value;
|
|
471
|
+
return value === void 0 ? { text: "\u2014" } : { text: formatValue(value, graph.valueFormat, format), value };
|
|
472
|
+
})
|
|
473
|
+
])
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
function barListToGraph(list, conversion) {
|
|
477
|
+
return ok({
|
|
478
|
+
kind: "GRAPH",
|
|
479
|
+
series: [
|
|
480
|
+
{
|
|
481
|
+
name: conversion.seriesName ?? "Value",
|
|
482
|
+
points: list.items.map((item) => ({
|
|
483
|
+
label: item.label,
|
|
484
|
+
value: item.value
|
|
485
|
+
}))
|
|
486
|
+
}
|
|
487
|
+
],
|
|
488
|
+
chartType: conversion.chartType,
|
|
489
|
+
valueFormat: conversion.valueFormat ?? "number",
|
|
490
|
+
...conversion.currency === void 0 ? {} : { currency: conversion.currency }
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
function convertView(widgetKey, view, data, options = {}) {
|
|
494
|
+
const conversion = view.convert;
|
|
495
|
+
const context = {
|
|
496
|
+
where: `Widget ${JSON.stringify(widgetKey)} view ${JSON.stringify(view.key)}`,
|
|
497
|
+
...options.locale === void 0 ? {} : { locale: options.locale }
|
|
498
|
+
};
|
|
499
|
+
if (conversion === void 0) {
|
|
500
|
+
return err(
|
|
501
|
+
`${context.where}: the provider supplies this view, so there is nothing to convert.`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
const expected = VIEW_CONVERSIONS[conversion.type]?.from;
|
|
505
|
+
if (expected === void 0) {
|
|
506
|
+
return err(
|
|
507
|
+
`${context.where}: unknown conversion type ${JSON.stringify(conversion.type)}.`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
if (data.kind !== expected) {
|
|
511
|
+
return err(
|
|
512
|
+
`${context.where}: ${conversion.type} reads a ${expected} payload, but the widget returned ${data.kind}.`
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
switch (conversion.type) {
|
|
516
|
+
case "tableToBarList":
|
|
517
|
+
return tableToBarList(data, conversion, context);
|
|
518
|
+
case "tableToGraph":
|
|
519
|
+
return tableToGraph(data, conversion, context);
|
|
520
|
+
case "barListToTable":
|
|
521
|
+
return barListToTable(data, conversion);
|
|
522
|
+
case "graphToTable":
|
|
523
|
+
return graphToTable(data, context);
|
|
524
|
+
case "barListToGraph":
|
|
525
|
+
return barListToGraph(data, conversion);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
function viewNote(view, data) {
|
|
529
|
+
const conversion = view.convert;
|
|
530
|
+
if (data?.kind !== "TABLE" || conversion === void 0) return "";
|
|
531
|
+
let indexes;
|
|
532
|
+
if (conversion.type === "tableToBarList") {
|
|
533
|
+
indexes = [conversion.label, conversion.value];
|
|
534
|
+
} else if (conversion.type === "tableToGraph") {
|
|
535
|
+
indexes = [conversion.label, ...conversion.values];
|
|
536
|
+
} else {
|
|
537
|
+
return "";
|
|
538
|
+
}
|
|
539
|
+
const names = indexes.map(
|
|
540
|
+
(index) => data.columns[index]?.label ?? `#${index + 1}`
|
|
541
|
+
);
|
|
542
|
+
return `Shows ${names.join(", ")} only.`;
|
|
543
|
+
}
|
|
544
|
+
function applyView(widget, chosen, options = {}) {
|
|
545
|
+
const key = effectiveViewKey(widget.definition, chosen);
|
|
546
|
+
const view = findView(widget.definition, key);
|
|
547
|
+
const wanted = loadViewKey(widget.definition, chosen);
|
|
548
|
+
const loaded = widget.status === "ok" ? widget.view ?? BASE_VIEW_KEY : void 0;
|
|
549
|
+
const mismatched = loaded !== void 0 && loaded !== wanted;
|
|
550
|
+
if (view === void 0) {
|
|
551
|
+
return mismatched ? {
|
|
552
|
+
definition: widget.definition,
|
|
553
|
+
status: "loading",
|
|
554
|
+
...widget.options === void 0 ? {} : { options: widget.options }
|
|
555
|
+
} : widget;
|
|
556
|
+
}
|
|
557
|
+
const definition = {
|
|
558
|
+
...widget.definition,
|
|
559
|
+
kind: view.kind
|
|
560
|
+
};
|
|
561
|
+
if (widget.status !== "ok") return { ...widget, definition };
|
|
562
|
+
if (mismatched) {
|
|
563
|
+
return {
|
|
564
|
+
definition,
|
|
565
|
+
status: "loading",
|
|
566
|
+
...widget.options === void 0 ? {} : { options: widget.options }
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (isProviderView(view)) return { ...widget, definition };
|
|
570
|
+
const converted = convertView(
|
|
571
|
+
widget.definition.key,
|
|
572
|
+
view,
|
|
573
|
+
widget.data,
|
|
574
|
+
options
|
|
575
|
+
);
|
|
576
|
+
if (!converted.ok) {
|
|
577
|
+
return {
|
|
578
|
+
definition,
|
|
579
|
+
status: "error",
|
|
580
|
+
error: converted.error,
|
|
581
|
+
...widget.options === void 0 ? {} : { options: widget.options }
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return { ...widget, definition, data: converted.value };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/core/url.ts
|
|
588
|
+
var SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):/i;
|
|
589
|
+
var IGNORED_IN_SCHEME = /[\u0000- \u007f]/g;
|
|
590
|
+
function schemeOf(url) {
|
|
591
|
+
const match = SCHEME_PATTERN.exec(
|
|
592
|
+
url.replace(IGNORED_IN_SCHEME, "")
|
|
593
|
+
);
|
|
594
|
+
return match === null ? null : (match[1] ?? "").toLowerCase();
|
|
595
|
+
}
|
|
596
|
+
function isSafeHref(url) {
|
|
597
|
+
if (url.trim() === "") {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
const scheme = schemeOf(url);
|
|
254
601
|
return scheme === null || scheme === "http" || scheme === "https" || scheme === "mailto";
|
|
255
602
|
}
|
|
256
603
|
function isSafeImageUrl(url) {
|
|
@@ -291,15 +638,15 @@ function defaultDetailQuery(pageSize = DEFAULT_DETAIL_PAGE_SIZE) {
|
|
|
291
638
|
function fold(text) {
|
|
292
639
|
return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
|
293
640
|
}
|
|
294
|
-
function
|
|
641
|
+
function cellValue2(cell) {
|
|
295
642
|
return cell.value ?? cell.text;
|
|
296
643
|
}
|
|
297
644
|
function cellMatches(cell, needle) {
|
|
298
|
-
return fold(String(
|
|
645
|
+
return fold(String(cellValue2(cell))).includes(needle);
|
|
299
646
|
}
|
|
300
647
|
function compareCells(a, b, collator) {
|
|
301
|
-
const left =
|
|
302
|
-
const right =
|
|
648
|
+
const left = cellValue2(a);
|
|
649
|
+
const right = cellValue2(b);
|
|
303
650
|
if (typeof left === "number" && typeof right === "number") {
|
|
304
651
|
return left - right;
|
|
305
652
|
}
|
|
@@ -591,1492 +938,1878 @@ function deriveDetailData(data, locale) {
|
|
|
591
938
|
return void 0;
|
|
592
939
|
}
|
|
593
940
|
|
|
594
|
-
// src/core/
|
|
595
|
-
var
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
941
|
+
// src/core/options.ts
|
|
942
|
+
var DATE_RANGE_PRESETS = [
|
|
943
|
+
"today",
|
|
944
|
+
"yesterday",
|
|
945
|
+
"last7",
|
|
946
|
+
"last30",
|
|
947
|
+
"last90",
|
|
948
|
+
"thisMonth",
|
|
949
|
+
"lastMonth",
|
|
950
|
+
"thisYear",
|
|
951
|
+
"lastYear"
|
|
952
|
+
];
|
|
953
|
+
var WIDGET_OPTION_TYPES = [
|
|
954
|
+
"choice",
|
|
955
|
+
"number",
|
|
956
|
+
"boolean",
|
|
957
|
+
"text",
|
|
958
|
+
"dateRange",
|
|
959
|
+
"color",
|
|
960
|
+
"sort",
|
|
961
|
+
"columns"
|
|
962
|
+
];
|
|
963
|
+
var MAX_WIDGET_OPTIONS = 20;
|
|
964
|
+
var MAX_TEXT_OPTION_LENGTH = 500;
|
|
965
|
+
var OPTION_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,39}$/;
|
|
966
|
+
var COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
967
|
+
var INTERVAL_PATTERN = /^(\d{4}-\d{2}-\d{2})\/(\d{4}-\d{2}-\d{2})$/;
|
|
968
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
969
|
+
function isColumnKeyFor(kind, key) {
|
|
970
|
+
if (kind === "TABLE") return /^c\d+$/.test(key);
|
|
971
|
+
if (kind === "BAR_LIST") return key === "label" || key === "value";
|
|
972
|
+
return false;
|
|
618
973
|
}
|
|
619
|
-
function
|
|
620
|
-
return
|
|
974
|
+
function isClientApplied(option) {
|
|
975
|
+
return (option.type === "sort" || option.type === "columns") && option.apply === "client";
|
|
621
976
|
}
|
|
622
|
-
function
|
|
623
|
-
|
|
977
|
+
function civilToday(context) {
|
|
978
|
+
const now = context.now ?? Date.now();
|
|
979
|
+
let timeZone = context.timeZone ?? "UTC";
|
|
980
|
+
try {
|
|
981
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
982
|
+
} catch {
|
|
983
|
+
timeZone = "UTC";
|
|
984
|
+
}
|
|
985
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
986
|
+
timeZone,
|
|
987
|
+
year: "numeric",
|
|
988
|
+
month: "2-digit",
|
|
989
|
+
day: "2-digit"
|
|
990
|
+
}).formatToParts(new Date(now));
|
|
991
|
+
const pick = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
|
|
992
|
+
return { year: pick("year"), month: pick("month"), day: pick("day") };
|
|
624
993
|
}
|
|
625
|
-
function
|
|
626
|
-
|
|
627
|
-
if (size === "large") return Math.min(largeSpan, columns);
|
|
628
|
-
return 1;
|
|
994
|
+
function toIso(epochMs) {
|
|
995
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
629
996
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
997
|
+
var DAY_MS = 864e5;
|
|
998
|
+
function parseIsoDay(text) {
|
|
999
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
1000
|
+
if (match === null) return void 0;
|
|
1001
|
+
const epoch = Date.UTC(
|
|
1002
|
+
Number(match[1]),
|
|
1003
|
+
Number(match[2]) - 1,
|
|
1004
|
+
Number(match[3])
|
|
635
1005
|
);
|
|
1006
|
+
return toIso(epoch) === text ? epoch : void 0;
|
|
636
1007
|
}
|
|
637
|
-
function
|
|
638
|
-
|
|
1008
|
+
function isIsoInterval(value) {
|
|
1009
|
+
const match = INTERVAL_PATTERN.exec(value);
|
|
1010
|
+
if (match === null) return false;
|
|
1011
|
+
const start = parseIsoDay(match[1]);
|
|
1012
|
+
const end = parseIsoDay(match[2]);
|
|
1013
|
+
return start !== void 0 && end !== void 0 && start <= end;
|
|
639
1014
|
}
|
|
640
|
-
function
|
|
641
|
-
return
|
|
1015
|
+
function isPreset(value) {
|
|
1016
|
+
return DATE_RANGE_PRESETS.includes(value);
|
|
642
1017
|
}
|
|
643
|
-
function
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
1018
|
+
function resolveDateRange(value, context = {}) {
|
|
1019
|
+
if (isIsoInterval(value)) return value;
|
|
1020
|
+
if (!isPreset(value)) return void 0;
|
|
1021
|
+
const today = civilToday(context);
|
|
1022
|
+
const todayMs = Date.UTC(today.year, today.month - 1, today.day);
|
|
1023
|
+
const range = (startMs, endMs) => `${toIso(startMs)}/${toIso(endMs)}`;
|
|
1024
|
+
switch (value) {
|
|
1025
|
+
case "today":
|
|
1026
|
+
return range(todayMs, todayMs);
|
|
1027
|
+
case "yesterday":
|
|
1028
|
+
return range(todayMs - DAY_MS, todayMs - DAY_MS);
|
|
1029
|
+
case "last7":
|
|
1030
|
+
return range(todayMs - 6 * DAY_MS, todayMs);
|
|
1031
|
+
case "last30":
|
|
1032
|
+
return range(todayMs - 29 * DAY_MS, todayMs);
|
|
1033
|
+
case "last90":
|
|
1034
|
+
return range(todayMs - 89 * DAY_MS, todayMs);
|
|
1035
|
+
case "thisMonth":
|
|
1036
|
+
return range(Date.UTC(today.year, today.month - 1, 1), todayMs);
|
|
1037
|
+
case "lastMonth":
|
|
1038
|
+
return range(
|
|
1039
|
+
Date.UTC(today.year, today.month - 2, 1),
|
|
1040
|
+
Date.UTC(today.year, today.month - 1, 1) - DAY_MS
|
|
1041
|
+
);
|
|
1042
|
+
case "thisYear":
|
|
1043
|
+
return range(Date.UTC(today.year, 0, 1), todayMs);
|
|
1044
|
+
case "lastYear":
|
|
1045
|
+
return range(
|
|
1046
|
+
Date.UTC(today.year - 1, 0, 1),
|
|
1047
|
+
Date.UTC(today.year, 0, 1) - DAY_MS
|
|
1048
|
+
);
|
|
648
1049
|
}
|
|
649
|
-
return count;
|
|
650
1050
|
}
|
|
651
|
-
function
|
|
652
|
-
|
|
1051
|
+
function onStepGrid(option, value) {
|
|
1052
|
+
if (option.step === void 0) return true;
|
|
1053
|
+
const steps = (value - option.min) / option.step;
|
|
1054
|
+
return Math.abs(steps - Math.round(steps)) < 1e-9;
|
|
653
1055
|
}
|
|
654
|
-
function
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
const
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1056
|
+
function parseSortValue(value) {
|
|
1057
|
+
const separator = value.lastIndexOf(":");
|
|
1058
|
+
if (separator <= 0) return void 0;
|
|
1059
|
+
const column = value.slice(0, separator);
|
|
1060
|
+
const direction = value.slice(separator + 1);
|
|
1061
|
+
if (direction !== "asc" && direction !== "desc") return void 0;
|
|
1062
|
+
return { column, direction };
|
|
1063
|
+
}
|
|
1064
|
+
function sortFromOptions(definition, values) {
|
|
1065
|
+
if (values === void 0) return void 0;
|
|
1066
|
+
const sortOption = definition.options?.find(
|
|
1067
|
+
(option) => option.type === "sort"
|
|
1068
|
+
);
|
|
1069
|
+
if (sortOption === void 0) return void 0;
|
|
1070
|
+
const chosen = values[sortOption.key];
|
|
1071
|
+
return typeof chosen === "string" ? parseSortValue(chosen) : void 0;
|
|
1072
|
+
}
|
|
1073
|
+
function coerceOptionValue(option, value, context = {}) {
|
|
1074
|
+
switch (option.type) {
|
|
1075
|
+
case "choice":
|
|
1076
|
+
return typeof value === "string" && option.choices.some((choice) => choice.value === value) ? value : void 0;
|
|
1077
|
+
case "number":
|
|
1078
|
+
return typeof value === "number" && Number.isFinite(value) && value >= option.min && value <= option.max && onStepGrid(option, value) ? value : void 0;
|
|
1079
|
+
case "boolean":
|
|
1080
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1081
|
+
case "text": {
|
|
1082
|
+
if (typeof value !== "string") return void 0;
|
|
1083
|
+
const trimmed = value.trim();
|
|
1084
|
+
return trimmed.length <= option.maxLength && !CONTROL_CHARACTERS.test(trimmed) ? trimmed : void 0;
|
|
1085
|
+
}
|
|
1086
|
+
case "dateRange": {
|
|
1087
|
+
if (typeof value !== "string") return void 0;
|
|
1088
|
+
if (isPreset(value) && option.presets !== void 0 && !option.presets.some((preset) => preset.value === value)) {
|
|
1089
|
+
return void 0;
|
|
1090
|
+
}
|
|
1091
|
+
return resolveDateRange(value, context);
|
|
1092
|
+
}
|
|
1093
|
+
case "color":
|
|
1094
|
+
return typeof value === "string" && COLOR_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
1095
|
+
case "sort": {
|
|
1096
|
+
if (typeof value !== "string") return void 0;
|
|
1097
|
+
const sort = parseSortValue(value);
|
|
1098
|
+
return sort !== void 0 && option.columns.some((column) => column.key === sort.column) ? value : void 0;
|
|
1099
|
+
}
|
|
1100
|
+
case "columns": {
|
|
1101
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
1102
|
+
const keys = value;
|
|
1103
|
+
const allowed = new Set(option.columns.map((c) => c.key));
|
|
1104
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1105
|
+
for (const key of keys) {
|
|
1106
|
+
if (typeof key !== "string" || !allowed.has(key) || seen.has(key)) {
|
|
1107
|
+
return void 0;
|
|
1108
|
+
}
|
|
1109
|
+
seen.add(key);
|
|
1110
|
+
}
|
|
1111
|
+
return [...seen];
|
|
673
1112
|
}
|
|
674
|
-
rows.push({
|
|
675
|
-
keys: row.map(({ item }) => item.key),
|
|
676
|
-
spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
|
|
677
|
-
});
|
|
678
|
-
row = [];
|
|
679
|
-
used = 0;
|
|
680
1113
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1114
|
+
}
|
|
1115
|
+
function defaultOptionValue(option, context = {}) {
|
|
1116
|
+
switch (option.type) {
|
|
1117
|
+
case "dateRange":
|
|
1118
|
+
return resolveDateRange(option.default, context);
|
|
1119
|
+
case "sort":
|
|
1120
|
+
return option.default;
|
|
1121
|
+
case "columns":
|
|
1122
|
+
return [...option.default];
|
|
1123
|
+
default:
|
|
1124
|
+
return option.default;
|
|
686
1125
|
}
|
|
687
|
-
closeRow();
|
|
688
|
-
return { spans, rows };
|
|
689
1126
|
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
var KIND_LABELS = {
|
|
702
|
-
TEXT: "Text",
|
|
703
|
-
KPI: "Number",
|
|
704
|
-
GAUGE: "Gauge",
|
|
705
|
-
TABLE: "Table",
|
|
706
|
-
BAR_LIST: "Bars",
|
|
707
|
-
ALERT_LIST: "List",
|
|
708
|
-
GRAPH: "Chart"
|
|
709
|
-
};
|
|
710
|
-
function kindLabel(kind) {
|
|
711
|
-
return KIND_LABELS[kind];
|
|
712
|
-
}
|
|
713
|
-
function hasViews(item) {
|
|
714
|
-
return item.views !== void 0 && item.views.length > 0;
|
|
1127
|
+
var NO_VALUES = Object.freeze({});
|
|
1128
|
+
function resolveOptionValues(definition, chosen, context = {}) {
|
|
1129
|
+
const options = definition.options;
|
|
1130
|
+
if (options === void 0 || options.length === 0) return NO_VALUES;
|
|
1131
|
+
const values = {};
|
|
1132
|
+
for (const option of options) {
|
|
1133
|
+
const picked = chosen?.[option.key];
|
|
1134
|
+
const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked, context)) ?? defaultOptionValue(option, context);
|
|
1135
|
+
if (value !== void 0) values[option.key] = value;
|
|
1136
|
+
}
|
|
1137
|
+
return values;
|
|
715
1138
|
}
|
|
716
|
-
function
|
|
717
|
-
|
|
1139
|
+
function sameValue(a, b) {
|
|
1140
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1141
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
1142
|
+
}
|
|
1143
|
+
return a === b;
|
|
718
1144
|
}
|
|
719
|
-
function
|
|
720
|
-
|
|
721
|
-
return [
|
|
722
|
-
{
|
|
723
|
-
key: BASE_VIEW_KEY,
|
|
724
|
-
label: item.baseViewLabel ?? kindLabel(item.kind),
|
|
725
|
-
kind: item.kind
|
|
726
|
-
},
|
|
727
|
-
...(item.views ?? []).map(({ key, label, kind }) => ({
|
|
728
|
-
key,
|
|
729
|
-
label,
|
|
730
|
-
kind
|
|
731
|
-
}))
|
|
732
|
-
];
|
|
1145
|
+
function sameOptionValues(a, b) {
|
|
1146
|
+
const keys = Object.keys({ ...a, ...b });
|
|
1147
|
+
return keys.every((key) => sameValue(a[key], b[key]));
|
|
733
1148
|
}
|
|
734
|
-
function
|
|
735
|
-
|
|
1149
|
+
function optionsCacheSuffix(definition, values, context = {}) {
|
|
1150
|
+
const parts = [];
|
|
1151
|
+
for (const option of definition.options ?? []) {
|
|
1152
|
+
if (isClientApplied(option)) continue;
|
|
1153
|
+
const value = values[option.key];
|
|
1154
|
+
if (sameValue(value, defaultOptionValue(option, context))) continue;
|
|
1155
|
+
parts.push(
|
|
1156
|
+
`${option.key}=${encodeURIComponent(JSON.stringify(value ?? null))}`
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
parts.sort();
|
|
1160
|
+
return parts.length === 0 ? "" : `?${parts.join("&")}`;
|
|
736
1161
|
}
|
|
737
|
-
function
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
if (isKnownView(item, item.defaultView)) return item.defaultView;
|
|
741
|
-
return BASE_VIEW_KEY;
|
|
1162
|
+
function tableColumnIndex(key) {
|
|
1163
|
+
const match = /^c(\d+)$/.exec(key);
|
|
1164
|
+
return match === null ? void 0 : Number(match[1]);
|
|
742
1165
|
}
|
|
743
|
-
function
|
|
744
|
-
|
|
1166
|
+
function sortBarItems(items, sort) {
|
|
1167
|
+
const sign = sort.direction === "desc" ? -1 : 1;
|
|
1168
|
+
return items.map((item, index) => ({ item, index })).sort((a, b) => {
|
|
1169
|
+
const order = sort.column === "value" ? a.item.value - b.item.value : a.item.label.localeCompare(b.item.label);
|
|
1170
|
+
return order * sign || a.index - b.index;
|
|
1171
|
+
}).map(({ item }) => item);
|
|
745
1172
|
}
|
|
746
|
-
function
|
|
747
|
-
const
|
|
748
|
-
|
|
1173
|
+
function applyClientOptions(definition, data, values) {
|
|
1174
|
+
const options = definition.options ?? [];
|
|
1175
|
+
const sortOption = options.find(
|
|
1176
|
+
(o) => o.type === "sort" && o.apply === "client"
|
|
1177
|
+
);
|
|
1178
|
+
const columnsOption = options.find(
|
|
1179
|
+
(o) => o.type === "columns" && o.apply === "client"
|
|
1180
|
+
);
|
|
1181
|
+
const sortValue = sortOption === void 0 ? void 0 : values[sortOption.key];
|
|
1182
|
+
const columnsValue = columnsOption === void 0 ? void 0 : values[columnsOption.key];
|
|
1183
|
+
let next = data;
|
|
1184
|
+
if (typeof sortValue === "string") {
|
|
1185
|
+
const sort = parseSortValue(sortValue);
|
|
1186
|
+
if (sort !== void 0 && next.kind === "BAR_LIST") {
|
|
1187
|
+
if (sort.column === "label" || sort.column === "value") {
|
|
1188
|
+
next = { ...next, items: sortBarItems(next.items, sort) };
|
|
1189
|
+
}
|
|
1190
|
+
} else if (sort !== void 0 && next.kind === "TABLE") {
|
|
1191
|
+
const index = tableColumnIndex(sort.column);
|
|
1192
|
+
if (index !== void 0 && index < next.columns.length) {
|
|
1193
|
+
const sorted = queryRows(tableDetailData(next), {
|
|
1194
|
+
page: 1,
|
|
1195
|
+
pageSize: Math.max(1, next.rows.length),
|
|
1196
|
+
sort
|
|
1197
|
+
});
|
|
1198
|
+
if (sorted.ok) next = { ...next, rows: sorted.value.rows };
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (Array.isArray(columnsValue) && next.kind === "TABLE") {
|
|
1203
|
+
const table = next;
|
|
1204
|
+
const indexes = columnsValue.map(tableColumnIndex).filter(
|
|
1205
|
+
(index) => index !== void 0 && index < table.columns.length
|
|
1206
|
+
);
|
|
1207
|
+
if (indexes.length > 0) {
|
|
1208
|
+
next = {
|
|
1209
|
+
...table,
|
|
1210
|
+
columns: indexes.map((i) => table.columns[i]),
|
|
1211
|
+
rows: table.rows.map((row) => indexes.map((i) => row[i]))
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
return next;
|
|
749
1216
|
}
|
|
750
|
-
|
|
751
|
-
|
|
1217
|
+
|
|
1218
|
+
// src/core/settings.ts
|
|
1219
|
+
var MAX_WIDGET_TITLE_LENGTH = 80;
|
|
1220
|
+
var MAX_PARSED_SETTINGS = 500;
|
|
1221
|
+
var MAX_COLUMN_LIST = 100;
|
|
1222
|
+
var MAX_VALUE_LENGTH = 500;
|
|
1223
|
+
function isRecord(value) {
|
|
1224
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
752
1225
|
}
|
|
753
|
-
function
|
|
754
|
-
if (
|
|
755
|
-
return
|
|
1226
|
+
function parseOptionValue(value) {
|
|
1227
|
+
if (typeof value === "string") {
|
|
1228
|
+
return value.length <= MAX_VALUE_LENGTH ? value : void 0;
|
|
1229
|
+
}
|
|
1230
|
+
if (typeof value === "number")
|
|
1231
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1232
|
+
if (typeof value === "boolean") return value;
|
|
1233
|
+
if (Array.isArray(value) && value.length <= MAX_COLUMN_LIST) {
|
|
1234
|
+
return value.every(
|
|
1235
|
+
(entry) => typeof entry === "string" && entry.length <= MAX_VALUE_LENGTH
|
|
1236
|
+
) ? value : void 0;
|
|
756
1237
|
}
|
|
757
1238
|
return void 0;
|
|
758
1239
|
}
|
|
759
|
-
function
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
1240
|
+
function parseOne(raw) {
|
|
1241
|
+
if (!isRecord(raw)) return void 0;
|
|
1242
|
+
const title = raw["title"];
|
|
1243
|
+
const width = raw["width"];
|
|
1244
|
+
const flexible = raw["flexible"];
|
|
1245
|
+
const options = raw["options"];
|
|
1246
|
+
const view = raw["view"];
|
|
1247
|
+
const cleanTitle = typeof title === "string" ? title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH) : "";
|
|
1248
|
+
const chosen = {};
|
|
1249
|
+
if (isRecord(options)) {
|
|
1250
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1251
|
+
const parsed = parseOptionValue(value);
|
|
1252
|
+
if (parsed !== void 0) chosen[key] = parsed;
|
|
767
1253
|
}
|
|
768
|
-
numbers.push(value);
|
|
769
1254
|
}
|
|
770
|
-
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
label: row[conversion.label]?.text ?? "",
|
|
779
|
-
value: numbers.value[index],
|
|
780
|
-
display: row[conversion.value]?.text ?? String(numbers.value[index])
|
|
781
|
-
}));
|
|
782
|
-
return ok({ kind: "BAR_LIST", items });
|
|
1255
|
+
const settings = {
|
|
1256
|
+
...cleanTitle === "" ? {} : { title: cleanTitle },
|
|
1257
|
+
...isWidgetWidth(width) ? { width } : {},
|
|
1258
|
+
...typeof flexible === "boolean" ? { flexible } : {},
|
|
1259
|
+
...Object.keys(chosen).length === 0 ? {} : { options: chosen },
|
|
1260
|
+
...typeof view === "string" && view !== "" && view.length <= 80 ? { view } : {}
|
|
1261
|
+
};
|
|
1262
|
+
return Object.keys(settings).length === 0 ? void 0 : settings;
|
|
783
1263
|
}
|
|
784
|
-
function
|
|
785
|
-
if (
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
context
|
|
795
|
-
);
|
|
796
|
-
for (const index of conversion.values) {
|
|
797
|
-
bad ??= checkColumn(table, index, "series", context);
|
|
798
|
-
}
|
|
799
|
-
if (bad !== void 0) return err(bad);
|
|
800
|
-
const series = [];
|
|
801
|
-
for (const index of conversion.values) {
|
|
802
|
-
const numbers = numericColumn(table, index, context);
|
|
803
|
-
if (!numbers.ok) return numbers;
|
|
804
|
-
series.push({
|
|
805
|
-
name: columnName(table, index),
|
|
806
|
-
points: table.rows.map((row, rowIndex) => ({
|
|
807
|
-
label: row[conversion.label]?.text ?? "",
|
|
808
|
-
value: numbers.value[rowIndex]
|
|
809
|
-
}))
|
|
810
|
-
});
|
|
1264
|
+
function parseSettings(raw) {
|
|
1265
|
+
if (!isRecord(raw)) return void 0;
|
|
1266
|
+
const parsed = {};
|
|
1267
|
+
for (const [key, value] of Object.entries(raw).slice(
|
|
1268
|
+
0,
|
|
1269
|
+
MAX_PARSED_SETTINGS
|
|
1270
|
+
)) {
|
|
1271
|
+
if (key === "") continue;
|
|
1272
|
+
const one = parseOne(value);
|
|
1273
|
+
if (one !== void 0) parsed[key] = one;
|
|
811
1274
|
}
|
|
812
|
-
return
|
|
813
|
-
kind: "GRAPH",
|
|
814
|
-
series,
|
|
815
|
-
chartType: conversion.chartType,
|
|
816
|
-
valueFormat: conversion.valueFormat ?? "number",
|
|
817
|
-
...conversion.currency === void 0 ? {} : { currency: conversion.currency },
|
|
818
|
-
xLabel: columnName(table, conversion.label)
|
|
819
|
-
});
|
|
1275
|
+
return Object.keys(parsed).length === 0 ? void 0 : parsed;
|
|
820
1276
|
}
|
|
821
|
-
function
|
|
822
|
-
return
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
{
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1277
|
+
function serializeSettings(settings) {
|
|
1278
|
+
if (settings === void 0) return void 0;
|
|
1279
|
+
const out = {};
|
|
1280
|
+
for (const key of Object.keys(settings)) {
|
|
1281
|
+
const one = settings[key];
|
|
1282
|
+
if (one === void 0) continue;
|
|
1283
|
+
const piece = {
|
|
1284
|
+
...one.title === void 0 ? {} : { title: one.title },
|
|
1285
|
+
...one.width === void 0 ? {} : { width: one.width },
|
|
1286
|
+
...one.flexible === void 0 ? {} : { flexible: one.flexible },
|
|
1287
|
+
...one.options === void 0 || Object.keys(one.options).length === 0 ? {} : { options: { ...one.options } },
|
|
1288
|
+
...one.view === void 0 ? {} : { view: one.view }
|
|
1289
|
+
};
|
|
1290
|
+
if (Object.keys(piece).length > 0) out[key] = piece;
|
|
1291
|
+
}
|
|
1292
|
+
return Object.keys(out).length === 0 ? void 0 : out;
|
|
833
1293
|
}
|
|
834
|
-
function
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const format = {
|
|
839
|
-
...graph.currency === void 0 ? {} : { currency: graph.currency },
|
|
840
|
-
...context.locale === void 0 ? {} : { locale: context.locale }
|
|
841
|
-
};
|
|
842
|
-
return ok({
|
|
843
|
-
kind: "TABLE",
|
|
844
|
-
columns: [
|
|
845
|
-
{ label: graph.xLabel ?? "Label" },
|
|
846
|
-
...graph.series.map((series) => ({ label: series.name, numeric: true }))
|
|
847
|
-
],
|
|
848
|
-
rows: first.points.map((point, index) => [
|
|
849
|
-
{ text: point.label },
|
|
850
|
-
...graph.series.map((series) => {
|
|
851
|
-
const value = series.points[index]?.value;
|
|
852
|
-
return value === void 0 ? { text: "\u2014" } : { text: formatValue(value, graph.valueFormat, format), value };
|
|
853
|
-
})
|
|
854
|
-
])
|
|
855
|
-
});
|
|
1294
|
+
function widthRange(item) {
|
|
1295
|
+
const min = isWidgetWidth(item.minWidth) ? item.minWidth : MIN_WIDGET_WIDTH;
|
|
1296
|
+
const max = isWidgetWidth(item.maxWidth) ? item.maxWidth : MAX_WIDGET_WIDTH;
|
|
1297
|
+
return { min, max: Math.max(min, max) };
|
|
856
1298
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
});
|
|
1299
|
+
var WIDTH_SHARES = {
|
|
1300
|
+
2: "1/6",
|
|
1301
|
+
3: "1/4",
|
|
1302
|
+
4: "1/3",
|
|
1303
|
+
5: "5/12",
|
|
1304
|
+
6: "1/2",
|
|
1305
|
+
7: "7/12",
|
|
1306
|
+
8: "2/3",
|
|
1307
|
+
9: "3/4",
|
|
1308
|
+
10: "5/6",
|
|
1309
|
+
11: "11/12",
|
|
1310
|
+
12: "Full"
|
|
1311
|
+
};
|
|
1312
|
+
function widthShareLabel(width) {
|
|
1313
|
+
return WIDTH_SHARES[width] ?? `${width}/12`;
|
|
873
1314
|
}
|
|
874
|
-
function
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1315
|
+
function isOptionsLocked(item) {
|
|
1316
|
+
return resolveWidgetLock(item).options;
|
|
1317
|
+
}
|
|
1318
|
+
function fillWith(fill, flexible) {
|
|
1319
|
+
const heightToo = fill === "height" || fill === "both";
|
|
1320
|
+
if (flexible) return heightToo ? "both" : "width";
|
|
1321
|
+
return heightToo ? "height" : void 0;
|
|
1322
|
+
}
|
|
1323
|
+
function isFlexible(item, settings) {
|
|
1324
|
+
if (settings?.flexible !== void 0) return settings.flexible;
|
|
1325
|
+
return item.fill === "width" || item.fill === "both";
|
|
1326
|
+
}
|
|
1327
|
+
function applySettings(definition, settings) {
|
|
1328
|
+
if (settings === void 0) return definition;
|
|
1329
|
+
const range = widthRange(definition);
|
|
1330
|
+
const width = settings.width === void 0 ? void 0 : Math.min(range.max, Math.max(range.min, settings.width));
|
|
1331
|
+
const title = settings.title === void 0 || settings.title.trim() === "" ? void 0 : settings.title;
|
|
1332
|
+
const flexibleChanged = settings.flexible !== void 0 && settings.flexible !== isFlexible(definition, void 0);
|
|
1333
|
+
if (title === void 0 && width === void 0 && !flexibleChanged) {
|
|
1334
|
+
return definition;
|
|
885
1335
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1336
|
+
const next = { ...definition };
|
|
1337
|
+
if (title !== void 0) next.title = title;
|
|
1338
|
+
if (width !== void 0) next.width = width;
|
|
1339
|
+
if (flexibleChanged) {
|
|
1340
|
+
const fill = fillWith(
|
|
1341
|
+
definition.fill,
|
|
1342
|
+
settings.flexible === true
|
|
889
1343
|
);
|
|
1344
|
+
if (fill === void 0) {
|
|
1345
|
+
delete next.fill;
|
|
1346
|
+
} else {
|
|
1347
|
+
next.fill = fill;
|
|
1348
|
+
}
|
|
890
1349
|
}
|
|
891
|
-
|
|
892
|
-
case "tableToBarList":
|
|
893
|
-
return tableToBarList(data, conversion, context);
|
|
894
|
-
case "tableToGraph":
|
|
895
|
-
return tableToGraph(data, conversion, context);
|
|
896
|
-
case "barListToTable":
|
|
897
|
-
return barListToTable(data, conversion);
|
|
898
|
-
case "graphToTable":
|
|
899
|
-
return graphToTable(data, context);
|
|
900
|
-
case "barListToGraph":
|
|
901
|
-
return barListToGraph(data, conversion);
|
|
902
|
-
}
|
|
1350
|
+
return next;
|
|
903
1351
|
}
|
|
904
|
-
function
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
let indexes;
|
|
908
|
-
if (conversion.type === "tableToBarList") {
|
|
909
|
-
indexes = [conversion.label, conversion.value];
|
|
910
|
-
} else if (conversion.type === "tableToGraph") {
|
|
911
|
-
indexes = [conversion.label, ...conversion.values];
|
|
912
|
-
} else {
|
|
913
|
-
return "";
|
|
914
|
-
}
|
|
915
|
-
const names = indexes.map(
|
|
916
|
-
(index) => data.columns[index]?.label ?? `#${index + 1}`
|
|
1352
|
+
function effectiveDefinitions(definitions, layout, options = {}) {
|
|
1353
|
+
return definitions.map(
|
|
1354
|
+
(definition) => options.overrideLocks !== true && isOptionsLocked(definition) ? definition : applySettings(definition, layout.settings?.[definition.key])
|
|
917
1355
|
);
|
|
918
|
-
return `Shows ${names.join(", ")} only.`;
|
|
919
1356
|
}
|
|
920
|
-
function
|
|
921
|
-
const
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
};
|
|
928
|
-
if (widget.status === "loading") return { ...widget, definition };
|
|
929
|
-
if (widget.status === "empty") return { ...widget, definition };
|
|
930
|
-
if (widget.status === "error") return { ...widget, definition };
|
|
931
|
-
const converted = convertView(
|
|
932
|
-
widget.definition.key,
|
|
933
|
-
view,
|
|
934
|
-
widget.data,
|
|
935
|
-
options
|
|
936
|
-
);
|
|
937
|
-
if (!converted.ok) {
|
|
938
|
-
return {
|
|
939
|
-
definition,
|
|
940
|
-
status: "error",
|
|
941
|
-
error: converted.error,
|
|
942
|
-
...widget.options === void 0 ? {} : { options: widget.options }
|
|
943
|
-
};
|
|
1357
|
+
function widgetOptionValues(definition, settings) {
|
|
1358
|
+
const declared = definition.options ?? [];
|
|
1359
|
+
const values = {};
|
|
1360
|
+
for (const option of declared) {
|
|
1361
|
+
const picked = settings?.options?.[option.key];
|
|
1362
|
+
const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked)) ?? defaultOptionValue(option);
|
|
1363
|
+
if (value !== void 0) values[option.key] = value;
|
|
944
1364
|
}
|
|
945
|
-
return
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
// src/core/options.ts
|
|
949
|
-
var DATE_RANGE_PRESETS = [
|
|
950
|
-
"today",
|
|
951
|
-
"yesterday",
|
|
952
|
-
"last7",
|
|
953
|
-
"last30",
|
|
954
|
-
"last90",
|
|
955
|
-
"thisMonth",
|
|
956
|
-
"lastMonth",
|
|
957
|
-
"thisYear",
|
|
958
|
-
"lastYear"
|
|
959
|
-
];
|
|
960
|
-
var WIDGET_OPTION_TYPES = [
|
|
961
|
-
"choice",
|
|
962
|
-
"number",
|
|
963
|
-
"boolean",
|
|
964
|
-
"text",
|
|
965
|
-
"dateRange",
|
|
966
|
-
"color",
|
|
967
|
-
"sort",
|
|
968
|
-
"columns"
|
|
969
|
-
];
|
|
970
|
-
var MAX_WIDGET_OPTIONS = 20;
|
|
971
|
-
var MAX_TEXT_OPTION_LENGTH = 500;
|
|
972
|
-
var OPTION_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,39}$/;
|
|
973
|
-
var COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
974
|
-
var INTERVAL_PATTERN = /^(\d{4}-\d{2}-\d{2})\/(\d{4}-\d{2}-\d{2})$/;
|
|
975
|
-
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
976
|
-
function isColumnKeyFor(kind, key) {
|
|
977
|
-
if (kind === "TABLE") return /^c\d+$/.test(key);
|
|
978
|
-
if (kind === "BAR_LIST") return key === "label" || key === "value";
|
|
979
|
-
return false;
|
|
1365
|
+
return values;
|
|
980
1366
|
}
|
|
981
|
-
function
|
|
982
|
-
|
|
1367
|
+
function optionValuesFromLayout(definitions, layout, options = {}) {
|
|
1368
|
+
const out = {};
|
|
1369
|
+
for (const definition of definitions) {
|
|
1370
|
+
if (definition.options === void 0 || definition.options.length === 0) {
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
const locked = options.overrideLocks !== true && isOptionsLocked(definition);
|
|
1374
|
+
out[definition.key] = locked ? {} : layout.settings?.[definition.key]?.options ?? {};
|
|
1375
|
+
}
|
|
1376
|
+
return out;
|
|
983
1377
|
}
|
|
984
|
-
function
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
timeZone = "UTC";
|
|
1378
|
+
function viewValuesFromLayout(definitions, layout, options = {}) {
|
|
1379
|
+
const out = {};
|
|
1380
|
+
for (const definition of definitions) {
|
|
1381
|
+
if (!(definition.views ?? []).some(isProviderView)) continue;
|
|
1382
|
+
const locked = options.overrideLocks !== true && isOptionsLocked(definition);
|
|
1383
|
+
out[definition.key] = locked ? "" : layout.settings?.[definition.key]?.view ?? "";
|
|
991
1384
|
}
|
|
992
|
-
|
|
993
|
-
timeZone,
|
|
994
|
-
year: "numeric",
|
|
995
|
-
month: "2-digit",
|
|
996
|
-
day: "2-digit"
|
|
997
|
-
}).formatToParts(new Date(now));
|
|
998
|
-
const pick = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
|
|
999
|
-
return { year: pick("year"), month: pick("month"), day: pick("day") };
|
|
1385
|
+
return out;
|
|
1000
1386
|
}
|
|
1001
|
-
function
|
|
1002
|
-
|
|
1387
|
+
function sameSettings(a, b) {
|
|
1388
|
+
if (a === b) return true;
|
|
1389
|
+
return a?.title === b?.title && a?.width === b?.width && a?.flexible === b?.flexible && a?.view === b?.view && sameOptionValues(a?.options ?? {}, b?.options ?? {});
|
|
1003
1390
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
const
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
const todayMs = Date.UTC(today.year, today.month - 1, today.day);
|
|
1030
|
-
const range = (startMs, endMs) => `${toIso(startMs)}/${toIso(endMs)}`;
|
|
1031
|
-
switch (value) {
|
|
1032
|
-
case "today":
|
|
1033
|
-
return range(todayMs, todayMs);
|
|
1034
|
-
case "yesterday":
|
|
1035
|
-
return range(todayMs - DAY_MS, todayMs - DAY_MS);
|
|
1036
|
-
case "last7":
|
|
1037
|
-
return range(todayMs - 6 * DAY_MS, todayMs);
|
|
1038
|
-
case "last30":
|
|
1039
|
-
return range(todayMs - 29 * DAY_MS, todayMs);
|
|
1040
|
-
case "last90":
|
|
1041
|
-
return range(todayMs - 89 * DAY_MS, todayMs);
|
|
1042
|
-
case "thisMonth":
|
|
1043
|
-
return range(Date.UTC(today.year, today.month - 1, 1), todayMs);
|
|
1044
|
-
case "lastMonth":
|
|
1045
|
-
return range(
|
|
1046
|
-
Date.UTC(today.year, today.month - 2, 1),
|
|
1047
|
-
Date.UTC(today.year, today.month - 1, 1) - DAY_MS
|
|
1048
|
-
);
|
|
1049
|
-
case "thisYear":
|
|
1050
|
-
return range(Date.UTC(today.year, 0, 1), todayMs);
|
|
1051
|
-
case "lastYear":
|
|
1052
|
-
return range(
|
|
1053
|
-
Date.UTC(today.year - 1, 0, 1),
|
|
1054
|
-
Date.UTC(today.year, 0, 1) - DAY_MS
|
|
1055
|
-
);
|
|
1391
|
+
function setWidgetSettings(layout, key, patch) {
|
|
1392
|
+
const before = layout.settings?.[key];
|
|
1393
|
+
const draft = { ...before };
|
|
1394
|
+
for (const field of [
|
|
1395
|
+
"title",
|
|
1396
|
+
"width",
|
|
1397
|
+
"flexible",
|
|
1398
|
+
"options",
|
|
1399
|
+
"view"
|
|
1400
|
+
]) {
|
|
1401
|
+
if (!(field in patch)) continue;
|
|
1402
|
+
const value = patch[field];
|
|
1403
|
+
if (value === void 0) {
|
|
1404
|
+
delete draft[field];
|
|
1405
|
+
} else {
|
|
1406
|
+
draft[field] = value;
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
const merged = parseOne(draft);
|
|
1410
|
+
if (sameSettings(before, merged)) return layout;
|
|
1411
|
+
const rest = { ...layout.settings };
|
|
1412
|
+
if (merged === void 0) {
|
|
1413
|
+
delete rest[key];
|
|
1414
|
+
} else {
|
|
1415
|
+
rest[key] = merged;
|
|
1056
1416
|
}
|
|
1417
|
+
const { settings: _previous, ...others } = layout;
|
|
1418
|
+
return Object.keys(rest).length === 0 ? others : { ...others, settings: rest };
|
|
1057
1419
|
}
|
|
1058
|
-
function
|
|
1059
|
-
if (
|
|
1060
|
-
|
|
1061
|
-
|
|
1420
|
+
function clearWidgetSettings(layout, key) {
|
|
1421
|
+
if (layout.settings?.[key] === void 0) return layout;
|
|
1422
|
+
return setWidgetSettings(layout, key, {
|
|
1423
|
+
title: void 0,
|
|
1424
|
+
width: void 0,
|
|
1425
|
+
flexible: void 0,
|
|
1426
|
+
options: void 0,
|
|
1427
|
+
view: void 0
|
|
1428
|
+
});
|
|
1062
1429
|
}
|
|
1063
|
-
function
|
|
1064
|
-
|
|
1065
|
-
if (separator <= 0) return void 0;
|
|
1066
|
-
const column = value.slice(0, separator);
|
|
1067
|
-
const direction = value.slice(separator + 1);
|
|
1068
|
-
if (direction !== "asc" && direction !== "desc") return void 0;
|
|
1069
|
-
return { column, direction };
|
|
1430
|
+
function normalizeTitle(title) {
|
|
1431
|
+
return title.normalize("NFKC").trim().toLowerCase();
|
|
1070
1432
|
}
|
|
1071
|
-
function
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1433
|
+
function isTitleInUse(definitions, layout, key, title) {
|
|
1434
|
+
const wanted = normalizeTitle(title);
|
|
1435
|
+
if (wanted === "") return false;
|
|
1436
|
+
return effectiveDefinitions(definitions, layout, {
|
|
1437
|
+
overrideLocks: true
|
|
1438
|
+
}).some(
|
|
1439
|
+
(other) => other.key !== key && normalizeTitle(other.title ?? other.key) === wanted
|
|
1075
1440
|
);
|
|
1076
|
-
if (sortOption === void 0) return void 0;
|
|
1077
|
-
const chosen = values[sortOption.key];
|
|
1078
|
-
return typeof chosen === "string" ? parseSortValue(chosen) : void 0;
|
|
1079
1441
|
}
|
|
1080
|
-
function
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1442
|
+
function uniqueTitle(taken, title) {
|
|
1443
|
+
if (!taken.has(normalizeTitle(title))) return title;
|
|
1444
|
+
for (let n = 2; ; n += 1) {
|
|
1445
|
+
const suffix = ` (#${n})`;
|
|
1446
|
+
const candidate = `${title.slice(0, MAX_WIDGET_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
1447
|
+
if (!taken.has(normalizeTitle(candidate))) return candidate;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
function normalizeSettings(definitions, layout, options = {}) {
|
|
1451
|
+
const settings = layout.settings;
|
|
1452
|
+
if (settings === void 0) return layout;
|
|
1453
|
+
const cleaned = {};
|
|
1454
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1455
|
+
for (const definition of definitions) {
|
|
1456
|
+
const renamed = settings[definition.key]?.title !== void 0 && (options.overrideLocks === true || !isOptionsLocked(definition));
|
|
1457
|
+
if (!renamed) taken.add(normalizeTitle(definition.title ?? definition.key));
|
|
1458
|
+
}
|
|
1459
|
+
for (const definition of definitions) {
|
|
1460
|
+
const one = settings[definition.key];
|
|
1461
|
+
if (one === void 0) continue;
|
|
1462
|
+
if (options.overrideLocks !== true && isOptionsLocked(definition)) continue;
|
|
1463
|
+
const range = widthRange(definition);
|
|
1464
|
+
const chosen = {};
|
|
1465
|
+
for (const option of definition.options ?? []) {
|
|
1466
|
+
const picked = one.options?.[option.key];
|
|
1467
|
+
if (picked === void 0) continue;
|
|
1468
|
+
const value = coerceOptionValue(option, picked);
|
|
1469
|
+
if (value !== void 0) {
|
|
1470
|
+
chosen[option.key] = option.type === "dateRange" ? picked : value;
|
|
1097
1471
|
}
|
|
1098
|
-
return resolveDateRange(value, context);
|
|
1099
|
-
}
|
|
1100
|
-
case "color":
|
|
1101
|
-
return typeof value === "string" && COLOR_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
1102
|
-
case "sort": {
|
|
1103
|
-
if (typeof value !== "string") return void 0;
|
|
1104
|
-
const sort = parseSortValue(value);
|
|
1105
|
-
return sort !== void 0 && option.columns.some((column) => column.key === sort.column) ? value : void 0;
|
|
1106
1472
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
}
|
|
1116
|
-
seen.add(key);
|
|
1473
|
+
let title = one.title;
|
|
1474
|
+
if (title !== void 0) {
|
|
1475
|
+
title = title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH);
|
|
1476
|
+
if (title === "") {
|
|
1477
|
+
title = void 0;
|
|
1478
|
+
} else {
|
|
1479
|
+
title = uniqueTitle(taken, title);
|
|
1480
|
+
taken.add(normalizeTitle(title));
|
|
1117
1481
|
}
|
|
1118
|
-
|
|
1482
|
+
}
|
|
1483
|
+
const next = {
|
|
1484
|
+
...title === void 0 ? {} : { title },
|
|
1485
|
+
...one.width === void 0 ? {} : { width: Math.min(range.max, Math.max(range.min, one.width)) },
|
|
1486
|
+
...one.flexible === void 0 ? {} : { flexible: one.flexible },
|
|
1487
|
+
...Object.keys(chosen).length === 0 ? {} : { options: chosen },
|
|
1488
|
+
...one.view !== void 0 && (one.view === BASE_VIEW_KEY || findView(definition, one.view) !== void 0) ? { view: one.view } : {}
|
|
1489
|
+
};
|
|
1490
|
+
if (Object.keys(next).length > 0) {
|
|
1491
|
+
cleaned[definition.key] = sameSettings(one, next) ? one : next;
|
|
1119
1492
|
}
|
|
1120
1493
|
}
|
|
1494
|
+
const keys = Object.keys(cleaned);
|
|
1495
|
+
const unchanged = keys.length === Object.keys(settings).length && keys.every((key) => cleaned[key] === settings[key]);
|
|
1496
|
+
if (unchanged) return layout;
|
|
1497
|
+
const { settings: _previous, ...others } = layout;
|
|
1498
|
+
return keys.length === 0 ? others : { ...others, settings: cleaned };
|
|
1121
1499
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
return option.default;
|
|
1128
|
-
case "columns":
|
|
1129
|
-
return [...option.default];
|
|
1130
|
-
default:
|
|
1131
|
-
return option.default;
|
|
1132
|
-
}
|
|
1500
|
+
|
|
1501
|
+
// src/core/clones.ts
|
|
1502
|
+
var MAX_PARSED_CLONES = 500;
|
|
1503
|
+
function isRecord2(value) {
|
|
1504
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1133
1505
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1140
|
-
const
|
|
1141
|
-
const
|
|
1142
|
-
if (
|
|
1506
|
+
function parseClones(raw) {
|
|
1507
|
+
if (!Array.isArray(raw)) return void 0;
|
|
1508
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1509
|
+
const clones = [];
|
|
1510
|
+
for (const entry of raw.slice(0, MAX_PARSED_CLONES)) {
|
|
1511
|
+
if (!isRecord2(entry)) continue;
|
|
1512
|
+
const key = entry["key"];
|
|
1513
|
+
const from = entry["from"];
|
|
1514
|
+
if (typeof key !== "string" || typeof from !== "string" || key === "" || from === "" || key === from || seen.has(key)) {
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
seen.add(key);
|
|
1518
|
+
clones.push({ key, from });
|
|
1143
1519
|
}
|
|
1144
|
-
return
|
|
1520
|
+
return clones.length === 0 ? void 0 : clones;
|
|
1145
1521
|
}
|
|
1146
|
-
function
|
|
1147
|
-
if (
|
|
1148
|
-
|
|
1149
|
-
}
|
|
1150
|
-
return a === b;
|
|
1522
|
+
function serializeClones(clones) {
|
|
1523
|
+
if (clones === void 0 || clones.length === 0) return void 0;
|
|
1524
|
+
return clones.map(({ key, from }) => ({ key, from }));
|
|
1151
1525
|
}
|
|
1152
|
-
function
|
|
1153
|
-
const
|
|
1154
|
-
return
|
|
1526
|
+
function cloneNumber(key) {
|
|
1527
|
+
const match = /#(\d+)$/.exec(key);
|
|
1528
|
+
return match === null ? void 0 : Number(match[1]);
|
|
1155
1529
|
}
|
|
1156
|
-
function
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
);
|
|
1530
|
+
function cleanClones(definitions, clones, options = {}) {
|
|
1531
|
+
if (clones === void 0) return void 0;
|
|
1532
|
+
const byKey = new Map(definitions.map((d) => [d.key, d]));
|
|
1533
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1534
|
+
const kept = [];
|
|
1535
|
+
for (const clone of clones) {
|
|
1536
|
+
if (seen.has(clone.key)) continue;
|
|
1537
|
+
const original = byKey.get(clone.from);
|
|
1538
|
+
if (original === void 0 || original.sourceKey !== void 0) continue;
|
|
1539
|
+
const existing = byKey.get(clone.key);
|
|
1540
|
+
if (existing !== void 0 && existing.sourceKey !== clone.from) continue;
|
|
1541
|
+
if (options.overrideLocks !== true && resolveWidgetLock(original).clone) {
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
seen.add(clone.key);
|
|
1545
|
+
kept.push(clone);
|
|
1165
1546
|
}
|
|
1166
|
-
|
|
1167
|
-
return
|
|
1547
|
+
if (kept.length === 0) return void 0;
|
|
1548
|
+
return kept.length === clones.length && kept.every((c, i) => c === clones[i]) ? clones : kept;
|
|
1168
1549
|
}
|
|
1169
|
-
function
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1550
|
+
function defaultCloneTitle(original, clone, index) {
|
|
1551
|
+
const base = original.title ?? original.key;
|
|
1552
|
+
const suffix = ` (#${cloneNumber(clone.key) ?? index + 1})`;
|
|
1553
|
+
return `${base.slice(0, MAX_WIDGET_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
1172
1554
|
}
|
|
1173
|
-
function
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
}).map(({ item }) => item);
|
|
1179
|
-
}
|
|
1180
|
-
function applyClientOptions(definition, data, values) {
|
|
1181
|
-
const options = definition.options ?? [];
|
|
1182
|
-
const sortOption = options.find(
|
|
1183
|
-
(o) => o.type === "sort" && o.apply === "client"
|
|
1184
|
-
);
|
|
1185
|
-
const columnsOption = options.find(
|
|
1186
|
-
(o) => o.type === "columns" && o.apply === "client"
|
|
1555
|
+
function withClones(definitions, layout, options = {}) {
|
|
1556
|
+
const valid = cleanClones(
|
|
1557
|
+
definitions,
|
|
1558
|
+
layout.clones,
|
|
1559
|
+
options
|
|
1187
1560
|
);
|
|
1188
|
-
|
|
1189
|
-
const
|
|
1190
|
-
|
|
1191
|
-
if (
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
});
|
|
1205
|
-
if (sorted.ok) next = { ...next, rows: sorted.value.rows };
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
if (Array.isArray(columnsValue) && next.kind === "TABLE") {
|
|
1210
|
-
const table = next;
|
|
1211
|
-
const indexes = columnsValue.map(tableColumnIndex).filter(
|
|
1212
|
-
(index) => index !== void 0 && index < table.columns.length
|
|
1213
|
-
);
|
|
1214
|
-
if (indexes.length > 0) {
|
|
1215
|
-
next = {
|
|
1216
|
-
...table,
|
|
1217
|
-
columns: indexes.map((i) => table.columns[i]),
|
|
1218
|
-
rows: table.rows.map((row) => indexes.map((i) => row[i]))
|
|
1219
|
-
};
|
|
1220
|
-
}
|
|
1561
|
+
if (valid === void 0) return definitions;
|
|
1562
|
+
const present = new Set(definitions.map((d) => d.key));
|
|
1563
|
+
const toAdd = valid.filter((c) => !present.has(c.key));
|
|
1564
|
+
if (toAdd.length === 0) return definitions;
|
|
1565
|
+
const out = [];
|
|
1566
|
+
for (const definition of definitions) {
|
|
1567
|
+
out.push(definition);
|
|
1568
|
+
toAdd.forEach((clone, index) => {
|
|
1569
|
+
if (clone.from !== definition.key) return;
|
|
1570
|
+
const copy = { ...definition };
|
|
1571
|
+
copy.key = clone.key;
|
|
1572
|
+
copy.sourceKey = definition.key;
|
|
1573
|
+
copy.title = defaultCloneTitle(definition, clone, index);
|
|
1574
|
+
delete copy.locked;
|
|
1575
|
+
out.push(copy);
|
|
1576
|
+
});
|
|
1221
1577
|
}
|
|
1222
|
-
return
|
|
1578
|
+
return out;
|
|
1223
1579
|
}
|
|
1224
1580
|
|
|
1225
|
-
// src/core/
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
var MAX_COLUMN_LIST = 100;
|
|
1229
|
-
var MAX_VALUE_LENGTH = 500;
|
|
1230
|
-
function isRecord(value) {
|
|
1231
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1581
|
+
// src/core/compat.ts
|
|
1582
|
+
function isStringArray(value) {
|
|
1583
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
1232
1584
|
}
|
|
1233
|
-
function
|
|
1234
|
-
if (typeof value
|
|
1235
|
-
return value
|
|
1585
|
+
function upgradeLegacyWidgetData(value) {
|
|
1586
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1587
|
+
return value;
|
|
1236
1588
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
(
|
|
1243
|
-
|
|
1589
|
+
const record = value;
|
|
1590
|
+
if (record["kind"] === "TABLE" && isStringArray(record["columns"]) && Array.isArray(record["rows"]) && record["rows"].every(isStringArray)) {
|
|
1591
|
+
const rows = record["rows"];
|
|
1592
|
+
return {
|
|
1593
|
+
...record,
|
|
1594
|
+
columns: record["columns"].map((label) => ({ label })),
|
|
1595
|
+
rows: rows.map((row) => row.map((text) => ({ text })))
|
|
1596
|
+
};
|
|
1244
1597
|
}
|
|
1245
|
-
|
|
1598
|
+
if (record["kind"] === "GRAPH" && Array.isArray(record["points"]) && record["series"] === void 0) {
|
|
1599
|
+
const { points, ...rest } = record;
|
|
1600
|
+
return {
|
|
1601
|
+
...rest,
|
|
1602
|
+
series: [{ name: "Value", points }],
|
|
1603
|
+
chartType: "bar",
|
|
1604
|
+
valueFormat: "number"
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
return value;
|
|
1246
1608
|
}
|
|
1247
|
-
function
|
|
1248
|
-
if (!
|
|
1249
|
-
|
|
1250
|
-
const width = raw["width"];
|
|
1251
|
-
const flexible = raw["flexible"];
|
|
1252
|
-
const options = raw["options"];
|
|
1253
|
-
const view = raw["view"];
|
|
1254
|
-
const cleanTitle = typeof title === "string" ? title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH) : "";
|
|
1255
|
-
const chosen = {};
|
|
1256
|
-
if (isRecord(options)) {
|
|
1257
|
-
for (const [key, value] of Object.entries(options)) {
|
|
1258
|
-
const parsed = parseOptionValue(value);
|
|
1259
|
-
if (parsed !== void 0) chosen[key] = parsed;
|
|
1260
|
-
}
|
|
1609
|
+
function fromLegacyDefinition(legacy) {
|
|
1610
|
+
if (!isWidgetKind(legacy.viewType)) {
|
|
1611
|
+
return null;
|
|
1261
1612
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
...
|
|
1266
|
-
|
|
1267
|
-
|
|
1613
|
+
return {
|
|
1614
|
+
key: legacy.widgetKey,
|
|
1615
|
+
title: legacy.title,
|
|
1616
|
+
...legacy.description === null ? {} : { description: legacy.description },
|
|
1617
|
+
kind: legacy.viewType,
|
|
1618
|
+
sortOrder: legacy.sortOrder
|
|
1268
1619
|
};
|
|
1269
|
-
return Object.keys(settings).length === 0 ? void 0 : settings;
|
|
1270
1620
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
)
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1621
|
+
|
|
1622
|
+
// src/core/layout.ts
|
|
1623
|
+
var MAX_PAGE_TITLE_LENGTH = 30;
|
|
1624
|
+
var MAX_PARSED_PAGES = 50;
|
|
1625
|
+
var MAX_PAGE_ROWS = 20;
|
|
1626
|
+
var EMPTY_LAYOUT = Object.freeze({
|
|
1627
|
+
order: Object.freeze([]),
|
|
1628
|
+
hidden: Object.freeze([]),
|
|
1629
|
+
minimized: Object.freeze([])
|
|
1630
|
+
});
|
|
1631
|
+
function uniqueStrings(value) {
|
|
1632
|
+
if (!Array.isArray(value)) return [];
|
|
1633
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1634
|
+
for (const entry of value) {
|
|
1635
|
+
if (typeof entry === "string" && entry !== "") seen.add(entry);
|
|
1281
1636
|
}
|
|
1282
|
-
return
|
|
1637
|
+
return [...seen];
|
|
1283
1638
|
}
|
|
1284
|
-
function
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1639
|
+
function parseLayout(raw) {
|
|
1640
|
+
let value = raw;
|
|
1641
|
+
if (typeof raw === "string") {
|
|
1642
|
+
try {
|
|
1643
|
+
value = JSON.parse(raw);
|
|
1644
|
+
} catch {
|
|
1645
|
+
return EMPTY_LAYOUT;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1649
|
+
return EMPTY_LAYOUT;
|
|
1650
|
+
}
|
|
1651
|
+
const record = value;
|
|
1652
|
+
const order = uniqueStrings(record["order"]);
|
|
1653
|
+
const hidden = uniqueStrings(record["hidden"]);
|
|
1654
|
+
const minimized = uniqueStrings(record["minimized"]);
|
|
1655
|
+
const pages = parsePages(record["pages"]);
|
|
1656
|
+
const settings = parseSettings(
|
|
1657
|
+
record["settings"]
|
|
1658
|
+
);
|
|
1659
|
+
const clones = parseClones(
|
|
1660
|
+
record["clones"]
|
|
1661
|
+
);
|
|
1662
|
+
const extras = {
|
|
1663
|
+
...settings === void 0 ? {} : { settings },
|
|
1664
|
+
...clones === void 0 ? {} : { clones }
|
|
1665
|
+
};
|
|
1666
|
+
if (pages.length > 0) {
|
|
1667
|
+
return {
|
|
1668
|
+
order: EMPTY_LAYOUT.order,
|
|
1669
|
+
hidden: EMPTY_LAYOUT.hidden,
|
|
1670
|
+
minimized: EMPTY_LAYOUT.minimized,
|
|
1671
|
+
pages,
|
|
1672
|
+
...extras
|
|
1296
1673
|
};
|
|
1297
|
-
if (Object.keys(piece).length > 0) out[key] = piece;
|
|
1298
1674
|
}
|
|
1299
|
-
|
|
1675
|
+
if (order.length === 0 && hidden.length === 0 && minimized.length === 0) {
|
|
1676
|
+
return Object.keys(extras).length === 0 ? EMPTY_LAYOUT : { ...EMPTY_LAYOUT, ...extras };
|
|
1677
|
+
}
|
|
1678
|
+
return { order, hidden, minimized, ...extras };
|
|
1300
1679
|
}
|
|
1301
|
-
function
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
|
|
1680
|
+
function parsePages(value) {
|
|
1681
|
+
if (!Array.isArray(value)) return [];
|
|
1682
|
+
const pages = [];
|
|
1683
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1684
|
+
for (const entry of value.slice(0, MAX_PARSED_PAGES)) {
|
|
1685
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
1686
|
+
continue;
|
|
1687
|
+
}
|
|
1688
|
+
const record = entry;
|
|
1689
|
+
const key = record["key"];
|
|
1690
|
+
if (typeof key !== "string" || key === "" || keys.has(key)) continue;
|
|
1691
|
+
keys.add(key);
|
|
1692
|
+
const rawTitle = record["title"];
|
|
1693
|
+
const title = typeof rawTitle === "string" && rawTitle.trim() !== "" ? rawTitle.trim().slice(0, MAX_PAGE_TITLE_LENGTH) : `Page ${pages.length + 1}`;
|
|
1694
|
+
const maxRows = record["maxRows"];
|
|
1695
|
+
const held = /* @__PURE__ */ new Set();
|
|
1696
|
+
for (const earlier of pages) {
|
|
1697
|
+
for (const k of [...earlier.order, ...earlier.hidden]) held.add(k);
|
|
1698
|
+
}
|
|
1699
|
+
const keep = (list) => uniqueStrings(list).filter((k) => !held.has(k));
|
|
1700
|
+
pages.push({
|
|
1701
|
+
key,
|
|
1702
|
+
title,
|
|
1703
|
+
order: keep(record["order"]),
|
|
1704
|
+
hidden: keep(record["hidden"]),
|
|
1705
|
+
minimized: keep(record["minimized"]),
|
|
1706
|
+
...typeof maxRows === "number" && Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_PAGE_ROWS ? { maxRows } : {}
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1709
|
+
return pages;
|
|
1305
1710
|
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1711
|
+
function serializeLayout(layout) {
|
|
1712
|
+
return JSON.stringify({
|
|
1713
|
+
order: [...layout.order],
|
|
1714
|
+
hidden: [...layout.hidden],
|
|
1715
|
+
minimized: [...layout.minimized],
|
|
1716
|
+
...layout.pages === void 0 || layout.pages.length === 0 ? {} : {
|
|
1717
|
+
pages: layout.pages.map((page) => ({
|
|
1718
|
+
key: page.key,
|
|
1719
|
+
title: page.title,
|
|
1720
|
+
order: [...page.order],
|
|
1721
|
+
hidden: [...page.hidden],
|
|
1722
|
+
minimized: [...page.minimized],
|
|
1723
|
+
...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
|
|
1724
|
+
}))
|
|
1725
|
+
},
|
|
1726
|
+
...serializeSettings(layout.settings) === void 0 ? {} : { settings: serializeSettings(layout.settings) },
|
|
1727
|
+
...serializeClones(layout.clones) === void 0 ? {} : { clones: serializeClones(layout.clones) }
|
|
1728
|
+
});
|
|
1321
1729
|
}
|
|
1322
|
-
function
|
|
1323
|
-
return
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
const heightToo = fill === "height" || fill === "both";
|
|
1327
|
-
if (flexible) return heightToo ? "both" : "width";
|
|
1328
|
-
return heightToo ? "height" : void 0;
|
|
1730
|
+
function bySortOrder(items) {
|
|
1731
|
+
return items.map((item, index) => ({ item, index })).sort(
|
|
1732
|
+
(a, b) => (a.item.sortOrder ?? 0) - (b.item.sortOrder ?? 0) || a.index - b.index
|
|
1733
|
+
).map(({ item }) => item);
|
|
1329
1734
|
}
|
|
1330
|
-
function
|
|
1331
|
-
|
|
1332
|
-
return item.fill === "width" || item.fill === "both";
|
|
1735
|
+
function isPinned(item) {
|
|
1736
|
+
return resolveWidgetLock(item).move;
|
|
1333
1737
|
}
|
|
1334
|
-
function
|
|
1335
|
-
|
|
1336
|
-
const
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
}
|
|
1343
|
-
const next = { ...definition };
|
|
1344
|
-
if (title !== void 0) next.title = title;
|
|
1345
|
-
if (width !== void 0) next.width = width;
|
|
1346
|
-
if (flexibleChanged) {
|
|
1347
|
-
const fill = fillWith(
|
|
1348
|
-
definition.fill,
|
|
1349
|
-
settings.flexible === true
|
|
1350
|
-
);
|
|
1351
|
-
if (fill === void 0) {
|
|
1352
|
-
delete next.fill;
|
|
1353
|
-
} else {
|
|
1354
|
-
next.fill = fill;
|
|
1738
|
+
function viewerOrder(visible, layout) {
|
|
1739
|
+
const byKey = new Map(visible.map((d) => [d.key, d]));
|
|
1740
|
+
const ordered = [];
|
|
1741
|
+
for (const key of layout.order) {
|
|
1742
|
+
const definition = byKey.get(key);
|
|
1743
|
+
if (definition !== void 0) {
|
|
1744
|
+
ordered.push(definition);
|
|
1745
|
+
byKey.delete(key);
|
|
1355
1746
|
}
|
|
1356
1747
|
}
|
|
1357
|
-
|
|
1748
|
+
for (const definition of bySortOrder(visible)) {
|
|
1749
|
+
if (byKey.has(definition.key)) ordered.push(definition);
|
|
1750
|
+
}
|
|
1751
|
+
return ordered;
|
|
1358
1752
|
}
|
|
1359
|
-
function
|
|
1360
|
-
|
|
1361
|
-
(
|
|
1753
|
+
function placeAroundPinned(visible, free) {
|
|
1754
|
+
const slots = bySortOrder(visible).map(
|
|
1755
|
+
(item) => isPinned(item) ? item : void 0
|
|
1362
1756
|
);
|
|
1757
|
+
let next = 0;
|
|
1758
|
+
return slots.map((pinned) => pinned ?? free[next++]);
|
|
1363
1759
|
}
|
|
1364
|
-
function
|
|
1365
|
-
const
|
|
1366
|
-
const
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
}
|
|
1374
|
-
function optionValuesFromLayout(definitions, layout, options = {}) {
|
|
1375
|
-
const out = {};
|
|
1376
|
-
for (const definition of definitions) {
|
|
1377
|
-
if (definition.options === void 0 || definition.options.length === 0) {
|
|
1378
|
-
continue;
|
|
1379
|
-
}
|
|
1380
|
-
const locked = options.overrideLocks !== true && isOptionsLocked(definition);
|
|
1381
|
-
out[definition.key] = locked ? {} : layout.settings?.[definition.key]?.options ?? {};
|
|
1382
|
-
}
|
|
1383
|
-
return out;
|
|
1760
|
+
function visibleWidgets(definitions, layout) {
|
|
1761
|
+
const hidden = new Set(layout.hidden);
|
|
1762
|
+
const visible = definitions.filter((d) => !hidden.has(d.key));
|
|
1763
|
+
const ordered = viewerOrder(visible, layout);
|
|
1764
|
+
if (!visible.some(isPinned)) return ordered;
|
|
1765
|
+
return placeAroundPinned(
|
|
1766
|
+
visible,
|
|
1767
|
+
ordered.filter((item) => !isPinned(item))
|
|
1768
|
+
);
|
|
1384
1769
|
}
|
|
1385
|
-
function
|
|
1386
|
-
|
|
1387
|
-
return
|
|
1770
|
+
function hiddenWidgets(definitions, layout) {
|
|
1771
|
+
const hidden = new Set(layout.hidden);
|
|
1772
|
+
return bySortOrder(definitions.filter((d) => hidden.has(d.key)));
|
|
1388
1773
|
}
|
|
1389
|
-
function
|
|
1390
|
-
const
|
|
1391
|
-
const
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
"flexible",
|
|
1396
|
-
"options",
|
|
1397
|
-
"view"
|
|
1398
|
-
]) {
|
|
1399
|
-
if (!(field in patch)) continue;
|
|
1400
|
-
const value = patch[field];
|
|
1401
|
-
if (value === void 0) {
|
|
1402
|
-
delete draft[field];
|
|
1403
|
-
} else {
|
|
1404
|
-
draft[field] = value;
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
const merged = parseOne(draft);
|
|
1408
|
-
if (sameSettings(before, merged)) return layout;
|
|
1409
|
-
const rest = { ...layout.settings };
|
|
1410
|
-
if (merged === void 0) {
|
|
1411
|
-
delete rest[key];
|
|
1412
|
-
} else {
|
|
1413
|
-
rest[key] = merged;
|
|
1774
|
+
function moveWidget(definitions, layout, key, toIndex) {
|
|
1775
|
+
const current = visibleWidgets(definitions, layout);
|
|
1776
|
+
const fromIndex = current.findIndex((d) => d.key === key);
|
|
1777
|
+
const mover = current[fromIndex];
|
|
1778
|
+
if (mover === void 0 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex || isPinned(mover) || isPinned(current[toIndex])) {
|
|
1779
|
+
return layout;
|
|
1414
1780
|
}
|
|
1415
|
-
const
|
|
1416
|
-
|
|
1781
|
+
const freeSlots = freeSlotIndexes(current);
|
|
1782
|
+
const free = current.filter((item) => !isPinned(item));
|
|
1783
|
+
const fromFree = freeSlots.indexOf(fromIndex);
|
|
1784
|
+
const toFree = freeSlots.indexOf(toIndex);
|
|
1785
|
+
free.splice(fromFree, 1);
|
|
1786
|
+
free.splice(toFree, 0, mover);
|
|
1787
|
+
const next = placeAroundPinned(current, free);
|
|
1788
|
+
return { ...layout, order: next.map((d) => d.key) };
|
|
1417
1789
|
}
|
|
1418
|
-
function
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
width: void 0,
|
|
1423
|
-
flexible: void 0,
|
|
1424
|
-
options: void 0,
|
|
1425
|
-
view: void 0
|
|
1790
|
+
function freeSlotIndexes(ordered) {
|
|
1791
|
+
const slots = [];
|
|
1792
|
+
ordered.forEach((item, index) => {
|
|
1793
|
+
if (!isPinned(item)) slots.push(index);
|
|
1426
1794
|
});
|
|
1795
|
+
return slots;
|
|
1427
1796
|
}
|
|
1428
|
-
function
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
(
|
|
1797
|
+
function moveWidgetBy(definitions, layout, key, offset) {
|
|
1798
|
+
const current = visibleWidgets(definitions, layout);
|
|
1799
|
+
const fromIndex = current.findIndex((d) => d.key === key);
|
|
1800
|
+
if (fromIndex === -1) return layout;
|
|
1801
|
+
const freeSlots = freeSlotIndexes(current);
|
|
1802
|
+
const fromFree = freeSlots.indexOf(fromIndex);
|
|
1803
|
+
if (fromFree === -1) return layout;
|
|
1804
|
+
const toFree = Math.max(
|
|
1805
|
+
0,
|
|
1806
|
+
Math.min(freeSlots.length - 1, fromFree + Math.trunc(offset))
|
|
1438
1807
|
);
|
|
1808
|
+
return moveWidget(definitions, layout, key, freeSlots[toFree]);
|
|
1439
1809
|
}
|
|
1440
|
-
function
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1810
|
+
function moveWidgetUp(definitions, layout, key) {
|
|
1811
|
+
return moveWidgetBy(definitions, layout, key, -1);
|
|
1812
|
+
}
|
|
1813
|
+
function moveWidgetDown(definitions, layout, key) {
|
|
1814
|
+
return moveWidgetBy(definitions, layout, key, 1);
|
|
1815
|
+
}
|
|
1816
|
+
function isHidden(layout, key) {
|
|
1817
|
+
return layout.hidden.includes(key);
|
|
1818
|
+
}
|
|
1819
|
+
function isMinimized(layout, key) {
|
|
1820
|
+
return layout.minimized.includes(key);
|
|
1821
|
+
}
|
|
1822
|
+
function hideWidget(layout, key) {
|
|
1823
|
+
if (isHidden(layout, key)) return layout;
|
|
1824
|
+
return { ...layout, hidden: [...layout.hidden, key] };
|
|
1825
|
+
}
|
|
1826
|
+
function showWidget(layout, key) {
|
|
1827
|
+
if (!isHidden(layout, key)) return layout;
|
|
1828
|
+
return {
|
|
1829
|
+
...layout,
|
|
1830
|
+
hidden: layout.hidden.filter((k) => k !== key),
|
|
1831
|
+
minimized: layout.minimized.filter((k) => k !== key)
|
|
1832
|
+
};
|
|
1833
|
+
}
|
|
1834
|
+
function toggleHidden(layout, key) {
|
|
1835
|
+
return isHidden(layout, key) ? showWidget(layout, key) : hideWidget(layout, key);
|
|
1836
|
+
}
|
|
1837
|
+
function minimizeWidget(layout, key) {
|
|
1838
|
+
if (isMinimized(layout, key)) return layout;
|
|
1839
|
+
return { ...layout, minimized: [...layout.minimized, key] };
|
|
1840
|
+
}
|
|
1841
|
+
function restoreWidget(layout, key) {
|
|
1842
|
+
if (!isMinimized(layout, key)) return layout;
|
|
1843
|
+
return { ...layout, minimized: layout.minimized.filter((k) => k !== key) };
|
|
1844
|
+
}
|
|
1845
|
+
function toggleMinimized(layout, key) {
|
|
1846
|
+
return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
|
|
1847
|
+
}
|
|
1848
|
+
function pruneLayout(definitions, layout) {
|
|
1849
|
+
const known = new Set(definitions.map((d) => d.key));
|
|
1850
|
+
const keep = (keys) => keys.every((k) => known.has(k)) ? keys : keys.filter((k) => known.has(k));
|
|
1851
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
1852
|
+
order: keep(lists.order),
|
|
1853
|
+
hidden: keep(lists.hidden),
|
|
1854
|
+
minimized: keep(lists.minimized)
|
|
1855
|
+
}));
|
|
1856
|
+
}
|
|
1857
|
+
function mapLayoutLists(layout, change) {
|
|
1858
|
+
const top = change(layout);
|
|
1859
|
+
const pages = layout.pages?.map((page) => {
|
|
1860
|
+
const next = change(page);
|
|
1861
|
+
return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
|
|
1862
|
+
});
|
|
1863
|
+
const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
|
|
1864
|
+
if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
|
|
1865
|
+
return layout;
|
|
1446
1866
|
}
|
|
1867
|
+
return {
|
|
1868
|
+
...layout,
|
|
1869
|
+
...top,
|
|
1870
|
+
...pages === void 0 ? {} : { pages }
|
|
1871
|
+
};
|
|
1447
1872
|
}
|
|
1448
|
-
function
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
const
|
|
1452
|
-
const
|
|
1873
|
+
function enforceLocks(definitions, layout, options = {}) {
|
|
1874
|
+
if (options.overrideLocks === true) return layout;
|
|
1875
|
+
const noMove = /* @__PURE__ */ new Set();
|
|
1876
|
+
const noHide = /* @__PURE__ */ new Set();
|
|
1877
|
+
const noMinimize = /* @__PURE__ */ new Set();
|
|
1453
1878
|
for (const definition of definitions) {
|
|
1454
|
-
const
|
|
1455
|
-
if (
|
|
1879
|
+
const lock = resolveWidgetLock(definition);
|
|
1880
|
+
if (lock.move) noMove.add(definition.key);
|
|
1881
|
+
if (lock.hide) noHide.add(definition.key);
|
|
1882
|
+
if (lock.minimize) noMinimize.add(definition.key);
|
|
1456
1883
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
for (const option of definition.options ?? []) {
|
|
1464
|
-
const picked = one.options?.[option.key];
|
|
1465
|
-
if (picked === void 0) continue;
|
|
1466
|
-
const value = coerceOptionValue(option, picked);
|
|
1467
|
-
if (value !== void 0) {
|
|
1468
|
-
chosen[option.key] = option.type === "dateRange" ? picked : value;
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
let title = one.title;
|
|
1472
|
-
if (title !== void 0) {
|
|
1473
|
-
title = title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH);
|
|
1474
|
-
if (title === "") {
|
|
1475
|
-
title = void 0;
|
|
1476
|
-
} else {
|
|
1477
|
-
title = uniqueTitle(taken, title);
|
|
1478
|
-
taken.add(normalizeTitle(title));
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
const next = {
|
|
1482
|
-
...title === void 0 ? {} : { title },
|
|
1483
|
-
...one.width === void 0 ? {} : { width: Math.min(range.max, Math.max(range.min, one.width)) },
|
|
1484
|
-
...one.flexible === void 0 ? {} : { flexible: one.flexible },
|
|
1485
|
-
...Object.keys(chosen).length === 0 ? {} : { options: chosen },
|
|
1486
|
-
...one.view !== void 0 && (one.view === BASE_VIEW_KEY || findView(definition, one.view) !== void 0) ? { view: one.view } : {}
|
|
1487
|
-
};
|
|
1488
|
-
if (Object.keys(next).length > 0) {
|
|
1489
|
-
cleaned[definition.key] = sameSettings(one, next) ? one : next;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
const keys = Object.keys(cleaned);
|
|
1493
|
-
const unchanged = keys.length === Object.keys(settings).length && keys.every((key) => cleaned[key] === settings[key]);
|
|
1494
|
-
if (unchanged) return layout;
|
|
1495
|
-
const { settings: _previous, ...others } = layout;
|
|
1496
|
-
return keys.length === 0 ? others : { ...others, settings: cleaned };
|
|
1884
|
+
const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
|
|
1885
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
1886
|
+
order: without2(lists.order, noMove),
|
|
1887
|
+
hidden: without2(lists.hidden, noHide),
|
|
1888
|
+
minimized: without2(lists.minimized, noMinimize)
|
|
1889
|
+
}));
|
|
1497
1890
|
}
|
|
1498
1891
|
|
|
1499
|
-
// src/core/
|
|
1500
|
-
var
|
|
1501
|
-
var
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
hidden: Object.freeze([]),
|
|
1506
|
-
minimized: Object.freeze([])
|
|
1507
|
-
});
|
|
1508
|
-
function uniqueStrings(value) {
|
|
1509
|
-
if (!Array.isArray(value)) return [];
|
|
1510
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1511
|
-
for (const entry of value) {
|
|
1512
|
-
if (typeof entry === "string" && entry !== "") seen.add(entry);
|
|
1513
|
-
}
|
|
1514
|
-
return [...seen];
|
|
1892
|
+
// src/core/pages.ts
|
|
1893
|
+
var DEFAULT_PAGE_KEY = "default";
|
|
1894
|
+
var DEFAULT_PAGE_ROWS = 4;
|
|
1895
|
+
function rowsAllowed(page, options) {
|
|
1896
|
+
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
1897
|
+
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
1515
1898
|
}
|
|
1516
|
-
function
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
return EMPTY_LAYOUT;
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1526
|
-
return EMPTY_LAYOUT;
|
|
1899
|
+
function hasPages(layout) {
|
|
1900
|
+
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
1901
|
+
}
|
|
1902
|
+
function pageList(layout) {
|
|
1903
|
+
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
1904
|
+
return layout.pages;
|
|
1527
1905
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1906
|
+
return [
|
|
1907
|
+
{
|
|
1908
|
+
key: DEFAULT_PAGE_KEY,
|
|
1909
|
+
title: "Page 1",
|
|
1910
|
+
order: layout.order,
|
|
1911
|
+
hidden: layout.hidden,
|
|
1912
|
+
minimized: layout.minimized
|
|
1913
|
+
}
|
|
1914
|
+
];
|
|
1915
|
+
}
|
|
1916
|
+
function withExplicitPages(layout) {
|
|
1917
|
+
if (hasPages(layout)) return layout;
|
|
1918
|
+
return {
|
|
1919
|
+
...layout,
|
|
1920
|
+
order: [],
|
|
1921
|
+
hidden: [],
|
|
1922
|
+
minimized: [],
|
|
1923
|
+
pages: pageList(layout)
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
function sameKeys(a, b) {
|
|
1927
|
+
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
1928
|
+
}
|
|
1929
|
+
function pageLayout(layout, pageKey) {
|
|
1930
|
+
if (!hasPages(layout))
|
|
1931
|
+
return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
|
|
1932
|
+
const page = layout.pages?.find(
|
|
1933
|
+
(p) => p.key === pageKey
|
|
1535
1934
|
);
|
|
1536
|
-
|
|
1537
|
-
|
|
1935
|
+
if (page === void 0) return void 0;
|
|
1936
|
+
const { pages, ...rest } = layout;
|
|
1937
|
+
return {
|
|
1938
|
+
...rest,
|
|
1939
|
+
order: page.order,
|
|
1940
|
+
hidden: page.hidden,
|
|
1941
|
+
minimized: page.minimized
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
function withPageLayout(layout, pageKey, next) {
|
|
1945
|
+
const current = pageLayout(layout, pageKey);
|
|
1946
|
+
if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
|
|
1947
|
+
return layout;
|
|
1948
|
+
}
|
|
1949
|
+
if (!hasPages(layout)) {
|
|
1538
1950
|
return {
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
...withSettings
|
|
1951
|
+
...layout,
|
|
1952
|
+
order: next.order,
|
|
1953
|
+
hidden: next.hidden,
|
|
1954
|
+
minimized: next.minimized
|
|
1544
1955
|
};
|
|
1545
1956
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
continue;
|
|
1558
|
-
}
|
|
1559
|
-
const record = entry;
|
|
1560
|
-
const key = record["key"];
|
|
1561
|
-
if (typeof key !== "string" || key === "" || keys.has(key)) continue;
|
|
1562
|
-
keys.add(key);
|
|
1563
|
-
const rawTitle = record["title"];
|
|
1564
|
-
const title = typeof rawTitle === "string" && rawTitle.trim() !== "" ? rawTitle.trim().slice(0, MAX_PAGE_TITLE_LENGTH) : `Page ${pages.length + 1}`;
|
|
1565
|
-
const maxRows = record["maxRows"];
|
|
1566
|
-
const held = /* @__PURE__ */ new Set();
|
|
1567
|
-
for (const earlier of pages) {
|
|
1568
|
-
for (const k of [...earlier.order, ...earlier.hidden]) held.add(k);
|
|
1569
|
-
}
|
|
1570
|
-
const keep = (list) => uniqueStrings(list).filter((k) => !held.has(k));
|
|
1571
|
-
pages.push({
|
|
1572
|
-
key,
|
|
1573
|
-
title,
|
|
1574
|
-
order: keep(record["order"]),
|
|
1575
|
-
hidden: keep(record["hidden"]),
|
|
1576
|
-
minimized: keep(record["minimized"]),
|
|
1577
|
-
...typeof maxRows === "number" && Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_PAGE_ROWS ? { maxRows } : {}
|
|
1578
|
-
});
|
|
1579
|
-
}
|
|
1580
|
-
return pages;
|
|
1957
|
+
return {
|
|
1958
|
+
...layout,
|
|
1959
|
+
pages: (layout.pages ?? []).map(
|
|
1960
|
+
(page) => page.key === pageKey ? {
|
|
1961
|
+
...page,
|
|
1962
|
+
order: next.order,
|
|
1963
|
+
hidden: next.hidden,
|
|
1964
|
+
minimized: next.minimized
|
|
1965
|
+
} : page
|
|
1966
|
+
)
|
|
1967
|
+
};
|
|
1581
1968
|
}
|
|
1582
|
-
function
|
|
1583
|
-
return
|
|
1584
|
-
order: [...layout.order],
|
|
1585
|
-
hidden: [...layout.hidden],
|
|
1586
|
-
minimized: [...layout.minimized],
|
|
1587
|
-
...layout.pages === void 0 || layout.pages.length === 0 ? {} : {
|
|
1588
|
-
pages: layout.pages.map((page) => ({
|
|
1589
|
-
key: page.key,
|
|
1590
|
-
title: page.title,
|
|
1591
|
-
order: [...page.order],
|
|
1592
|
-
hidden: [...page.hidden],
|
|
1593
|
-
minimized: [...page.minimized],
|
|
1594
|
-
...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
|
|
1595
|
-
}))
|
|
1596
|
-
},
|
|
1597
|
-
...serializeSettings(layout.settings) === void 0 ? {} : { settings: serializeSettings(layout.settings) }
|
|
1598
|
-
});
|
|
1969
|
+
function normalizedTitle(title) {
|
|
1970
|
+
return title.trim().normalize("NFKC").toLowerCase();
|
|
1599
1971
|
}
|
|
1600
|
-
function
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1972
|
+
function resolvePageKey(layout, reference) {
|
|
1973
|
+
if (reference === void 0 || reference === "") return void 0;
|
|
1974
|
+
const pages = pageList(layout);
|
|
1975
|
+
const byKey = pages.find((p) => p.key === reference);
|
|
1976
|
+
if (byKey !== void 0) return byKey.key;
|
|
1977
|
+
const wanted = normalizedTitle(reference);
|
|
1978
|
+
return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
|
|
1604
1979
|
}
|
|
1605
|
-
function
|
|
1606
|
-
return
|
|
1980
|
+
function homePageKey(layout, item) {
|
|
1981
|
+
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
1607
1982
|
}
|
|
1608
|
-
function
|
|
1609
|
-
const
|
|
1610
|
-
const
|
|
1611
|
-
for (const
|
|
1612
|
-
|
|
1613
|
-
if (definition
|
|
1614
|
-
|
|
1615
|
-
|
|
1983
|
+
function assignPages(definitions, layout) {
|
|
1984
|
+
const pages = pageList(layout);
|
|
1985
|
+
const assigned = new Map(pages.map((p) => [p.key, []]));
|
|
1986
|
+
for (const definition of definitions) {
|
|
1987
|
+
let pageKey;
|
|
1988
|
+
if (!resolveWidgetLock(definition).move) {
|
|
1989
|
+
pageKey = pages.find(
|
|
1990
|
+
(p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
|
|
1991
|
+
)?.key;
|
|
1616
1992
|
}
|
|
1993
|
+
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
1617
1994
|
}
|
|
1618
|
-
|
|
1619
|
-
|
|
1995
|
+
return assigned;
|
|
1996
|
+
}
|
|
1997
|
+
function pageOf(definitions, layout, key) {
|
|
1998
|
+
for (const [pageKey, widgets] of assignPages(definitions, layout)) {
|
|
1999
|
+
if (widgets.some((w) => w.key === key)) return pageKey;
|
|
1620
2000
|
}
|
|
1621
|
-
return
|
|
2001
|
+
return void 0;
|
|
1622
2002
|
}
|
|
1623
|
-
function
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
);
|
|
1627
|
-
let next = 0;
|
|
1628
|
-
return slots.map((pinned) => pinned ?? free[next++]);
|
|
2003
|
+
function pageWidgets(definitions, layout, pageKey) {
|
|
2004
|
+
const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
|
|
2005
|
+
const view = pageLayout(layout, pageKey);
|
|
2006
|
+
return view === void 0 ? [] : visibleWidgets(onPage, view);
|
|
1629
2007
|
}
|
|
1630
|
-
function
|
|
1631
|
-
const
|
|
1632
|
-
|
|
1633
|
-
const ordered = viewerOrder(visible, layout);
|
|
1634
|
-
if (!visible.some(isPinned)) return ordered;
|
|
1635
|
-
return placeAroundPinned(
|
|
1636
|
-
visible,
|
|
1637
|
-
ordered.filter((item) => !isPinned(item))
|
|
2008
|
+
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
2009
|
+
const page = pageList(layout).find(
|
|
2010
|
+
(p) => p.key === pageKey
|
|
1638
2011
|
);
|
|
2012
|
+
if (page === void 0) return void 0;
|
|
2013
|
+
const maxRows = rowsAllowed(page, options);
|
|
2014
|
+
const rowsUsed = placeRows(
|
|
2015
|
+
pageWidgets(definitions, layout, pageKey)
|
|
2016
|
+
).length;
|
|
2017
|
+
return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
|
|
1639
2018
|
}
|
|
1640
|
-
function
|
|
1641
|
-
|
|
1642
|
-
return bySortOrder(definitions.filter((d) => hidden.has(d.key)));
|
|
1643
|
-
}
|
|
1644
|
-
function moveWidget(definitions, layout, key, toIndex) {
|
|
1645
|
-
const current = visibleWidgets(definitions, layout);
|
|
1646
|
-
const fromIndex = current.findIndex((d) => d.key === key);
|
|
1647
|
-
const mover = current[fromIndex];
|
|
1648
|
-
if (mover === void 0 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex || isPinned(mover) || isPinned(current[toIndex])) {
|
|
1649
|
-
return layout;
|
|
1650
|
-
}
|
|
1651
|
-
const freeSlots = freeSlotIndexes(current);
|
|
1652
|
-
const free = current.filter((item) => !isPinned(item));
|
|
1653
|
-
const fromFree = freeSlots.indexOf(fromIndex);
|
|
1654
|
-
const toFree = freeSlots.indexOf(toIndex);
|
|
1655
|
-
free.splice(fromFree, 1);
|
|
1656
|
-
free.splice(toFree, 0, mover);
|
|
1657
|
-
const next = placeAroundPinned(current, free);
|
|
1658
|
-
return { ...layout, order: next.map((d) => d.key) };
|
|
2019
|
+
function describePage(page) {
|
|
2020
|
+
return `Page "${page.title}"`;
|
|
1659
2021
|
}
|
|
1660
|
-
function
|
|
1661
|
-
|
|
1662
|
-
ordered.forEach((item, index) => {
|
|
1663
|
-
if (!isPinned(item)) slots.push(index);
|
|
1664
|
-
});
|
|
1665
|
-
return slots;
|
|
2022
|
+
function titleOf(item) {
|
|
2023
|
+
return item.title ?? item.key;
|
|
1666
2024
|
}
|
|
1667
|
-
function
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
if (
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
2025
|
+
function checkTitle(layout, title, exceptKey) {
|
|
2026
|
+
const trimmed = title.trim();
|
|
2027
|
+
if (trimmed === "") return err("A page title must not be empty.");
|
|
2028
|
+
if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
|
|
2029
|
+
return err(
|
|
2030
|
+
`Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
const wanted = normalizedTitle(trimmed);
|
|
2034
|
+
const clash = pageList(layout).find(
|
|
2035
|
+
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
1677
2036
|
);
|
|
1678
|
-
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1681
|
-
return
|
|
1682
|
-
}
|
|
1683
|
-
function moveWidgetDown(definitions, layout, key) {
|
|
1684
|
-
return moveWidgetBy(definitions, layout, key, 1);
|
|
1685
|
-
}
|
|
1686
|
-
function isHidden(layout, key) {
|
|
1687
|
-
return layout.hidden.includes(key);
|
|
2037
|
+
if (clash !== void 0) {
|
|
2038
|
+
return err(`Another page is already called "${clash.title}".`);
|
|
2039
|
+
}
|
|
2040
|
+
return ok(trimmed);
|
|
1688
2041
|
}
|
|
1689
|
-
function
|
|
1690
|
-
|
|
2042
|
+
function nextPageKey(layout) {
|
|
2043
|
+
const keys = new Set(pageList(layout).map((p) => p.key));
|
|
2044
|
+
let n = 1;
|
|
2045
|
+
while (keys.has(`page-${n}`)) n += 1;
|
|
2046
|
+
return `page-${n}`;
|
|
1691
2047
|
}
|
|
1692
|
-
function
|
|
1693
|
-
|
|
1694
|
-
|
|
2048
|
+
function nextPageTitle(layout) {
|
|
2049
|
+
const used = new Set(
|
|
2050
|
+
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
2051
|
+
);
|
|
2052
|
+
let n = pageList(layout).length + 1;
|
|
2053
|
+
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
2054
|
+
return `Page ${n}`;
|
|
1695
2055
|
}
|
|
1696
|
-
function
|
|
1697
|
-
if (
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
2056
|
+
function addPage(layout, options = {}) {
|
|
2057
|
+
if (pageList(layout).length >= MAX_PARSED_PAGES) {
|
|
2058
|
+
return err(
|
|
2059
|
+
`The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
const key = options.key ?? nextPageKey(layout);
|
|
2063
|
+
if (pageList(layout).some((p) => p.key === key)) {
|
|
2064
|
+
return err(`A page with the key "${key}" already exists.`);
|
|
2065
|
+
}
|
|
2066
|
+
const title = checkTitle(
|
|
2067
|
+
layout,
|
|
2068
|
+
options.title ?? nextPageTitle(layout)
|
|
2069
|
+
);
|
|
2070
|
+
if (!title.ok) return title;
|
|
2071
|
+
const base = withExplicitPages(layout);
|
|
2072
|
+
const page = {
|
|
2073
|
+
key,
|
|
2074
|
+
title: title.value,
|
|
2075
|
+
order: [],
|
|
2076
|
+
hidden: [],
|
|
2077
|
+
minimized: []
|
|
1702
2078
|
};
|
|
2079
|
+
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
1703
2080
|
}
|
|
1704
|
-
function
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
if (
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
return {
|
|
2081
|
+
function renamePage(layout, pageKey, title) {
|
|
2082
|
+
const page = pageList(layout).find(
|
|
2083
|
+
(p) => p.key === pageKey
|
|
2084
|
+
);
|
|
2085
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2086
|
+
const checked = checkTitle(layout, title, pageKey);
|
|
2087
|
+
if (!checked.ok) return checked;
|
|
2088
|
+
if (checked.value === page.title) return ok(layout);
|
|
2089
|
+
const base = withExplicitPages(layout);
|
|
2090
|
+
return ok({
|
|
2091
|
+
...base,
|
|
2092
|
+
pages: (base.pages ?? []).map(
|
|
2093
|
+
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
2094
|
+
)
|
|
2095
|
+
});
|
|
1714
2096
|
}
|
|
1715
|
-
function
|
|
1716
|
-
|
|
2097
|
+
function movePage(layout, pageKey, offset) {
|
|
2098
|
+
const pages = layout.pages ?? [];
|
|
2099
|
+
const from = pages.findIndex((p) => p.key === pageKey);
|
|
2100
|
+
if (from === -1) return layout;
|
|
2101
|
+
const to = Math.max(
|
|
2102
|
+
0,
|
|
2103
|
+
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
2104
|
+
);
|
|
2105
|
+
if (to === from) return layout;
|
|
2106
|
+
const next = [...pages];
|
|
2107
|
+
const [moved] = next.splice(from, 1);
|
|
2108
|
+
next.splice(to, 0, moved);
|
|
2109
|
+
return { ...layout, pages: next };
|
|
1717
2110
|
}
|
|
1718
|
-
function
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
order: keep(lists.order),
|
|
1723
|
-
hidden: keep(lists.hidden),
|
|
1724
|
-
minimized: keep(lists.minimized)
|
|
1725
|
-
}));
|
|
2111
|
+
function overflowing(definitions, layout, options) {
|
|
2112
|
+
return pageList(layout).find(
|
|
2113
|
+
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
2114
|
+
);
|
|
1726
2115
|
}
|
|
1727
|
-
function
|
|
1728
|
-
const
|
|
1729
|
-
const
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
2116
|
+
function removePage(definitions, layout, pageKey, options = {}) {
|
|
2117
|
+
const pages = pageList(layout);
|
|
2118
|
+
const index = pages.findIndex((p) => p.key === pageKey);
|
|
2119
|
+
const page = pages[index];
|
|
2120
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2121
|
+
if (pages.length === 1) {
|
|
2122
|
+
return err(
|
|
2123
|
+
`${describePage(page)} is the only page, so it can't be removed.`
|
|
2124
|
+
);
|
|
1736
2125
|
}
|
|
1737
|
-
|
|
2126
|
+
const target = pages[index > 0 ? index - 1 : 1];
|
|
2127
|
+
const merged = {
|
|
1738
2128
|
...layout,
|
|
1739
|
-
|
|
1740
|
-
|
|
2129
|
+
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
2130
|
+
(p) => p.key === target.key ? {
|
|
2131
|
+
...p,
|
|
2132
|
+
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
2133
|
+
hidden: [...p.hidden, ...page.hidden],
|
|
2134
|
+
minimized: [...p.minimized, ...page.minimized]
|
|
2135
|
+
} : p
|
|
2136
|
+
)
|
|
1741
2137
|
};
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
if (lock.move) noMove.add(definition.key);
|
|
1751
|
-
if (lock.hide) noHide.add(definition.key);
|
|
1752
|
-
if (lock.minimize) noMinimize.add(definition.key);
|
|
1753
|
-
}
|
|
1754
|
-
const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
|
|
1755
|
-
return mapLayoutLists(layout, (lists) => ({
|
|
1756
|
-
order: without2(lists.order, noMove),
|
|
1757
|
-
hidden: without2(lists.hidden, noHide),
|
|
1758
|
-
minimized: without2(lists.minimized, noMinimize)
|
|
1759
|
-
}));
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
// src/core/validate.ts
|
|
1763
|
-
var MAX_REPORTED_PROBLEMS = 10;
|
|
1764
|
-
function describeType2(value) {
|
|
1765
|
-
if (value === null) return "null";
|
|
1766
|
-
if (Array.isArray(value)) return "array";
|
|
1767
|
-
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
1768
|
-
return String(value);
|
|
1769
|
-
}
|
|
1770
|
-
return typeof value;
|
|
1771
|
-
}
|
|
1772
|
-
function isRecord2(value) {
|
|
1773
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1774
|
-
}
|
|
1775
|
-
function join(path, field) {
|
|
1776
|
-
return path === "" ? field : `${path}.${field}`;
|
|
1777
|
-
}
|
|
1778
|
-
function requireString(record, field, path, problems) {
|
|
1779
|
-
const value = record[field];
|
|
1780
|
-
if (typeof value !== "string") {
|
|
1781
|
-
problems.push(
|
|
1782
|
-
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
2138
|
+
const full = overflowing(
|
|
2139
|
+
definitions,
|
|
2140
|
+
merged,
|
|
2141
|
+
options
|
|
2142
|
+
);
|
|
2143
|
+
if (full !== void 0) {
|
|
2144
|
+
return err(
|
|
2145
|
+
`${describePage(page)} has widgets that don't fit on the other pages (${describePage(full)} would need more than ${rowsAllowed(full, options)} rows). Move or hide some first.`
|
|
1783
2146
|
);
|
|
1784
2147
|
}
|
|
2148
|
+
return ok(merged);
|
|
1785
2149
|
}
|
|
1786
|
-
function
|
|
1787
|
-
|
|
1788
|
-
requireString(record, field, path, problems);
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
function requireNumber(record, field, path, problems) {
|
|
1792
|
-
const value = record[field];
|
|
1793
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1794
|
-
problems.push(
|
|
1795
|
-
`${join(path, field)} must be a finite number, got ${describeType2(value)}.`
|
|
1796
|
-
);
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
function optionalNumber(record, field, path, problems) {
|
|
1800
|
-
if (record[field] !== void 0) {
|
|
1801
|
-
requireNumber(record, field, path, problems);
|
|
1802
|
-
}
|
|
2150
|
+
function without(keys, key) {
|
|
2151
|
+
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
1803
2152
|
}
|
|
1804
|
-
function
|
|
1805
|
-
const
|
|
1806
|
-
if (
|
|
1807
|
-
|
|
1808
|
-
|
|
2153
|
+
function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
|
|
2154
|
+
const widget = definitions.find((d) => d.key === widgetKey);
|
|
2155
|
+
if (widget === void 0) {
|
|
2156
|
+
return err(
|
|
2157
|
+
`Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
|
|
1809
2158
|
);
|
|
1810
2159
|
}
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
2160
|
+
const pages = pageList(layout);
|
|
2161
|
+
const target = pages.find((p) => p.key === targetKey);
|
|
2162
|
+
if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
|
|
2163
|
+
const sourceKey = pageOf(definitions, layout, widgetKey);
|
|
2164
|
+
if (sourceKey === targetKey) return ok(layout);
|
|
2165
|
+
if (resolveWidgetLock(widget).move) {
|
|
2166
|
+
return err(
|
|
2167
|
+
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
1817
2168
|
);
|
|
1818
2169
|
}
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
2170
|
+
const base = withExplicitPages(layout);
|
|
2171
|
+
const source = (base.pages ?? []).find(
|
|
2172
|
+
(p) => p.key === sourceKey
|
|
2173
|
+
);
|
|
2174
|
+
const hidden = source.hidden.includes(widgetKey);
|
|
2175
|
+
const minimized = source.minimized.includes(widgetKey);
|
|
2176
|
+
const targetOrder = pageWidgets(
|
|
2177
|
+
definitions,
|
|
2178
|
+
base,
|
|
2179
|
+
targetKey
|
|
2180
|
+
).map((d) => d.key);
|
|
2181
|
+
const moved = {
|
|
2182
|
+
...base,
|
|
2183
|
+
pages: (base.pages ?? []).map((p) => {
|
|
2184
|
+
if (p.key === sourceKey) {
|
|
2185
|
+
return {
|
|
2186
|
+
...p,
|
|
2187
|
+
order: without(p.order, widgetKey),
|
|
2188
|
+
hidden: without(p.hidden, widgetKey),
|
|
2189
|
+
minimized: without(p.minimized, widgetKey)
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
if (p.key === targetKey) {
|
|
2193
|
+
return {
|
|
2194
|
+
...p,
|
|
2195
|
+
order: hidden ? p.order : [...targetOrder, widgetKey],
|
|
2196
|
+
hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
|
|
2197
|
+
minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
return p;
|
|
2201
|
+
})
|
|
2202
|
+
};
|
|
2203
|
+
if (!hidden) {
|
|
2204
|
+
const allowed = rowsAllowed(target, options);
|
|
2205
|
+
const needed = placeRows(
|
|
2206
|
+
pageWidgets(definitions, moved, targetKey)
|
|
2207
|
+
).length;
|
|
2208
|
+
if (needed > allowed) {
|
|
2209
|
+
return err(
|
|
2210
|
+
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
1831
2213
|
}
|
|
2214
|
+
return ok(moved);
|
|
1832
2215
|
}
|
|
1833
|
-
function
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
);
|
|
1839
|
-
return null;
|
|
1840
|
-
}
|
|
1841
|
-
return value;
|
|
2216
|
+
function withoutLockField(definition) {
|
|
2217
|
+
if (definition.locked === void 0) return definition;
|
|
2218
|
+
const copy = { ...definition };
|
|
2219
|
+
delete copy.locked;
|
|
2220
|
+
return copy;
|
|
1842
2221
|
}
|
|
1843
|
-
function
|
|
1844
|
-
|
|
1845
|
-
if (
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
2222
|
+
function uniqueTitle2(taken, title, index) {
|
|
2223
|
+
let base = title.trim().slice(0, MAX_PAGE_TITLE_LENGTH);
|
|
2224
|
+
if (base === "") base = `Page ${index + 1}`;
|
|
2225
|
+
let candidate = base;
|
|
2226
|
+
let n = 1;
|
|
2227
|
+
while (taken.has(normalizedTitle(candidate))) {
|
|
2228
|
+
const suffix = ` (#${n})`;
|
|
2229
|
+
candidate = `${base.slice(0, MAX_PAGE_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
2230
|
+
n += 1;
|
|
1849
2231
|
}
|
|
2232
|
+
taken.add(normalizedTitle(candidate));
|
|
2233
|
+
return candidate;
|
|
1850
2234
|
}
|
|
1851
|
-
function
|
|
1852
|
-
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1855
|
-
check(item, itemPath);
|
|
1856
|
-
} else {
|
|
1857
|
-
problems.push(
|
|
1858
|
-
`${itemPath} must be an object, got ${describeType2(item)}.`
|
|
1859
|
-
);
|
|
1860
|
-
}
|
|
2235
|
+
function samePages(a, b) {
|
|
2236
|
+
return a.length === b.length && a.every((p, i) => {
|
|
2237
|
+
const q = b[i];
|
|
2238
|
+
return p.key === q.key && p.title === q.title && p.maxRows === q.maxRows && sameKeys(p.order, q.order) && sameKeys(p.hidden, q.hidden) && sameKeys(p.minimized, q.minimized);
|
|
1861
2239
|
});
|
|
1862
2240
|
}
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
2241
|
+
function normalizeLayout(definitions, layout, options = {}) {
|
|
2242
|
+
const clones = cleanClones(definitions, layout.clones, options);
|
|
2243
|
+
let base = layout;
|
|
2244
|
+
if (clones !== layout.clones) {
|
|
2245
|
+
const { clones: _dropped, ...others } = layout;
|
|
2246
|
+
base = clones === void 0 ? others : { ...others, clones };
|
|
2247
|
+
}
|
|
2248
|
+
return normalizeWidgets(
|
|
2249
|
+
withClones(definitions, base, options),
|
|
2250
|
+
base,
|
|
2251
|
+
options
|
|
2252
|
+
);
|
|
2253
|
+
}
|
|
2254
|
+
function normalizeWidgets(definitions, layout, options) {
|
|
2255
|
+
const known = pruneLayout(definitions, layout);
|
|
2256
|
+
const enforced = enforceLocks(definitions, known, options);
|
|
2257
|
+
const locked = normalizeSettings(
|
|
2258
|
+
definitions,
|
|
2259
|
+
enforced,
|
|
2260
|
+
options
|
|
2261
|
+
);
|
|
2262
|
+
if (!hasPages(locked)) return locked === layout ? layout : locked;
|
|
2263
|
+
const defs = effectiveDefinitions(
|
|
2264
|
+
options.overrideLocks === true ? definitions.map(withoutLockField) : definitions,
|
|
2265
|
+
locked,
|
|
2266
|
+
options
|
|
2267
|
+
);
|
|
2268
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2269
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
2270
|
+
const held = /* @__PURE__ */ new Set();
|
|
2271
|
+
let pages = [];
|
|
2272
|
+
(locked.pages ?? []).forEach((page, index) => {
|
|
2273
|
+
if (seenKeys.has(page.key)) return;
|
|
2274
|
+
seenKeys.add(page.key);
|
|
2275
|
+
const keep = (list) => [
|
|
2276
|
+
...new Set(list.filter((k) => !held.has(k)))
|
|
2277
|
+
];
|
|
2278
|
+
const next = {
|
|
2279
|
+
...page,
|
|
2280
|
+
title: uniqueTitle2(taken, page.title, index),
|
|
2281
|
+
order: keep(page.order),
|
|
2282
|
+
hidden: keep(page.hidden),
|
|
2283
|
+
minimized: keep(page.minimized)
|
|
2284
|
+
};
|
|
2285
|
+
for (const k of [...next.order, ...next.hidden]) held.add(k);
|
|
2286
|
+
pages.push(next);
|
|
2287
|
+
});
|
|
2288
|
+
const work = () => ({ ...locked, pages });
|
|
2289
|
+
const pinned = defs.filter((d) => resolveWidgetLock(d).move);
|
|
2290
|
+
for (const widget of pinned) {
|
|
2291
|
+
const home = homePageKey(work(), widget);
|
|
2292
|
+
const keysHere = (p, list) => p.key !== home && p[list].includes(widget.key);
|
|
2293
|
+
const wasHidden = pages.some((p) => keysHere(p, "hidden"));
|
|
2294
|
+
const wasMinimized = pages.some((p) => keysHere(p, "minimized"));
|
|
2295
|
+
if (!wasHidden && !wasMinimized) continue;
|
|
2296
|
+
pages = pages.map((p) => {
|
|
2297
|
+
let next = p;
|
|
2298
|
+
if (keysHere(p, "hidden")) {
|
|
2299
|
+
next = { ...next, hidden: without(next.hidden, widget.key) };
|
|
1875
2300
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
requireString(data, "label", "", problems);
|
|
1879
|
-
optionalString(data, "hint", "", problems);
|
|
1880
|
-
optionalBoolean(data, "higherIsBetter", "", problems);
|
|
1881
|
-
return;
|
|
1882
|
-
case "GAUGE":
|
|
1883
|
-
requireNumber(data, "value", "", problems);
|
|
1884
|
-
requireNumber(data, "max", "", problems);
|
|
1885
|
-
requireString(data, "label", "", problems);
|
|
1886
|
-
if (typeof data["max"] === "number" && data["max"] <= 0) {
|
|
1887
|
-
problems.push(`max must be greater than 0, got ${data["max"]}.`);
|
|
2301
|
+
if (keysHere(p, "minimized")) {
|
|
2302
|
+
next = { ...next, minimized: without(next.minimized, widget.key) };
|
|
1888
2303
|
}
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
optionalBoolean(column, "numeric", path, problems);
|
|
1896
|
-
});
|
|
2304
|
+
if (p.key === home) {
|
|
2305
|
+
next = {
|
|
2306
|
+
...next,
|
|
2307
|
+
hidden: wasHidden && !next.hidden.includes(widget.key) ? [...next.hidden, widget.key] : next.hidden,
|
|
2308
|
+
minimized: wasMinimized && !next.minimized.includes(widget.key) ? [...next.minimized, widget.key] : next.minimized
|
|
2309
|
+
};
|
|
1897
2310
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
2311
|
+
return next;
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
const placedKeys = /* @__PURE__ */ new Set();
|
|
2315
|
+
for (const p of pages) {
|
|
2316
|
+
for (const k of [...p.order, ...p.hidden]) placedKeys.add(k);
|
|
2317
|
+
}
|
|
2318
|
+
const pinnedKeys = new Set(pinned.map((d) => d.key));
|
|
2319
|
+
const unplaced = defs.filter((d) => !placedKeys.has(d.key) && !pinnedKeys.has(d.key)).map((definition, index) => ({ definition, index })).sort(
|
|
2320
|
+
(a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
|
|
2321
|
+
).map(({ definition }) => definition);
|
|
2322
|
+
for (const widget of unplaced) {
|
|
2323
|
+
const home = homePageKey(work(), widget);
|
|
2324
|
+
pages = pages.map(
|
|
2325
|
+
(p) => p.key === home ? { ...p, order: [...p.order, widget.key] } : p
|
|
2326
|
+
);
|
|
2327
|
+
}
|
|
2328
|
+
for (let i = 0; i < pages.length; i += 1) {
|
|
2329
|
+
const page = pages[i];
|
|
2330
|
+
const shown = pageWidgets(defs, work(), page.key);
|
|
2331
|
+
const fits = fitCount(shown, rowsAllowed(page, options));
|
|
2332
|
+
if (fits >= shown.length) continue;
|
|
2333
|
+
const moving = shown.slice(fits).filter((d) => !pinnedKeys.has(d.key)).map((d) => d.key);
|
|
2334
|
+
if (moving.length === 0) continue;
|
|
2335
|
+
const moveSet = new Set(moving);
|
|
2336
|
+
const movedMinimized = page.minimized.filter(
|
|
2337
|
+
(k) => moveSet.has(k)
|
|
2338
|
+
);
|
|
2339
|
+
pages = pages.map(
|
|
2340
|
+
(p) => p.key === page.key ? {
|
|
2341
|
+
...p,
|
|
2342
|
+
order: p.order.filter((k) => !moveSet.has(k)),
|
|
2343
|
+
minimized: p.minimized.filter((k) => !moveSet.has(k))
|
|
2344
|
+
} : p
|
|
2345
|
+
);
|
|
2346
|
+
let placed = false;
|
|
2347
|
+
for (let j = i + 1; j < pages.length && !placed; j += 1) {
|
|
2348
|
+
const candidate = pages[j];
|
|
2349
|
+
const trial = pages.map(
|
|
2350
|
+
(p) => p.key === candidate.key ? {
|
|
2351
|
+
...p,
|
|
2352
|
+
order: [...moving, ...p.order],
|
|
2353
|
+
minimized: [...p.minimized, ...movedMinimized]
|
|
2354
|
+
} : p
|
|
2355
|
+
);
|
|
2356
|
+
const items = pageWidgets(
|
|
2357
|
+
defs,
|
|
2358
|
+
{ ...locked, pages: trial },
|
|
2359
|
+
candidate.key
|
|
2360
|
+
);
|
|
2361
|
+
if (placeRows(items).length <= rowsAllowed(candidate, options)) {
|
|
2362
|
+
pages = trial;
|
|
2363
|
+
placed = true;
|
|
1938
2364
|
}
|
|
1939
|
-
optionalNumber(data, "total", "", problems);
|
|
1940
|
-
return;
|
|
1941
2365
|
}
|
|
1942
|
-
|
|
1943
|
-
const
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
isSafeImageUrl,
|
|
1954
|
-
IMAGE_RULE
|
|
1955
|
-
);
|
|
1956
|
-
requireString(item, "valueLabel", path, problems);
|
|
1957
|
-
optionalString(item, "detail", path, problems);
|
|
1958
|
-
});
|
|
1959
|
-
}
|
|
1960
|
-
requireNumber(data, "total", "", problems);
|
|
1961
|
-
requireString(data, "emptyText", "", problems);
|
|
1962
|
-
if (items !== null && typeof data["total"] === "number" && data["total"] < items.length) {
|
|
1963
|
-
problems.push(
|
|
1964
|
-
`total (${data["total"]}) must be at least the number of items (${items.length}).`
|
|
1965
|
-
);
|
|
1966
|
-
}
|
|
1967
|
-
return;
|
|
2366
|
+
if (!placed) {
|
|
2367
|
+
const key = nextPageKey(work());
|
|
2368
|
+
const title = uniqueTitle2(
|
|
2369
|
+
new Set(pages.map((p) => normalizedTitle(p.title))),
|
|
2370
|
+
`Page ${pages.length + 1}`,
|
|
2371
|
+
pages.length
|
|
2372
|
+
);
|
|
2373
|
+
pages = [
|
|
2374
|
+
...pages,
|
|
2375
|
+
{ key, title, order: moving, hidden: [], minimized: movedMinimized }
|
|
2376
|
+
];
|
|
1968
2377
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
optionalString(data, "xLabel", "", problems);
|
|
1997
|
-
return;
|
|
2378
|
+
}
|
|
2379
|
+
const result = work();
|
|
2380
|
+
return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) && locked.settings === layout.settings ? layout : result;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
// src/core/duplicate.ts
|
|
2384
|
+
function canDuplicate(definitions, key, options = {}) {
|
|
2385
|
+
const definition = definitions.find(
|
|
2386
|
+
(d) => d.key === key
|
|
2387
|
+
);
|
|
2388
|
+
if (definition === void 0) return false;
|
|
2389
|
+
if (options.overrideLocks === true) return true;
|
|
2390
|
+
const sourceKey = definition.sourceKey ?? definition.key;
|
|
2391
|
+
const original = definitions.find(
|
|
2392
|
+
(d) => d.key === sourceKey
|
|
2393
|
+
);
|
|
2394
|
+
return original !== void 0 && !resolveWidgetLock(original).clone;
|
|
2395
|
+
}
|
|
2396
|
+
function nextCloneKey(definitions, layout, fromKey) {
|
|
2397
|
+
const taken = /* @__PURE__ */ new Set([
|
|
2398
|
+
...definitions.map((d) => d.key),
|
|
2399
|
+
...(layout.clones ?? []).map((c) => c.key)
|
|
2400
|
+
]);
|
|
2401
|
+
let highest = 0;
|
|
2402
|
+
for (const clone of layout.clones ?? []) {
|
|
2403
|
+
if (clone.from === fromKey) {
|
|
2404
|
+
highest = Math.max(highest, cloneNumber(clone.key) ?? 0);
|
|
1998
2405
|
}
|
|
1999
2406
|
}
|
|
2407
|
+
let n = highest + 1;
|
|
2408
|
+
while (taken.has(`${fromKey}#${n}`)) n += 1;
|
|
2409
|
+
return `${fromKey}#${n}`;
|
|
2000
2410
|
}
|
|
2001
|
-
function
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
return;
|
|
2411
|
+
function suggestedViewTitle(title, viewLabel) {
|
|
2412
|
+
return `${title} ${viewLabel}`.trim().slice(0, MAX_WIDGET_TITLE_LENGTH);
|
|
2413
|
+
}
|
|
2414
|
+
function duplicateWidget(definitions, layout, key, options = {}) {
|
|
2415
|
+
const widget = definitions.find((d) => d.key === key);
|
|
2416
|
+
if (widget === void 0) {
|
|
2417
|
+
return err(`Widget ${JSON.stringify(key)} isn't on this dashboard.`);
|
|
2008
2418
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
`detail.pageSize must be a whole number from 1 to ${MAX_DETAIL_PAGE_SIZE}, got ${typeof pageSize === "number" ? String(pageSize) : describeType2(pageSize)}.`
|
|
2419
|
+
const originalKey = widget.sourceKey ?? widget.key;
|
|
2420
|
+
if (!canDuplicate(definitions, key, options)) {
|
|
2421
|
+
return err(
|
|
2422
|
+
`Widget ${JSON.stringify(originalKey)} is locked against duplicating.`
|
|
2014
2423
|
);
|
|
2015
2424
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2425
|
+
const cloneKey = nextCloneKey(definitions, layout, originalKey);
|
|
2426
|
+
const copiedSettings = (() => {
|
|
2427
|
+
const mine = layout.settings?.[key];
|
|
2428
|
+
if (mine === void 0) return void 0;
|
|
2429
|
+
const { title: _title, ...rest } = mine;
|
|
2430
|
+
return Object.keys(rest).length === 0 ? void 0 : rest;
|
|
2431
|
+
})();
|
|
2432
|
+
const settings = copiedSettings === void 0 ? layout.settings : { ...layout.settings, [cloneKey]: copiedSettings };
|
|
2433
|
+
let next = {
|
|
2434
|
+
...layout,
|
|
2435
|
+
clones: [...layout.clones ?? [], { key: cloneKey, from: originalKey }],
|
|
2436
|
+
...settings === void 0 ? {} : { settings }
|
|
2437
|
+
};
|
|
2438
|
+
const all = withClones(definitions, next, options);
|
|
2439
|
+
const assigned = assignPages(all, next);
|
|
2440
|
+
const pageKey = [...assigned].find(
|
|
2441
|
+
([, widgets]) => widgets.some((w) => w.key === key)
|
|
2442
|
+
)?.[0];
|
|
2443
|
+
if (pageKey !== void 0) {
|
|
2444
|
+
const view = pageLayout(next, pageKey);
|
|
2445
|
+
const onPage = assigned.get(pageKey) ?? [];
|
|
2446
|
+
if (view !== void 0) {
|
|
2447
|
+
const shown = visibleWidgets(onPage, view).filter((d) => !resolveWidgetLock(d).move && d.key !== cloneKey).map((d) => d.key);
|
|
2448
|
+
const at = shown.indexOf(key);
|
|
2449
|
+
const order = at === -1 ? [...shown, cloneKey] : [...shown.slice(0, at + 1), cloneKey, ...shown.slice(at + 1)];
|
|
2450
|
+
next = withPageLayout(next, pageKey, { ...view, order });
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
if (hasPages(next) && pageKey !== void 0) {
|
|
2454
|
+
const effective = effectiveDefinitions(all, next, options);
|
|
2455
|
+
const room = pageRoom(effective, next, pageKey, options);
|
|
2456
|
+
if (room !== void 0 && room.rowsUsed > room.maxRows) {
|
|
2457
|
+
const page = next.pages?.find(
|
|
2458
|
+
(p) => p.key === pageKey
|
|
2459
|
+
);
|
|
2460
|
+
const width = effective.find(
|
|
2461
|
+
(d) => d.key === cloneKey
|
|
2462
|
+
)?.width;
|
|
2463
|
+
return err(
|
|
2464
|
+
`Page "${page?.title ?? pageKey}" has no room for a widget${width === void 0 ? "" : ` ${width} wide`} (it holds ${room.maxRows} rows).`
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2018
2467
|
}
|
|
2468
|
+
return ok({ layout: next, key: cloneKey });
|
|
2019
2469
|
}
|
|
2020
|
-
function
|
|
2021
|
-
if (
|
|
2022
|
-
|
|
2470
|
+
function deleteClone(layout, key) {
|
|
2471
|
+
if (layout.clones?.some((clone) => clone.key === key) !== true) return layout;
|
|
2472
|
+
const without2 = (keys) => keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
2473
|
+
const { clones, settings, ...rest } = layout;
|
|
2474
|
+
const remaining = (clones ?? []).filter((c) => c.key !== key);
|
|
2475
|
+
const { [key]: _gone, ...otherSettings } = settings ?? {};
|
|
2476
|
+
return {
|
|
2477
|
+
...rest,
|
|
2478
|
+
order: without2(layout.order),
|
|
2479
|
+
hidden: without2(layout.hidden),
|
|
2480
|
+
minimized: without2(layout.minimized),
|
|
2481
|
+
...layout.pages === void 0 ? {} : {
|
|
2482
|
+
pages: layout.pages.map((page) => ({
|
|
2483
|
+
...page,
|
|
2484
|
+
order: without2(page.order),
|
|
2485
|
+
hidden: without2(page.hidden),
|
|
2486
|
+
minimized: without2(page.minimized)
|
|
2487
|
+
}))
|
|
2488
|
+
},
|
|
2489
|
+
...Object.keys(otherSettings).length === 0 ? {} : { settings: otherSettings },
|
|
2490
|
+
...remaining.length === 0 ? {} : { clones: remaining }
|
|
2491
|
+
};
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
// src/core/validate.ts
|
|
2495
|
+
var MAX_REPORTED_PROBLEMS = 10;
|
|
2496
|
+
function describeType2(value) {
|
|
2497
|
+
if (value === null) return "null";
|
|
2498
|
+
if (Array.isArray(value)) return "array";
|
|
2499
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
2500
|
+
return String(value);
|
|
2501
|
+
}
|
|
2502
|
+
return typeof value;
|
|
2503
|
+
}
|
|
2504
|
+
function isRecord3(value) {
|
|
2505
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2506
|
+
}
|
|
2507
|
+
function join(path, field) {
|
|
2508
|
+
return path === "" ? field : `${path}.${field}`;
|
|
2509
|
+
}
|
|
2510
|
+
function requireString(record, field, path, problems) {
|
|
2511
|
+
const value = record[field];
|
|
2512
|
+
if (typeof value !== "string") {
|
|
2023
2513
|
problems.push(
|
|
2024
|
-
|
|
2514
|
+
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
2025
2515
|
);
|
|
2026
|
-
return;
|
|
2027
2516
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2517
|
+
}
|
|
2518
|
+
function optionalString(record, field, path, problems) {
|
|
2519
|
+
if (record[field] !== void 0) {
|
|
2520
|
+
requireString(record, field, path, problems);
|
|
2031
2521
|
}
|
|
2032
|
-
|
|
2522
|
+
}
|
|
2523
|
+
function requireNumber(record, field, path, problems) {
|
|
2524
|
+
const value = record[field];
|
|
2525
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2033
2526
|
problems.push(
|
|
2034
|
-
|
|
2527
|
+
`${join(path, field)} must be a finite number, got ${describeType2(value)}.`
|
|
2035
2528
|
);
|
|
2036
2529
|
}
|
|
2037
2530
|
}
|
|
2038
|
-
function
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
if (limit !== void 0 && !isWidgetWidth(limit)) {
|
|
2042
|
-
problems.push(
|
|
2043
|
-
`${field} must be an integer from ${MIN_WIDGET_WIDTH} to ${MAX_WIDGET_WIDTH} (twelfths of the row), got ${typeof limit === "number" ? String(limit) : describeType2(limit)}.`
|
|
2044
|
-
);
|
|
2045
|
-
return;
|
|
2046
|
-
}
|
|
2531
|
+
function optionalNumber(record, field, path, problems) {
|
|
2532
|
+
if (record[field] !== void 0) {
|
|
2533
|
+
requireNumber(record, field, path, problems);
|
|
2047
2534
|
}
|
|
2048
|
-
|
|
2049
|
-
|
|
2535
|
+
}
|
|
2536
|
+
function optionalSortValue(record, path, problems) {
|
|
2537
|
+
const value = record["value"];
|
|
2538
|
+
if (value !== void 0 && typeof value !== "string" && !(typeof value === "number" && Number.isFinite(value))) {
|
|
2050
2539
|
problems.push(
|
|
2051
|
-
|
|
2540
|
+
`${path}.value must be a string or a finite number, got ${describeType2(value)}.`
|
|
2052
2541
|
);
|
|
2053
|
-
return;
|
|
2054
|
-
}
|
|
2055
|
-
if (isWidgetWidth(width)) {
|
|
2056
|
-
if (isWidgetWidth(minWidth) && width < minWidth) {
|
|
2057
|
-
problems.push(
|
|
2058
|
-
`width (${width}) must not be less than minWidth (${minWidth}).`
|
|
2059
|
-
);
|
|
2060
|
-
}
|
|
2061
|
-
if (isWidgetWidth(maxWidth) && width > maxWidth) {
|
|
2062
|
-
problems.push(
|
|
2063
|
-
`width (${width}) must not be more than maxWidth (${maxWidth}).`
|
|
2064
|
-
);
|
|
2065
|
-
}
|
|
2066
2542
|
}
|
|
2067
2543
|
}
|
|
2068
|
-
function
|
|
2069
|
-
|
|
2070
|
-
if (
|
|
2544
|
+
function optionalBoolean(record, field, path, problems) {
|
|
2545
|
+
const value = record[field];
|
|
2546
|
+
if (value !== void 0 && typeof value !== "boolean") {
|
|
2071
2547
|
problems.push(
|
|
2072
|
-
|
|
2548
|
+
`${join(path, field)} must be a boolean, got ${describeType2(value)}.`
|
|
2073
2549
|
);
|
|
2074
|
-
return;
|
|
2075
2550
|
}
|
|
2076
|
-
|
|
2551
|
+
}
|
|
2552
|
+
function optionalUrl(record, field, path, problems, isSafe, allowed) {
|
|
2553
|
+
const value = record[field];
|
|
2554
|
+
if (value === void 0) return;
|
|
2555
|
+
if (typeof value !== "string") {
|
|
2556
|
+
problems.push(
|
|
2557
|
+
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
2558
|
+
);
|
|
2559
|
+
} else if (!isSafe(value)) {
|
|
2560
|
+
problems.push(
|
|
2561
|
+
`${join(path, field)} must be ${allowed}, got ${JSON.stringify(value.slice(0, 80))}.`
|
|
2562
|
+
);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
function requireArray(record, field, path, problems) {
|
|
2566
|
+
const value = record[field];
|
|
2567
|
+
if (!Array.isArray(value)) {
|
|
2568
|
+
problems.push(
|
|
2569
|
+
`${join(path, field)} must be an array, got ${describeType2(value)}.`
|
|
2570
|
+
);
|
|
2571
|
+
return null;
|
|
2572
|
+
}
|
|
2573
|
+
return value;
|
|
2574
|
+
}
|
|
2575
|
+
function requireOneOf(record, field, options, path, problems) {
|
|
2576
|
+
const value = record[field];
|
|
2577
|
+
if (typeof value !== "string" || !options.includes(value)) {
|
|
2578
|
+
problems.push(
|
|
2579
|
+
`${join(path, field)} must be one of ${options.map((o) => `"${o}"`).join(", ")}, got ${typeof value === "string" ? JSON.stringify(value) : describeType2(value)}.`
|
|
2580
|
+
);
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
function eachRecord(items, path, problems, check) {
|
|
2584
|
+
items.forEach((item, index) => {
|
|
2585
|
+
const itemPath = `${path}[${index}]`;
|
|
2586
|
+
if (isRecord3(item)) {
|
|
2587
|
+
check(item, itemPath);
|
|
2588
|
+
} else {
|
|
2589
|
+
problems.push(
|
|
2590
|
+
`${itemPath} must be an object, got ${describeType2(item)}.`
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
});
|
|
2594
|
+
}
|
|
2595
|
+
var HREF_RULE = "an http(s), mailto or relative URL";
|
|
2596
|
+
var IMAGE_RULE = "an http(s) or relative URL";
|
|
2597
|
+
function checkPayloadFields(kind, data, problems) {
|
|
2598
|
+
switch (kind) {
|
|
2599
|
+
case "TEXT":
|
|
2600
|
+
requireString(data, "value", "", problems);
|
|
2601
|
+
requireString(data, "label", "", problems);
|
|
2602
|
+
return;
|
|
2603
|
+
case "KPI":
|
|
2604
|
+
requireNumber(data, "value", "", problems);
|
|
2605
|
+
if (data["previous"] !== null) {
|
|
2606
|
+
optionalNumber(data, "previous", "", problems);
|
|
2607
|
+
}
|
|
2608
|
+
requireOneOf(data, "format", VALUE_FORMATS, "", problems);
|
|
2609
|
+
optionalString(data, "currency", "", problems);
|
|
2610
|
+
requireString(data, "label", "", problems);
|
|
2611
|
+
optionalString(data, "hint", "", problems);
|
|
2612
|
+
optionalBoolean(data, "higherIsBetter", "", problems);
|
|
2613
|
+
return;
|
|
2614
|
+
case "GAUGE":
|
|
2615
|
+
requireNumber(data, "value", "", problems);
|
|
2616
|
+
requireNumber(data, "max", "", problems);
|
|
2617
|
+
requireString(data, "label", "", problems);
|
|
2618
|
+
if (typeof data["max"] === "number" && data["max"] <= 0) {
|
|
2619
|
+
problems.push(`max must be greater than 0, got ${data["max"]}.`);
|
|
2620
|
+
}
|
|
2621
|
+
return;
|
|
2622
|
+
case "TABLE": {
|
|
2623
|
+
const columns = requireArray(data, "columns", "", problems);
|
|
2624
|
+
if (columns !== null) {
|
|
2625
|
+
eachRecord(columns, "columns", problems, (column, path) => {
|
|
2626
|
+
requireString(column, "label", path, problems);
|
|
2627
|
+
optionalBoolean(column, "numeric", path, problems);
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
const rows = requireArray(data, "rows", "", problems);
|
|
2631
|
+
if (rows !== null) {
|
|
2632
|
+
rows.forEach((row, rowIndex) => {
|
|
2633
|
+
const rowPath = `rows[${rowIndex}]`;
|
|
2634
|
+
if (!Array.isArray(row)) {
|
|
2635
|
+
problems.push(
|
|
2636
|
+
`${rowPath} must be an array of cells, got ${describeType2(row)}.`
|
|
2637
|
+
);
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
if (columns !== null && row.length !== columns.length) {
|
|
2641
|
+
problems.push(
|
|
2642
|
+
`${rowPath} has ${row.length} cell(s) but there are ${columns.length} column(s).`
|
|
2643
|
+
);
|
|
2644
|
+
}
|
|
2645
|
+
eachRecord(row, rowPath, problems, (cell, cellPath) => {
|
|
2646
|
+
requireString(cell, "text", cellPath, problems);
|
|
2647
|
+
optionalSortValue(cell, cellPath, problems);
|
|
2648
|
+
optionalUrl(
|
|
2649
|
+
cell,
|
|
2650
|
+
"href",
|
|
2651
|
+
cellPath,
|
|
2652
|
+
problems,
|
|
2653
|
+
isSafeHref,
|
|
2654
|
+
HREF_RULE
|
|
2655
|
+
);
|
|
2656
|
+
});
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
optionalString(data, "footer", "", problems);
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
case "BAR_LIST": {
|
|
2663
|
+
const items = requireArray(data, "items", "", problems);
|
|
2664
|
+
if (items !== null) {
|
|
2665
|
+
eachRecord(items, "items", problems, (item, path) => {
|
|
2666
|
+
requireString(item, "label", path, problems);
|
|
2667
|
+
requireNumber(item, "value", path, problems);
|
|
2668
|
+
optionalString(item, "display", path, problems);
|
|
2669
|
+
});
|
|
2670
|
+
}
|
|
2671
|
+
optionalNumber(data, "total", "", problems);
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
case "ALERT_LIST": {
|
|
2675
|
+
const items = requireArray(data, "items", "", problems);
|
|
2676
|
+
if (items !== null) {
|
|
2677
|
+
eachRecord(items, "items", problems, (item, path) => {
|
|
2678
|
+
requireString(item, "title", path, problems);
|
|
2679
|
+
optionalUrl(item, "href", path, problems, isSafeHref, HREF_RULE);
|
|
2680
|
+
optionalUrl(
|
|
2681
|
+
item,
|
|
2682
|
+
"thumbnailUrl",
|
|
2683
|
+
path,
|
|
2684
|
+
problems,
|
|
2685
|
+
isSafeImageUrl,
|
|
2686
|
+
IMAGE_RULE
|
|
2687
|
+
);
|
|
2688
|
+
requireString(item, "valueLabel", path, problems);
|
|
2689
|
+
optionalString(item, "detail", path, problems);
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
requireNumber(data, "total", "", problems);
|
|
2693
|
+
requireString(data, "emptyText", "", problems);
|
|
2694
|
+
if (items !== null && typeof data["total"] === "number" && data["total"] < items.length) {
|
|
2695
|
+
problems.push(
|
|
2696
|
+
`total (${data["total"]}) must be at least the number of items (${items.length}).`
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
case "GRAPH": {
|
|
2702
|
+
const series = requireArray(data, "series", "", problems);
|
|
2703
|
+
if (series !== null) {
|
|
2704
|
+
if (series.length > MAX_GRAPH_SERIES) {
|
|
2705
|
+
problems.push(
|
|
2706
|
+
`series has ${series.length} entries; at most ${MAX_GRAPH_SERIES} are allowed (fold the rest into "Other").`
|
|
2707
|
+
);
|
|
2708
|
+
}
|
|
2709
|
+
eachRecord(series, "series", problems, (entry, path) => {
|
|
2710
|
+
requireString(entry, "name", path, problems);
|
|
2711
|
+
const points = requireArray(entry, "points", path, problems);
|
|
2712
|
+
if (points !== null) {
|
|
2713
|
+
eachRecord(
|
|
2714
|
+
points,
|
|
2715
|
+
`${path}.points`,
|
|
2716
|
+
problems,
|
|
2717
|
+
(point, pointPath) => {
|
|
2718
|
+
requireString(point, "label", pointPath, problems);
|
|
2719
|
+
requireNumber(point, "value", pointPath, problems);
|
|
2720
|
+
}
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
requireOneOf(data, "chartType", ["bar", "line"], "", problems);
|
|
2726
|
+
requireOneOf(data, "valueFormat", VALUE_FORMATS, "", problems);
|
|
2727
|
+
optionalString(data, "currency", "", problems);
|
|
2728
|
+
optionalString(data, "xLabel", "", problems);
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
function checkDetailOption(detail, problems) {
|
|
2734
|
+
if (detail === void 0 || typeof detail === "boolean") return;
|
|
2735
|
+
if (!isRecord3(detail)) {
|
|
2736
|
+
problems.push(
|
|
2737
|
+
`detail must be true, false or an options object, got ${describeType2(detail)}.`
|
|
2738
|
+
);
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
optionalString(detail, "title", "detail", problems);
|
|
2742
|
+
const pageSize = detail["pageSize"];
|
|
2743
|
+
if (pageSize !== void 0 && !(typeof pageSize === "number" && Number.isInteger(pageSize) && pageSize >= 1 && pageSize <= MAX_DETAIL_PAGE_SIZE)) {
|
|
2744
|
+
problems.push(
|
|
2745
|
+
`detail.pageSize must be a whole number from 1 to ${MAX_DETAIL_PAGE_SIZE}, got ${typeof pageSize === "number" ? String(pageSize) : describeType2(pageSize)}.`
|
|
2746
|
+
);
|
|
2747
|
+
}
|
|
2748
|
+
if (detail["mode"] !== void 0) {
|
|
2749
|
+
requireOneOf(detail, "mode", DETAIL_MODES, "detail", problems);
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
function checkTableControlsOption(controls, kind, problems) {
|
|
2753
|
+
if (controls === void 0 || controls === false) return;
|
|
2754
|
+
if (typeof controls !== "boolean" && !isRecord3(controls)) {
|
|
2755
|
+
problems.push(
|
|
2756
|
+
`tableControls must be true, false or an options object, got ${describeType2(controls)}.`
|
|
2757
|
+
);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
if (isRecord3(controls)) {
|
|
2761
|
+
optionalBoolean(controls, "search", "tableControls", problems);
|
|
2762
|
+
optionalBoolean(controls, "sort", "tableControls", problems);
|
|
2763
|
+
}
|
|
2764
|
+
if (typeof kind === "string" && kind !== "TABLE") {
|
|
2765
|
+
problems.push(
|
|
2766
|
+
`tableControls only applies to TABLE widgets, but kind is "${kind}".`
|
|
2767
|
+
);
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
function checkWidthLimits(value, problems) {
|
|
2771
|
+
for (const field of ["minWidth", "maxWidth"]) {
|
|
2772
|
+
const limit = value[field];
|
|
2773
|
+
if (limit !== void 0 && !isWidgetWidth(limit)) {
|
|
2774
|
+
problems.push(
|
|
2775
|
+
`${field} must be an integer from ${MIN_WIDGET_WIDTH} to ${MAX_WIDGET_WIDTH} (twelfths of the row), got ${typeof limit === "number" ? String(limit) : describeType2(limit)}.`
|
|
2776
|
+
);
|
|
2777
|
+
return;
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
const { minWidth, maxWidth, width } = value;
|
|
2781
|
+
if (isWidgetWidth(minWidth) && isWidgetWidth(maxWidth) && minWidth > maxWidth) {
|
|
2782
|
+
problems.push(
|
|
2783
|
+
`minWidth (${minWidth}) must not be more than maxWidth (${maxWidth}).`
|
|
2784
|
+
);
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
if (isWidgetWidth(width)) {
|
|
2788
|
+
if (isWidgetWidth(minWidth) && width < minWidth) {
|
|
2789
|
+
problems.push(
|
|
2790
|
+
`width (${width}) must not be less than minWidth (${minWidth}).`
|
|
2791
|
+
);
|
|
2792
|
+
}
|
|
2793
|
+
if (isWidgetWidth(maxWidth) && width > maxWidth) {
|
|
2794
|
+
problems.push(
|
|
2795
|
+
`width (${width}) must not be more than maxWidth (${maxWidth}).`
|
|
2796
|
+
);
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2800
|
+
function checkLockedOption(locked, problems) {
|
|
2801
|
+
if (locked === void 0 || typeof locked === "boolean") return;
|
|
2802
|
+
if (!isRecord3(locked)) {
|
|
2803
|
+
problems.push(
|
|
2804
|
+
`locked must be true, false or an object, got ${describeType2(locked)}.`
|
|
2805
|
+
);
|
|
2806
|
+
return;
|
|
2807
|
+
}
|
|
2808
|
+
optionalBoolean(locked, "move", "locked", problems);
|
|
2077
2809
|
optionalBoolean(locked, "hide", "locked", problems);
|
|
2078
2810
|
optionalBoolean(locked, "minimize", "locked", problems);
|
|
2079
2811
|
optionalBoolean(locked, "options", "locked", problems);
|
|
2812
|
+
optionalBoolean(locked, "clone", "locked", problems);
|
|
2080
2813
|
}
|
|
2081
2814
|
function checkOptionColumns(option, path, kind, problems) {
|
|
2082
2815
|
const columns = option["columns"];
|
|
@@ -2089,7 +2822,7 @@ function checkOptionColumns(option, path, kind, problems) {
|
|
|
2089
2822
|
const keys = [];
|
|
2090
2823
|
columns.forEach((entry, index) => {
|
|
2091
2824
|
const entryPath = `${path}.columns[${index}]`;
|
|
2092
|
-
if (!
|
|
2825
|
+
if (!isRecord3(entry)) {
|
|
2093
2826
|
problems.push(
|
|
2094
2827
|
`${entryPath} must be an object, got ${describeType2(entry)}.`
|
|
2095
2828
|
);
|
|
@@ -2114,7 +2847,7 @@ function checkOptionColumns(option, path, kind, problems) {
|
|
|
2114
2847
|
return keys;
|
|
2115
2848
|
}
|
|
2116
2849
|
function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
2117
|
-
if (!
|
|
2850
|
+
if (!isRecord3(option)) {
|
|
2118
2851
|
problems.push(`${path} must be an object, got ${describeType2(option)}.`);
|
|
2119
2852
|
return;
|
|
2120
2853
|
}
|
|
@@ -2146,7 +2879,7 @@ function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
|
2146
2879
|
} else {
|
|
2147
2880
|
choices.forEach((choice, index) => {
|
|
2148
2881
|
const choicePath = `${path}.choices[${index}]`;
|
|
2149
|
-
if (!
|
|
2882
|
+
if (!isRecord3(choice)) {
|
|
2150
2883
|
problems.push(
|
|
2151
2884
|
`${choicePath} must be an object, got ${describeType2(choice)}.`
|
|
2152
2885
|
);
|
|
@@ -2236,7 +2969,7 @@ function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
|
2236
2969
|
} else {
|
|
2237
2970
|
presets.forEach((preset, index) => {
|
|
2238
2971
|
const presetPath = `${path}.presets[${index}]`;
|
|
2239
|
-
if (!
|
|
2972
|
+
if (!isRecord3(preset)) {
|
|
2240
2973
|
problems.push(
|
|
2241
2974
|
`${presetPath} must be an object, got ${describeType2(preset)}.`
|
|
2242
2975
|
);
|
|
@@ -2365,7 +3098,7 @@ function checkColumnIndex(value, path, problems) {
|
|
|
2365
3098
|
}
|
|
2366
3099
|
}
|
|
2367
3100
|
function checkConversion(conversion, path, baseKind, viewKind2, problems) {
|
|
2368
|
-
if (!
|
|
3101
|
+
if (!isRecord3(conversion)) {
|
|
2369
3102
|
problems.push(
|
|
2370
3103
|
`${path} must be an object with a type, got ${describeType2(conversion)}.`
|
|
2371
3104
|
);
|
|
@@ -2443,7 +3176,7 @@ function checkViews(value, problems) {
|
|
|
2443
3176
|
} else {
|
|
2444
3177
|
views.forEach((view, index) => {
|
|
2445
3178
|
const path = `views[${index}]`;
|
|
2446
|
-
if (!
|
|
3179
|
+
if (!isRecord3(view)) {
|
|
2447
3180
|
problems.push(
|
|
2448
3181
|
`${path} must be an object, got ${describeType2(view)}.`
|
|
2449
3182
|
);
|
|
@@ -2463,11 +3196,7 @@ function checkViews(value, problems) {
|
|
|
2463
3196
|
problems.push(`${path}.label must be a non-empty string.`);
|
|
2464
3197
|
}
|
|
2465
3198
|
requireOneOf(view, "kind", WIDGET_KINDS, path, problems);
|
|
2466
|
-
if (view["convert"]
|
|
2467
|
-
problems.push(
|
|
2468
|
-
`${path}.convert is required: views supplied by a provider are not supported yet.`
|
|
2469
|
-
);
|
|
2470
|
-
} else {
|
|
3199
|
+
if (view["convert"] !== void 0) {
|
|
2471
3200
|
checkConversion(
|
|
2472
3201
|
view["convert"],
|
|
2473
3202
|
`${path}.convert`,
|
|
@@ -2494,7 +3223,7 @@ function formatProblems(prefix, problems) {
|
|
|
2494
3223
|
}
|
|
2495
3224
|
function validateWidgetData(value, options = {}) {
|
|
2496
3225
|
const subject = options.widgetKey === void 0 ? "Widget data" : `Widget ${JSON.stringify(options.widgetKey)}`;
|
|
2497
|
-
if (!
|
|
3226
|
+
if (!isRecord3(value)) {
|
|
2498
3227
|
return err(`${subject}: must be an object, got ${describeType2(value)}.`);
|
|
2499
3228
|
}
|
|
2500
3229
|
if (value["empty"] === true) {
|
|
@@ -2524,7 +3253,7 @@ function validateWidgetData(value, options = {}) {
|
|
|
2524
3253
|
return ok(value);
|
|
2525
3254
|
}
|
|
2526
3255
|
function validateWidgetDefinition(value) {
|
|
2527
|
-
if (!
|
|
3256
|
+
if (!isRecord3(value)) {
|
|
2528
3257
|
return err(
|
|
2529
3258
|
`Widget definition: must be an object, got ${describeType2(value)}.`
|
|
2530
3259
|
);
|
|
@@ -2588,7 +3317,10 @@ function resolvePayload(definition, payload, options = {}) {
|
|
|
2588
3317
|
} else {
|
|
2589
3318
|
const result = validateWidgetData(payload, {
|
|
2590
3319
|
widgetKey: definition.key,
|
|
2591
|
-
expectedKind:
|
|
3320
|
+
expectedKind: loadViewKind(
|
|
3321
|
+
definition,
|
|
3322
|
+
options.viewValues?.[definition.key]
|
|
3323
|
+
)
|
|
2592
3324
|
});
|
|
2593
3325
|
if (!result.ok) {
|
|
2594
3326
|
return { definition, status: "error", error: result.error };
|
|
@@ -2599,7 +3331,14 @@ function resolvePayload(definition, payload, options = {}) {
|
|
|
2599
3331
|
if (emptyText !== null) {
|
|
2600
3332
|
return { definition, status: "empty", emptyText };
|
|
2601
3333
|
}
|
|
2602
|
-
return {
|
|
3334
|
+
return {
|
|
3335
|
+
definition,
|
|
3336
|
+
status: "ok",
|
|
3337
|
+
data: checked,
|
|
3338
|
+
...hasViews(definition) ? {
|
|
3339
|
+
view: loadViewKey(definition, options.viewValues?.[definition.key])
|
|
3340
|
+
} : {}
|
|
3341
|
+
};
|
|
2603
3342
|
}
|
|
2604
3343
|
function failedWidget(definition, error) {
|
|
2605
3344
|
return { definition, status: "error", error };
|
|
@@ -2608,8 +3347,15 @@ function loadingWidgets(definitions) {
|
|
|
2608
3347
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
2609
3348
|
}
|
|
2610
3349
|
function cacheKeyFor(definition, options, values) {
|
|
2611
|
-
const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
2612
|
-
|
|
3350
|
+
const base = options.cacheKey === void 0 ? definition.sourceKey ?? definition.key : options.cacheKey(definition);
|
|
3351
|
+
const view = loadViewKey(
|
|
3352
|
+
definition,
|
|
3353
|
+
options.viewValues?.[definition.key]
|
|
3354
|
+
);
|
|
3355
|
+
const viewPart = view === BASE_VIEW_KEY ? "" : `view=${encodeURIComponent(view)}`;
|
|
3356
|
+
const optionPart = values === void 0 ? "" : optionsCacheSuffix(definition, values, options);
|
|
3357
|
+
if (viewPart === "") return `${base}${optionPart}`;
|
|
3358
|
+
return optionPart === "" ? `${base}?${viewPart}` : `${base}?${viewPart}&${optionPart.slice(1)}`;
|
|
2613
3359
|
}
|
|
2614
3360
|
function optionValuesFor(definition, options) {
|
|
2615
3361
|
return resolveOptionValues(
|
|
@@ -2620,6 +3366,9 @@ function optionValuesFor(definition, options) {
|
|
|
2620
3366
|
}
|
|
2621
3367
|
function finalizeWidget(widget, values) {
|
|
2622
3368
|
if (Object.keys(values).length === 0) return widget;
|
|
3369
|
+
if (widget.status === "ok" && widget.view !== void 0 && widget.view !== BASE_VIEW_KEY) {
|
|
3370
|
+
return { ...widget, options: values };
|
|
3371
|
+
}
|
|
2623
3372
|
if (widget.status === "ok") {
|
|
2624
3373
|
const data = applyClientOptions(
|
|
2625
3374
|
widget.definition,
|
|
@@ -2677,7 +3426,8 @@ async function resolveWidgetUnapplied(definition, provider, context, options = {
|
|
|
2677
3426
|
if (controller.signal.aborted) throw new LoadInterruption();
|
|
2678
3427
|
return provider(context, definition, {
|
|
2679
3428
|
signal: controller.signal,
|
|
2680
|
-
options: values
|
|
3429
|
+
options: values,
|
|
3430
|
+
view: loadViewKey(definition, options.viewValues?.[definition.key])
|
|
2681
3431
|
});
|
|
2682
3432
|
}),
|
|
2683
3433
|
interrupted
|
|
@@ -2722,7 +3472,10 @@ async function resolveWidgets(definitions, providers, context, options = {}) {
|
|
|
2722
3472
|
visible.map(
|
|
2723
3473
|
(definition) => resolveWidget(
|
|
2724
3474
|
definition,
|
|
2725
|
-
Object.prototype.hasOwnProperty.call(
|
|
3475
|
+
Object.prototype.hasOwnProperty.call(
|
|
3476
|
+
providers,
|
|
3477
|
+
definition.sourceKey ?? definition.key
|
|
3478
|
+
) ? providers[definition.sourceKey ?? definition.key] : void 0,
|
|
2726
3479
|
context,
|
|
2727
3480
|
options
|
|
2728
3481
|
)
|
|
@@ -2738,9 +3491,18 @@ function createWidgetLoader(options) {
|
|
|
2738
3491
|
([key, values]) => values === void 0 ? [] : [[key, values]]
|
|
2739
3492
|
)
|
|
2740
3493
|
);
|
|
3494
|
+
const chosenViews = new Map(
|
|
3495
|
+
Object.entries(baseOptions.viewValues ?? {}).flatMap(
|
|
3496
|
+
([key, view]) => view === void 0 ? [] : [[key, view]]
|
|
3497
|
+
)
|
|
3498
|
+
);
|
|
2741
3499
|
const resolveOptions = baseOptions;
|
|
2742
3500
|
function currentOptions() {
|
|
2743
|
-
return {
|
|
3501
|
+
return {
|
|
3502
|
+
...resolveOptions,
|
|
3503
|
+
optionValues: Object.fromEntries(chosen),
|
|
3504
|
+
viewValues: Object.fromEntries(chosenViews)
|
|
3505
|
+
};
|
|
2744
3506
|
}
|
|
2745
3507
|
const unapplied = /* @__PURE__ */ new Map();
|
|
2746
3508
|
const visible = widgetsFor(definitions, context.roles);
|
|
@@ -2802,7 +3564,11 @@ function createWidgetLoader(options) {
|
|
|
2802
3564
|
}
|
|
2803
3565
|
}
|
|
2804
3566
|
if (!isCurrent()) return;
|
|
2805
|
-
const
|
|
3567
|
+
const providerKey = definition.sourceKey ?? key;
|
|
3568
|
+
const provider = Object.prototype.hasOwnProperty.call(
|
|
3569
|
+
providers,
|
|
3570
|
+
providerKey
|
|
3571
|
+
) ? providers[providerKey] : void 0;
|
|
2806
3572
|
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
2807
3573
|
...runOptions,
|
|
2808
3574
|
signal: controller.signal
|
|
@@ -2869,6 +3635,23 @@ function createWidgetLoader(options) {
|
|
|
2869
3635
|
}
|
|
2870
3636
|
start(definition, true);
|
|
2871
3637
|
}
|
|
3638
|
+
function setView(key, view) {
|
|
3639
|
+
if (disposed) return;
|
|
3640
|
+
const definition = definitionFor(key);
|
|
3641
|
+
if (definition === void 0) return;
|
|
3642
|
+
const before = loadViewKey(definition, chosenViews.get(key));
|
|
3643
|
+
if (view === void 0 || view === "") chosenViews.delete(key);
|
|
3644
|
+
else chosenViews.set(key, view);
|
|
3645
|
+
const after = loadViewKey(definition, chosenViews.get(key));
|
|
3646
|
+
if (before === after || !started.has(key)) return;
|
|
3647
|
+
const current = snapshot.find(
|
|
3648
|
+
(widget) => widget.definition.key === key
|
|
3649
|
+
);
|
|
3650
|
+
if (current !== void 0 && current.status !== "loading") {
|
|
3651
|
+
replace(key, { definition, status: "loading" });
|
|
3652
|
+
}
|
|
3653
|
+
start(definition, true);
|
|
3654
|
+
}
|
|
2872
3655
|
function dispose() {
|
|
2873
3656
|
disposed = true;
|
|
2874
3657
|
abortAll(new Error("The widget loader was disposed."));
|
|
@@ -2887,488 +3670,11 @@ function createWidgetLoader(options) {
|
|
|
2887
3670
|
loadAll,
|
|
2888
3671
|
refresh,
|
|
2889
3672
|
setOptions,
|
|
3673
|
+
setView,
|
|
2890
3674
|
dispose
|
|
2891
3675
|
};
|
|
2892
3676
|
}
|
|
2893
3677
|
|
|
2894
|
-
// src/core/pages.ts
|
|
2895
|
-
var DEFAULT_PAGE_KEY = "default";
|
|
2896
|
-
var DEFAULT_PAGE_ROWS = 4;
|
|
2897
|
-
function rowsAllowed(page, options) {
|
|
2898
|
-
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
2899
|
-
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
2900
|
-
}
|
|
2901
|
-
function hasPages(layout) {
|
|
2902
|
-
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
2903
|
-
}
|
|
2904
|
-
function pageList(layout) {
|
|
2905
|
-
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
2906
|
-
return layout.pages;
|
|
2907
|
-
}
|
|
2908
|
-
return [
|
|
2909
|
-
{
|
|
2910
|
-
key: DEFAULT_PAGE_KEY,
|
|
2911
|
-
title: "Page 1",
|
|
2912
|
-
order: layout.order,
|
|
2913
|
-
hidden: layout.hidden,
|
|
2914
|
-
minimized: layout.minimized
|
|
2915
|
-
}
|
|
2916
|
-
];
|
|
2917
|
-
}
|
|
2918
|
-
function withExplicitPages(layout) {
|
|
2919
|
-
if (hasPages(layout)) return layout;
|
|
2920
|
-
return {
|
|
2921
|
-
...layout,
|
|
2922
|
-
order: [],
|
|
2923
|
-
hidden: [],
|
|
2924
|
-
minimized: [],
|
|
2925
|
-
pages: pageList(layout)
|
|
2926
|
-
};
|
|
2927
|
-
}
|
|
2928
|
-
function sameKeys(a, b) {
|
|
2929
|
-
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
2930
|
-
}
|
|
2931
|
-
function pageLayout(layout, pageKey) {
|
|
2932
|
-
if (!hasPages(layout))
|
|
2933
|
-
return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
|
|
2934
|
-
const page = layout.pages?.find(
|
|
2935
|
-
(p) => p.key === pageKey
|
|
2936
|
-
);
|
|
2937
|
-
if (page === void 0) return void 0;
|
|
2938
|
-
const { pages, ...rest } = layout;
|
|
2939
|
-
return {
|
|
2940
|
-
...rest,
|
|
2941
|
-
order: page.order,
|
|
2942
|
-
hidden: page.hidden,
|
|
2943
|
-
minimized: page.minimized
|
|
2944
|
-
};
|
|
2945
|
-
}
|
|
2946
|
-
function withPageLayout(layout, pageKey, next) {
|
|
2947
|
-
const current = pageLayout(layout, pageKey);
|
|
2948
|
-
if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
|
|
2949
|
-
return layout;
|
|
2950
|
-
}
|
|
2951
|
-
if (!hasPages(layout)) {
|
|
2952
|
-
return {
|
|
2953
|
-
...layout,
|
|
2954
|
-
order: next.order,
|
|
2955
|
-
hidden: next.hidden,
|
|
2956
|
-
minimized: next.minimized
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
return {
|
|
2960
|
-
...layout,
|
|
2961
|
-
pages: (layout.pages ?? []).map(
|
|
2962
|
-
(page) => page.key === pageKey ? {
|
|
2963
|
-
...page,
|
|
2964
|
-
order: next.order,
|
|
2965
|
-
hidden: next.hidden,
|
|
2966
|
-
minimized: next.minimized
|
|
2967
|
-
} : page
|
|
2968
|
-
)
|
|
2969
|
-
};
|
|
2970
|
-
}
|
|
2971
|
-
function normalizedTitle(title) {
|
|
2972
|
-
return title.trim().normalize("NFKC").toLowerCase();
|
|
2973
|
-
}
|
|
2974
|
-
function resolvePageKey(layout, reference) {
|
|
2975
|
-
if (reference === void 0 || reference === "") return void 0;
|
|
2976
|
-
const pages = pageList(layout);
|
|
2977
|
-
const byKey = pages.find((p) => p.key === reference);
|
|
2978
|
-
if (byKey !== void 0) return byKey.key;
|
|
2979
|
-
const wanted = normalizedTitle(reference);
|
|
2980
|
-
return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
|
|
2981
|
-
}
|
|
2982
|
-
function homePageKey(layout, item) {
|
|
2983
|
-
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
2984
|
-
}
|
|
2985
|
-
function assignPages(definitions, layout) {
|
|
2986
|
-
const pages = pageList(layout);
|
|
2987
|
-
const assigned = new Map(pages.map((p) => [p.key, []]));
|
|
2988
|
-
for (const definition of definitions) {
|
|
2989
|
-
let pageKey;
|
|
2990
|
-
if (!resolveWidgetLock(definition).move) {
|
|
2991
|
-
pageKey = pages.find(
|
|
2992
|
-
(p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
|
|
2993
|
-
)?.key;
|
|
2994
|
-
}
|
|
2995
|
-
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
2996
|
-
}
|
|
2997
|
-
return assigned;
|
|
2998
|
-
}
|
|
2999
|
-
function pageOf(definitions, layout, key) {
|
|
3000
|
-
for (const [pageKey, widgets] of assignPages(definitions, layout)) {
|
|
3001
|
-
if (widgets.some((w) => w.key === key)) return pageKey;
|
|
3002
|
-
}
|
|
3003
|
-
return void 0;
|
|
3004
|
-
}
|
|
3005
|
-
function pageWidgets(definitions, layout, pageKey) {
|
|
3006
|
-
const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
|
|
3007
|
-
const view = pageLayout(layout, pageKey);
|
|
3008
|
-
return view === void 0 ? [] : visibleWidgets(onPage, view);
|
|
3009
|
-
}
|
|
3010
|
-
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
3011
|
-
const page = pageList(layout).find(
|
|
3012
|
-
(p) => p.key === pageKey
|
|
3013
|
-
);
|
|
3014
|
-
if (page === void 0) return void 0;
|
|
3015
|
-
const maxRows = rowsAllowed(page, options);
|
|
3016
|
-
const rowsUsed = placeRows(
|
|
3017
|
-
pageWidgets(definitions, layout, pageKey)
|
|
3018
|
-
).length;
|
|
3019
|
-
return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
|
|
3020
|
-
}
|
|
3021
|
-
function describePage(page) {
|
|
3022
|
-
return `Page "${page.title}"`;
|
|
3023
|
-
}
|
|
3024
|
-
function titleOf(item) {
|
|
3025
|
-
return item.title ?? item.key;
|
|
3026
|
-
}
|
|
3027
|
-
function checkTitle(layout, title, exceptKey) {
|
|
3028
|
-
const trimmed = title.trim();
|
|
3029
|
-
if (trimmed === "") return err("A page title must not be empty.");
|
|
3030
|
-
if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
|
|
3031
|
-
return err(
|
|
3032
|
-
`Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
|
|
3033
|
-
);
|
|
3034
|
-
}
|
|
3035
|
-
const wanted = normalizedTitle(trimmed);
|
|
3036
|
-
const clash = pageList(layout).find(
|
|
3037
|
-
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
3038
|
-
);
|
|
3039
|
-
if (clash !== void 0) {
|
|
3040
|
-
return err(`Another page is already called "${clash.title}".`);
|
|
3041
|
-
}
|
|
3042
|
-
return ok(trimmed);
|
|
3043
|
-
}
|
|
3044
|
-
function nextPageKey(layout) {
|
|
3045
|
-
const keys = new Set(pageList(layout).map((p) => p.key));
|
|
3046
|
-
let n = 1;
|
|
3047
|
-
while (keys.has(`page-${n}`)) n += 1;
|
|
3048
|
-
return `page-${n}`;
|
|
3049
|
-
}
|
|
3050
|
-
function nextPageTitle(layout) {
|
|
3051
|
-
const used = new Set(
|
|
3052
|
-
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
3053
|
-
);
|
|
3054
|
-
let n = pageList(layout).length + 1;
|
|
3055
|
-
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
3056
|
-
return `Page ${n}`;
|
|
3057
|
-
}
|
|
3058
|
-
function addPage(layout, options = {}) {
|
|
3059
|
-
if (pageList(layout).length >= MAX_PARSED_PAGES) {
|
|
3060
|
-
return err(
|
|
3061
|
-
`The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
|
|
3062
|
-
);
|
|
3063
|
-
}
|
|
3064
|
-
const key = options.key ?? nextPageKey(layout);
|
|
3065
|
-
if (pageList(layout).some((p) => p.key === key)) {
|
|
3066
|
-
return err(`A page with the key "${key}" already exists.`);
|
|
3067
|
-
}
|
|
3068
|
-
const title = checkTitle(
|
|
3069
|
-
layout,
|
|
3070
|
-
options.title ?? nextPageTitle(layout)
|
|
3071
|
-
);
|
|
3072
|
-
if (!title.ok) return title;
|
|
3073
|
-
const base = withExplicitPages(layout);
|
|
3074
|
-
const page = {
|
|
3075
|
-
key,
|
|
3076
|
-
title: title.value,
|
|
3077
|
-
order: [],
|
|
3078
|
-
hidden: [],
|
|
3079
|
-
minimized: []
|
|
3080
|
-
};
|
|
3081
|
-
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
3082
|
-
}
|
|
3083
|
-
function renamePage(layout, pageKey, title) {
|
|
3084
|
-
const page = pageList(layout).find(
|
|
3085
|
-
(p) => p.key === pageKey
|
|
3086
|
-
);
|
|
3087
|
-
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
3088
|
-
const checked = checkTitle(layout, title, pageKey);
|
|
3089
|
-
if (!checked.ok) return checked;
|
|
3090
|
-
if (checked.value === page.title) return ok(layout);
|
|
3091
|
-
const base = withExplicitPages(layout);
|
|
3092
|
-
return ok({
|
|
3093
|
-
...base,
|
|
3094
|
-
pages: (base.pages ?? []).map(
|
|
3095
|
-
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
3096
|
-
)
|
|
3097
|
-
});
|
|
3098
|
-
}
|
|
3099
|
-
function movePage(layout, pageKey, offset) {
|
|
3100
|
-
const pages = layout.pages ?? [];
|
|
3101
|
-
const from = pages.findIndex((p) => p.key === pageKey);
|
|
3102
|
-
if (from === -1) return layout;
|
|
3103
|
-
const to = Math.max(
|
|
3104
|
-
0,
|
|
3105
|
-
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
3106
|
-
);
|
|
3107
|
-
if (to === from) return layout;
|
|
3108
|
-
const next = [...pages];
|
|
3109
|
-
const [moved] = next.splice(from, 1);
|
|
3110
|
-
next.splice(to, 0, moved);
|
|
3111
|
-
return { ...layout, pages: next };
|
|
3112
|
-
}
|
|
3113
|
-
function overflowing(definitions, layout, options) {
|
|
3114
|
-
return pageList(layout).find(
|
|
3115
|
-
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
3116
|
-
);
|
|
3117
|
-
}
|
|
3118
|
-
function removePage(definitions, layout, pageKey, options = {}) {
|
|
3119
|
-
const pages = pageList(layout);
|
|
3120
|
-
const index = pages.findIndex((p) => p.key === pageKey);
|
|
3121
|
-
const page = pages[index];
|
|
3122
|
-
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
3123
|
-
if (pages.length === 1) {
|
|
3124
|
-
return err(
|
|
3125
|
-
`${describePage(page)} is the only page, so it can't be removed.`
|
|
3126
|
-
);
|
|
3127
|
-
}
|
|
3128
|
-
const target = pages[index > 0 ? index - 1 : 1];
|
|
3129
|
-
const merged = {
|
|
3130
|
-
...layout,
|
|
3131
|
-
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
3132
|
-
(p) => p.key === target.key ? {
|
|
3133
|
-
...p,
|
|
3134
|
-
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
3135
|
-
hidden: [...p.hidden, ...page.hidden],
|
|
3136
|
-
minimized: [...p.minimized, ...page.minimized]
|
|
3137
|
-
} : p
|
|
3138
|
-
)
|
|
3139
|
-
};
|
|
3140
|
-
const full = overflowing(
|
|
3141
|
-
definitions,
|
|
3142
|
-
merged,
|
|
3143
|
-
options
|
|
3144
|
-
);
|
|
3145
|
-
if (full !== void 0) {
|
|
3146
|
-
return err(
|
|
3147
|
-
`${describePage(page)} has widgets that don't fit on the other pages (${describePage(full)} would need more than ${rowsAllowed(full, options)} rows). Move or hide some first.`
|
|
3148
|
-
);
|
|
3149
|
-
}
|
|
3150
|
-
return ok(merged);
|
|
3151
|
-
}
|
|
3152
|
-
function without(keys, key) {
|
|
3153
|
-
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
3154
|
-
}
|
|
3155
|
-
function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
|
|
3156
|
-
const widget = definitions.find((d) => d.key === widgetKey);
|
|
3157
|
-
if (widget === void 0) {
|
|
3158
|
-
return err(
|
|
3159
|
-
`Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
|
|
3160
|
-
);
|
|
3161
|
-
}
|
|
3162
|
-
const pages = pageList(layout);
|
|
3163
|
-
const target = pages.find((p) => p.key === targetKey);
|
|
3164
|
-
if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
|
|
3165
|
-
const sourceKey = pageOf(definitions, layout, widgetKey);
|
|
3166
|
-
if (sourceKey === targetKey) return ok(layout);
|
|
3167
|
-
if (resolveWidgetLock(widget).move) {
|
|
3168
|
-
return err(
|
|
3169
|
-
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
3170
|
-
);
|
|
3171
|
-
}
|
|
3172
|
-
const base = withExplicitPages(layout);
|
|
3173
|
-
const source = (base.pages ?? []).find(
|
|
3174
|
-
(p) => p.key === sourceKey
|
|
3175
|
-
);
|
|
3176
|
-
const hidden = source.hidden.includes(widgetKey);
|
|
3177
|
-
const minimized = source.minimized.includes(widgetKey);
|
|
3178
|
-
const targetOrder = pageWidgets(
|
|
3179
|
-
definitions,
|
|
3180
|
-
base,
|
|
3181
|
-
targetKey
|
|
3182
|
-
).map((d) => d.key);
|
|
3183
|
-
const moved = {
|
|
3184
|
-
...base,
|
|
3185
|
-
pages: (base.pages ?? []).map((p) => {
|
|
3186
|
-
if (p.key === sourceKey) {
|
|
3187
|
-
return {
|
|
3188
|
-
...p,
|
|
3189
|
-
order: without(p.order, widgetKey),
|
|
3190
|
-
hidden: without(p.hidden, widgetKey),
|
|
3191
|
-
minimized: without(p.minimized, widgetKey)
|
|
3192
|
-
};
|
|
3193
|
-
}
|
|
3194
|
-
if (p.key === targetKey) {
|
|
3195
|
-
return {
|
|
3196
|
-
...p,
|
|
3197
|
-
order: hidden ? p.order : [...targetOrder, widgetKey],
|
|
3198
|
-
hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
|
|
3199
|
-
minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
|
|
3200
|
-
};
|
|
3201
|
-
}
|
|
3202
|
-
return p;
|
|
3203
|
-
})
|
|
3204
|
-
};
|
|
3205
|
-
if (!hidden) {
|
|
3206
|
-
const allowed = rowsAllowed(target, options);
|
|
3207
|
-
const needed = placeRows(
|
|
3208
|
-
pageWidgets(definitions, moved, targetKey)
|
|
3209
|
-
).length;
|
|
3210
|
-
if (needed > allowed) {
|
|
3211
|
-
return err(
|
|
3212
|
-
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
3213
|
-
);
|
|
3214
|
-
}
|
|
3215
|
-
}
|
|
3216
|
-
return ok(moved);
|
|
3217
|
-
}
|
|
3218
|
-
function withoutLockField(definition) {
|
|
3219
|
-
if (definition.locked === void 0) return definition;
|
|
3220
|
-
const copy = { ...definition };
|
|
3221
|
-
delete copy.locked;
|
|
3222
|
-
return copy;
|
|
3223
|
-
}
|
|
3224
|
-
function uniqueTitle2(taken, title, index) {
|
|
3225
|
-
let base = title.trim().slice(0, MAX_PAGE_TITLE_LENGTH);
|
|
3226
|
-
if (base === "") base = `Page ${index + 1}`;
|
|
3227
|
-
let candidate = base;
|
|
3228
|
-
let n = 1;
|
|
3229
|
-
while (taken.has(normalizedTitle(candidate))) {
|
|
3230
|
-
const suffix = ` (#${n})`;
|
|
3231
|
-
candidate = `${base.slice(0, MAX_PAGE_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
3232
|
-
n += 1;
|
|
3233
|
-
}
|
|
3234
|
-
taken.add(normalizedTitle(candidate));
|
|
3235
|
-
return candidate;
|
|
3236
|
-
}
|
|
3237
|
-
function samePages(a, b) {
|
|
3238
|
-
return a.length === b.length && a.every((p, i) => {
|
|
3239
|
-
const q = b[i];
|
|
3240
|
-
return p.key === q.key && p.title === q.title && p.maxRows === q.maxRows && sameKeys(p.order, q.order) && sameKeys(p.hidden, q.hidden) && sameKeys(p.minimized, q.minimized);
|
|
3241
|
-
});
|
|
3242
|
-
}
|
|
3243
|
-
function normalizeLayout(definitions, layout, options = {}) {
|
|
3244
|
-
const known = pruneLayout(definitions, layout);
|
|
3245
|
-
const enforced = enforceLocks(definitions, known, options);
|
|
3246
|
-
const locked = normalizeSettings(
|
|
3247
|
-
definitions,
|
|
3248
|
-
enforced,
|
|
3249
|
-
options
|
|
3250
|
-
);
|
|
3251
|
-
if (!hasPages(locked)) return locked === layout ? layout : locked;
|
|
3252
|
-
const defs = effectiveDefinitions(
|
|
3253
|
-
options.overrideLocks === true ? definitions.map(withoutLockField) : definitions,
|
|
3254
|
-
locked,
|
|
3255
|
-
options
|
|
3256
|
-
);
|
|
3257
|
-
const taken = /* @__PURE__ */ new Set();
|
|
3258
|
-
const seenKeys = /* @__PURE__ */ new Set();
|
|
3259
|
-
const held = /* @__PURE__ */ new Set();
|
|
3260
|
-
let pages = [];
|
|
3261
|
-
(locked.pages ?? []).forEach((page, index) => {
|
|
3262
|
-
if (seenKeys.has(page.key)) return;
|
|
3263
|
-
seenKeys.add(page.key);
|
|
3264
|
-
const keep = (list) => [
|
|
3265
|
-
...new Set(list.filter((k) => !held.has(k)))
|
|
3266
|
-
];
|
|
3267
|
-
const next = {
|
|
3268
|
-
...page,
|
|
3269
|
-
title: uniqueTitle2(taken, page.title, index),
|
|
3270
|
-
order: keep(page.order),
|
|
3271
|
-
hidden: keep(page.hidden),
|
|
3272
|
-
minimized: keep(page.minimized)
|
|
3273
|
-
};
|
|
3274
|
-
for (const k of [...next.order, ...next.hidden]) held.add(k);
|
|
3275
|
-
pages.push(next);
|
|
3276
|
-
});
|
|
3277
|
-
const work = () => ({ ...locked, pages });
|
|
3278
|
-
const pinned = defs.filter((d) => resolveWidgetLock(d).move);
|
|
3279
|
-
for (const widget of pinned) {
|
|
3280
|
-
const home = homePageKey(work(), widget);
|
|
3281
|
-
const keysHere = (p, list) => p.key !== home && p[list].includes(widget.key);
|
|
3282
|
-
const wasHidden = pages.some((p) => keysHere(p, "hidden"));
|
|
3283
|
-
const wasMinimized = pages.some((p) => keysHere(p, "minimized"));
|
|
3284
|
-
if (!wasHidden && !wasMinimized) continue;
|
|
3285
|
-
pages = pages.map((p) => {
|
|
3286
|
-
let next = p;
|
|
3287
|
-
if (keysHere(p, "hidden")) {
|
|
3288
|
-
next = { ...next, hidden: without(next.hidden, widget.key) };
|
|
3289
|
-
}
|
|
3290
|
-
if (keysHere(p, "minimized")) {
|
|
3291
|
-
next = { ...next, minimized: without(next.minimized, widget.key) };
|
|
3292
|
-
}
|
|
3293
|
-
if (p.key === home) {
|
|
3294
|
-
next = {
|
|
3295
|
-
...next,
|
|
3296
|
-
hidden: wasHidden && !next.hidden.includes(widget.key) ? [...next.hidden, widget.key] : next.hidden,
|
|
3297
|
-
minimized: wasMinimized && !next.minimized.includes(widget.key) ? [...next.minimized, widget.key] : next.minimized
|
|
3298
|
-
};
|
|
3299
|
-
}
|
|
3300
|
-
return next;
|
|
3301
|
-
});
|
|
3302
|
-
}
|
|
3303
|
-
const placedKeys = /* @__PURE__ */ new Set();
|
|
3304
|
-
for (const p of pages) {
|
|
3305
|
-
for (const k of [...p.order, ...p.hidden]) placedKeys.add(k);
|
|
3306
|
-
}
|
|
3307
|
-
const pinnedKeys = new Set(pinned.map((d) => d.key));
|
|
3308
|
-
const unplaced = defs.filter((d) => !placedKeys.has(d.key) && !pinnedKeys.has(d.key)).map((definition, index) => ({ definition, index })).sort(
|
|
3309
|
-
(a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
|
|
3310
|
-
).map(({ definition }) => definition);
|
|
3311
|
-
for (const widget of unplaced) {
|
|
3312
|
-
const home = homePageKey(work(), widget);
|
|
3313
|
-
pages = pages.map(
|
|
3314
|
-
(p) => p.key === home ? { ...p, order: [...p.order, widget.key] } : p
|
|
3315
|
-
);
|
|
3316
|
-
}
|
|
3317
|
-
for (let i = 0; i < pages.length; i += 1) {
|
|
3318
|
-
const page = pages[i];
|
|
3319
|
-
const shown = pageWidgets(defs, work(), page.key);
|
|
3320
|
-
const fits = fitCount(shown, rowsAllowed(page, options));
|
|
3321
|
-
if (fits >= shown.length) continue;
|
|
3322
|
-
const moving = shown.slice(fits).filter((d) => !pinnedKeys.has(d.key)).map((d) => d.key);
|
|
3323
|
-
if (moving.length === 0) continue;
|
|
3324
|
-
const moveSet = new Set(moving);
|
|
3325
|
-
const movedMinimized = page.minimized.filter(
|
|
3326
|
-
(k) => moveSet.has(k)
|
|
3327
|
-
);
|
|
3328
|
-
pages = pages.map(
|
|
3329
|
-
(p) => p.key === page.key ? {
|
|
3330
|
-
...p,
|
|
3331
|
-
order: p.order.filter((k) => !moveSet.has(k)),
|
|
3332
|
-
minimized: p.minimized.filter((k) => !moveSet.has(k))
|
|
3333
|
-
} : p
|
|
3334
|
-
);
|
|
3335
|
-
let placed = false;
|
|
3336
|
-
for (let j = i + 1; j < pages.length && !placed; j += 1) {
|
|
3337
|
-
const candidate = pages[j];
|
|
3338
|
-
const trial = pages.map(
|
|
3339
|
-
(p) => p.key === candidate.key ? {
|
|
3340
|
-
...p,
|
|
3341
|
-
order: [...moving, ...p.order],
|
|
3342
|
-
minimized: [...p.minimized, ...movedMinimized]
|
|
3343
|
-
} : p
|
|
3344
|
-
);
|
|
3345
|
-
const items = pageWidgets(
|
|
3346
|
-
defs,
|
|
3347
|
-
{ ...locked, pages: trial },
|
|
3348
|
-
candidate.key
|
|
3349
|
-
);
|
|
3350
|
-
if (placeRows(items).length <= rowsAllowed(candidate, options)) {
|
|
3351
|
-
pages = trial;
|
|
3352
|
-
placed = true;
|
|
3353
|
-
}
|
|
3354
|
-
}
|
|
3355
|
-
if (!placed) {
|
|
3356
|
-
const key = nextPageKey(work());
|
|
3357
|
-
const title = uniqueTitle2(
|
|
3358
|
-
new Set(pages.map((p) => normalizedTitle(p.title))),
|
|
3359
|
-
`Page ${pages.length + 1}`,
|
|
3360
|
-
pages.length
|
|
3361
|
-
);
|
|
3362
|
-
pages = [
|
|
3363
|
-
...pages,
|
|
3364
|
-
{ key, title, order: moving, hidden: [], minimized: movedMinimized }
|
|
3365
|
-
];
|
|
3366
|
-
}
|
|
3367
|
-
}
|
|
3368
|
-
const result = work();
|
|
3369
|
-
return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) && locked.settings === layout.settings ? layout : result;
|
|
3370
|
-
}
|
|
3371
|
-
|
|
3372
3678
|
// src/core/scale.ts
|
|
3373
3679
|
function niceStep(value) {
|
|
3374
3680
|
if (!(value > 0) || !Number.isFinite(value)) return 1;
|
|
@@ -4214,6 +4520,7 @@ exports.MAX_DETAIL_PAGE_SIZE = MAX_DETAIL_PAGE_SIZE;
|
|
|
4214
4520
|
exports.MAX_GRAPH_SERIES = MAX_GRAPH_SERIES;
|
|
4215
4521
|
exports.MAX_PAGE_ROWS = MAX_PAGE_ROWS;
|
|
4216
4522
|
exports.MAX_PAGE_TITLE_LENGTH = MAX_PAGE_TITLE_LENGTH;
|
|
4523
|
+
exports.MAX_PARSED_CLONES = MAX_PARSED_CLONES;
|
|
4217
4524
|
exports.MAX_PARSED_PAGES = MAX_PARSED_PAGES;
|
|
4218
4525
|
exports.MAX_PARSED_SETTINGS = MAX_PARSED_SETTINGS;
|
|
4219
4526
|
exports.MAX_REPORTED_PROBLEMS = MAX_REPORTED_PROBLEMS;
|
|
@@ -4239,7 +4546,10 @@ exports.applyView = applyView;
|
|
|
4239
4546
|
exports.assignPages = assignPages;
|
|
4240
4547
|
exports.baseColumnSpan = baseColumnSpan;
|
|
4241
4548
|
exports.cacheKeyFor = cacheKeyFor;
|
|
4549
|
+
exports.canDuplicate = canDuplicate;
|
|
4550
|
+
exports.cleanClones = cleanClones;
|
|
4242
4551
|
exports.clearWidgetSettings = clearWidgetSettings;
|
|
4552
|
+
exports.cloneNumber = cloneNumber;
|
|
4243
4553
|
exports.coerceOptionValue = coerceOptionValue;
|
|
4244
4554
|
exports.convertView = convertView;
|
|
4245
4555
|
exports.createLayoutPersistence = createLayoutPersistence;
|
|
@@ -4248,8 +4558,10 @@ exports.createWidgetLoader = createWidgetLoader;
|
|
|
4248
4558
|
exports.defaultDetailQuery = defaultDetailQuery;
|
|
4249
4559
|
exports.defaultOptionValue = defaultOptionValue;
|
|
4250
4560
|
exports.defineWidget = defineWidget;
|
|
4561
|
+
exports.deleteClone = deleteClone;
|
|
4251
4562
|
exports.deriveDetailData = deriveDetailData;
|
|
4252
4563
|
exports.describeUnknownError = describeUnknownError;
|
|
4564
|
+
exports.duplicateWidget = duplicateWidget;
|
|
4253
4565
|
exports.effectiveDefinitions = effectiveDefinitions;
|
|
4254
4566
|
exports.effectiveViewKey = effectiveViewKey;
|
|
4255
4567
|
exports.emptyTextFor = emptyTextFor;
|
|
@@ -4279,6 +4591,7 @@ exports.isHidden = isHidden;
|
|
|
4279
4591
|
exports.isIsoInterval = isIsoInterval;
|
|
4280
4592
|
exports.isMinimized = isMinimized;
|
|
4281
4593
|
exports.isOptionsLocked = isOptionsLocked;
|
|
4594
|
+
exports.isProviderView = isProviderView;
|
|
4282
4595
|
exports.isSafeHref = isSafeHref;
|
|
4283
4596
|
exports.isSafeImageUrl = isSafeImageUrl;
|
|
4284
4597
|
exports.isTitleInUse = isTitleInUse;
|
|
@@ -4290,6 +4603,8 @@ exports.itemWidth = itemWidth;
|
|
|
4290
4603
|
exports.kindLabel = kindLabel;
|
|
4291
4604
|
exports.kpiDelta = kpiDelta;
|
|
4292
4605
|
exports.labelStride = labelStride;
|
|
4606
|
+
exports.loadViewKey = loadViewKey;
|
|
4607
|
+
exports.loadViewKind = loadViewKind;
|
|
4293
4608
|
exports.loadingWidgets = loadingWidgets;
|
|
4294
4609
|
exports.memoryAdapter = memoryAdapter;
|
|
4295
4610
|
exports.minimizeWidget = minimizeWidget;
|
|
@@ -4299,6 +4614,7 @@ exports.moveWidgetBy = moveWidgetBy;
|
|
|
4299
4614
|
exports.moveWidgetDown = moveWidgetDown;
|
|
4300
4615
|
exports.moveWidgetToPage = moveWidgetToPage;
|
|
4301
4616
|
exports.moveWidgetUp = moveWidgetUp;
|
|
4617
|
+
exports.nextCloneKey = nextCloneKey;
|
|
4302
4618
|
exports.nextPageKey = nextPageKey;
|
|
4303
4619
|
exports.niceDomain = niceDomain;
|
|
4304
4620
|
exports.niceStep = niceStep;
|
|
@@ -4314,6 +4630,7 @@ exports.pageList = pageList;
|
|
|
4314
4630
|
exports.pageOf = pageOf;
|
|
4315
4631
|
exports.pageRoom = pageRoom;
|
|
4316
4632
|
exports.pageWidgets = pageWidgets;
|
|
4633
|
+
exports.parseClones = parseClones;
|
|
4317
4634
|
exports.parseDetailQuery = parseDetailQuery;
|
|
4318
4635
|
exports.parseLayout = parseLayout;
|
|
4319
4636
|
exports.parseSettings = parseSettings;
|
|
@@ -4337,6 +4654,7 @@ exports.resolveWidgetUnapplied = resolveWidgetUnapplied;
|
|
|
4337
4654
|
exports.resolveWidgets = resolveWidgets;
|
|
4338
4655
|
exports.restoreWidget = restoreWidget;
|
|
4339
4656
|
exports.sameOptionValues = sameOptionValues;
|
|
4657
|
+
exports.serializeClones = serializeClones;
|
|
4340
4658
|
exports.serializeDetailQuery = serializeDetailQuery;
|
|
4341
4659
|
exports.serializeLayout = serializeLayout;
|
|
4342
4660
|
exports.serializeSettings = serializeSettings;
|
|
@@ -4345,6 +4663,7 @@ exports.shareOf = shareOf;
|
|
|
4345
4663
|
exports.showWidget = showWidget;
|
|
4346
4664
|
exports.sortDefinitions = sortDefinitions;
|
|
4347
4665
|
exports.sortFromOptions = sortFromOptions;
|
|
4666
|
+
exports.suggestedViewTitle = suggestedViewTitle;
|
|
4348
4667
|
exports.tableDetailData = tableDetailData;
|
|
4349
4668
|
exports.toggleHidden = toggleHidden;
|
|
4350
4669
|
exports.toggleMinimized = toggleMinimized;
|
|
@@ -4357,12 +4676,14 @@ exports.validateWidgetDefinition = validateWidgetDefinition;
|
|
|
4357
4676
|
exports.viewChoices = viewChoices;
|
|
4358
4677
|
exports.viewKind = viewKind;
|
|
4359
4678
|
exports.viewNote = viewNote;
|
|
4679
|
+
exports.viewValuesFromLayout = viewValuesFromLayout;
|
|
4360
4680
|
exports.visibleWidgets = visibleWidgets;
|
|
4361
4681
|
exports.widgetOptionValues = widgetOptionValues;
|
|
4362
4682
|
exports.widgetsFor = widgetsFor;
|
|
4363
4683
|
exports.widthForSize = widthForSize;
|
|
4364
4684
|
exports.widthRange = widthRange;
|
|
4365
4685
|
exports.widthShareLabel = widthShareLabel;
|
|
4686
|
+
exports.withClones = withClones;
|
|
4366
4687
|
exports.withEncoding = withEncoding;
|
|
4367
4688
|
exports.withFallback = withFallback;
|
|
4368
4689
|
exports.withLogging = withLogging;
|