@richardmcquiston01/dashboard-widgets-toolkit 0.14.0 → 0.16.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 +2232 -1587
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +49 -3
- package/dist/core.d.ts +49 -3
- package/dist/core.js +2210 -1588
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +2232 -1587
- 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 +2210 -1588
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +600 -59
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +25 -2
- package/dist/react.d.ts +25 -2
- package/dist/react.js +600 -59
- package/dist/react.js.map +1 -1
- package/dist/{storage-_Eb8Y98-.d.cts → storage-oivwB7al.d.cts} +247 -56
- package/dist/{storage-_Eb8Y98-.d.ts → storage-oivwB7al.d.ts} +247 -56
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -1,86 +1,3 @@
|
|
|
1
|
-
// src/core/payload.ts
|
|
2
|
-
var WIDGET_KINDS = [
|
|
3
|
-
"TEXT",
|
|
4
|
-
"KPI",
|
|
5
|
-
"GAUGE",
|
|
6
|
-
"TABLE",
|
|
7
|
-
"BAR_LIST",
|
|
8
|
-
"ALERT_LIST",
|
|
9
|
-
"GRAPH"
|
|
10
|
-
];
|
|
11
|
-
function isWidgetKind(value) {
|
|
12
|
-
return typeof value === "string" && WIDGET_KINDS.includes(value);
|
|
13
|
-
}
|
|
14
|
-
var VALUE_FORMATS = ["number", "currency", "percent"];
|
|
15
|
-
var MAX_GRAPH_SERIES = 8;
|
|
16
|
-
var DEFAULT_EMPTY_TEXT = "Nothing to show yet.";
|
|
17
|
-
function emptyWidget(text = DEFAULT_EMPTY_TEXT) {
|
|
18
|
-
return { empty: true, text };
|
|
19
|
-
}
|
|
20
|
-
function isWidgetEmptyState(payload) {
|
|
21
|
-
return "empty" in payload && payload.empty === true;
|
|
22
|
-
}
|
|
23
|
-
function emptyTextFor(payload) {
|
|
24
|
-
if (isWidgetEmptyState(payload)) {
|
|
25
|
-
return payload.text;
|
|
26
|
-
}
|
|
27
|
-
switch (payload.kind) {
|
|
28
|
-
case "TABLE":
|
|
29
|
-
return payload.rows.length === 0 ? DEFAULT_EMPTY_TEXT : null;
|
|
30
|
-
case "BAR_LIST":
|
|
31
|
-
return payload.items.length === 0 ? DEFAULT_EMPTY_TEXT : null;
|
|
32
|
-
case "ALERT_LIST":
|
|
33
|
-
return payload.items.length === 0 ? payload.emptyText : null;
|
|
34
|
-
case "GRAPH":
|
|
35
|
-
return payload.series.every((series) => series.points.length === 0) ? DEFAULT_EMPTY_TEXT : null;
|
|
36
|
-
case "TEXT":
|
|
37
|
-
case "KPI":
|
|
38
|
-
case "GAUGE":
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/core/compat.ts
|
|
44
|
-
function isStringArray(value) {
|
|
45
|
-
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
46
|
-
}
|
|
47
|
-
function upgradeLegacyWidgetData(value) {
|
|
48
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
49
|
-
return value;
|
|
50
|
-
}
|
|
51
|
-
const record = value;
|
|
52
|
-
if (record["kind"] === "TABLE" && isStringArray(record["columns"]) && Array.isArray(record["rows"]) && record["rows"].every(isStringArray)) {
|
|
53
|
-
const rows = record["rows"];
|
|
54
|
-
return {
|
|
55
|
-
...record,
|
|
56
|
-
columns: record["columns"].map((label) => ({ label })),
|
|
57
|
-
rows: rows.map((row) => row.map((text) => ({ text })))
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
if (record["kind"] === "GRAPH" && Array.isArray(record["points"]) && record["series"] === void 0) {
|
|
61
|
-
const { points, ...rest } = record;
|
|
62
|
-
return {
|
|
63
|
-
...rest,
|
|
64
|
-
series: [{ name: "Value", points }],
|
|
65
|
-
chartType: "bar",
|
|
66
|
-
valueFormat: "number"
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
function fromLegacyDefinition(legacy) {
|
|
72
|
-
if (!isWidgetKind(legacy.viewType)) {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
key: legacy.widgetKey,
|
|
77
|
-
title: legacy.title,
|
|
78
|
-
...legacy.description === null ? {} : { description: legacy.description },
|
|
79
|
-
kind: legacy.viewType,
|
|
80
|
-
sortOrder: legacy.sortOrder
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
1
|
// src/core/definition.ts
|
|
85
2
|
var WIDGET_SIZES = ["small", "medium", "large", "full"];
|
|
86
3
|
var WIDGET_FILLS = ["height", "width", "both"];
|
|
@@ -88,13 +5,15 @@ var NOT_LOCKED = Object.freeze({
|
|
|
88
5
|
move: false,
|
|
89
6
|
hide: false,
|
|
90
7
|
minimize: false,
|
|
91
|
-
options: false
|
|
8
|
+
options: false,
|
|
9
|
+
clone: false
|
|
92
10
|
});
|
|
93
11
|
var FULLY_LOCKED = Object.freeze({
|
|
94
12
|
move: true,
|
|
95
13
|
hide: true,
|
|
96
14
|
minimize: true,
|
|
97
|
-
options: true
|
|
15
|
+
options: true,
|
|
16
|
+
clone: true
|
|
98
17
|
});
|
|
99
18
|
function resolveWidgetLock(definition) {
|
|
100
19
|
const setting = definition.locked;
|
|
@@ -104,7 +23,8 @@ function resolveWidgetLock(definition) {
|
|
|
104
23
|
move: setting.move === true,
|
|
105
24
|
hide: setting.hide === true,
|
|
106
25
|
minimize: setting.minimize === true,
|
|
107
|
-
options: setting.options === true
|
|
26
|
+
options: setting.options === true,
|
|
27
|
+
clone: setting.clone === true
|
|
108
28
|
};
|
|
109
29
|
}
|
|
110
30
|
function defineWidget(definition) {
|
|
@@ -128,6 +48,103 @@ function isWidgetVisibleTo(definition, roles) {
|
|
|
128
48
|
return definition.roles.some((role) => roles.includes(role));
|
|
129
49
|
}
|
|
130
50
|
|
|
51
|
+
// src/core/grid.ts
|
|
52
|
+
var WIDTH_COLUMNS = 12;
|
|
53
|
+
var MIN_WIDGET_WIDTH = 2;
|
|
54
|
+
var MAX_WIDGET_WIDTH = 12;
|
|
55
|
+
function isWidgetWidth(value) {
|
|
56
|
+
return typeof value === "number" && Number.isInteger(value) && value >= MIN_WIDGET_WIDTH && value <= MAX_WIDGET_WIDTH;
|
|
57
|
+
}
|
|
58
|
+
function widthForSize(size) {
|
|
59
|
+
switch (size) {
|
|
60
|
+
case "small":
|
|
61
|
+
return 3;
|
|
62
|
+
case "large":
|
|
63
|
+
return 6;
|
|
64
|
+
case "full":
|
|
65
|
+
return 12;
|
|
66
|
+
default:
|
|
67
|
+
return 4;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function itemWidth(item) {
|
|
71
|
+
return isWidgetWidth(item.width) ? item.width : widthForSize(item.defaultSize);
|
|
72
|
+
}
|
|
73
|
+
function usesWidthColumns(items) {
|
|
74
|
+
return items.some((item) => item.width !== void 0);
|
|
75
|
+
}
|
|
76
|
+
function fillsWidth(fill) {
|
|
77
|
+
return fill === "width" || fill === "both";
|
|
78
|
+
}
|
|
79
|
+
function fillsHeight(fill) {
|
|
80
|
+
return fill === "height" || fill === "both";
|
|
81
|
+
}
|
|
82
|
+
function baseColumnSpan(size, columns, largeSpan = 2) {
|
|
83
|
+
if (size === "full") return columns;
|
|
84
|
+
if (size === "large") return Math.min(largeSpan, columns);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
function fillColumnSpans(items, columns, largeSpan = 2) {
|
|
88
|
+
return placeAndFill(
|
|
89
|
+
items,
|
|
90
|
+
columns,
|
|
91
|
+
(item, trackCount) => baseColumnSpan(item.defaultSize, trackCount, largeSpan)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
function fillWidthSpans(items) {
|
|
95
|
+
return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
|
|
96
|
+
}
|
|
97
|
+
function placeRows(items) {
|
|
98
|
+
return placeAndFillRows(items, WIDTH_COLUMNS, itemWidth).rows;
|
|
99
|
+
}
|
|
100
|
+
function fitCount(items, maxRows) {
|
|
101
|
+
const rows = placeRows(items);
|
|
102
|
+
let count = 0;
|
|
103
|
+
for (const row of rows.slice(0, Math.max(0, Math.floor(maxRows)))) {
|
|
104
|
+
count += row.keys.length;
|
|
105
|
+
}
|
|
106
|
+
return count;
|
|
107
|
+
}
|
|
108
|
+
function placeAndFill(items, columns, spanOf) {
|
|
109
|
+
return placeAndFillRows(items, columns, spanOf).spans;
|
|
110
|
+
}
|
|
111
|
+
function placeAndFillRows(items, columns, spanOf) {
|
|
112
|
+
const trackCount = Math.max(1, Math.floor(columns));
|
|
113
|
+
const spans = /* @__PURE__ */ new Map();
|
|
114
|
+
const rows = [];
|
|
115
|
+
let row = [];
|
|
116
|
+
let used = 0;
|
|
117
|
+
function closeRow() {
|
|
118
|
+
if (row.length === 0) return;
|
|
119
|
+
const fillers = row.filter(({ item }) => fillsWidth(item.fill));
|
|
120
|
+
const leftover = trackCount - used;
|
|
121
|
+
const finalSpans = /* @__PURE__ */ new Map();
|
|
122
|
+
if (fillers.length > 0) {
|
|
123
|
+
const share = Math.floor(leftover / fillers.length);
|
|
124
|
+
const remainder = leftover % fillers.length;
|
|
125
|
+
fillers.forEach(({ item, span }, index) => {
|
|
126
|
+
const grown = span + share + (index < remainder ? 1 : 0);
|
|
127
|
+
spans.set(item.key, grown);
|
|
128
|
+
finalSpans.set(item.key, grown);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
rows.push({
|
|
132
|
+
keys: row.map(({ item }) => item.key),
|
|
133
|
+
spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
|
|
134
|
+
});
|
|
135
|
+
row = [];
|
|
136
|
+
used = 0;
|
|
137
|
+
}
|
|
138
|
+
for (const item of items) {
|
|
139
|
+
const span = Math.min(spanOf(item, trackCount), trackCount);
|
|
140
|
+
if (used + span > trackCount && row.length > 0) closeRow();
|
|
141
|
+
row.push({ item, span });
|
|
142
|
+
used += span;
|
|
143
|
+
}
|
|
144
|
+
closeRow();
|
|
145
|
+
return { spans, rows };
|
|
146
|
+
}
|
|
147
|
+
|
|
131
148
|
// src/core/format.ts
|
|
132
149
|
var DEFAULT_CURRENCY = "USD";
|
|
133
150
|
function localeArgument(locale) {
|
|
@@ -213,6 +230,48 @@ function kpiDelta(current, previous, options = {}) {
|
|
|
213
230
|
};
|
|
214
231
|
}
|
|
215
232
|
|
|
233
|
+
// src/core/payload.ts
|
|
234
|
+
var WIDGET_KINDS = [
|
|
235
|
+
"TEXT",
|
|
236
|
+
"KPI",
|
|
237
|
+
"GAUGE",
|
|
238
|
+
"TABLE",
|
|
239
|
+
"BAR_LIST",
|
|
240
|
+
"ALERT_LIST",
|
|
241
|
+
"GRAPH"
|
|
242
|
+
];
|
|
243
|
+
function isWidgetKind(value) {
|
|
244
|
+
return typeof value === "string" && WIDGET_KINDS.includes(value);
|
|
245
|
+
}
|
|
246
|
+
var VALUE_FORMATS = ["number", "currency", "percent"];
|
|
247
|
+
var MAX_GRAPH_SERIES = 8;
|
|
248
|
+
var DEFAULT_EMPTY_TEXT = "Nothing to show yet.";
|
|
249
|
+
function emptyWidget(text = DEFAULT_EMPTY_TEXT) {
|
|
250
|
+
return { empty: true, text };
|
|
251
|
+
}
|
|
252
|
+
function isWidgetEmptyState(payload) {
|
|
253
|
+
return "empty" in payload && payload.empty === true;
|
|
254
|
+
}
|
|
255
|
+
function emptyTextFor(payload) {
|
|
256
|
+
if (isWidgetEmptyState(payload)) {
|
|
257
|
+
return payload.text;
|
|
258
|
+
}
|
|
259
|
+
switch (payload.kind) {
|
|
260
|
+
case "TABLE":
|
|
261
|
+
return payload.rows.length === 0 ? DEFAULT_EMPTY_TEXT : null;
|
|
262
|
+
case "BAR_LIST":
|
|
263
|
+
return payload.items.length === 0 ? DEFAULT_EMPTY_TEXT : null;
|
|
264
|
+
case "ALERT_LIST":
|
|
265
|
+
return payload.items.length === 0 ? payload.emptyText : null;
|
|
266
|
+
case "GRAPH":
|
|
267
|
+
return payload.series.every((series) => series.points.length === 0) ? DEFAULT_EMPTY_TEXT : null;
|
|
268
|
+
case "TEXT":
|
|
269
|
+
case "KPI":
|
|
270
|
+
case "GAUGE":
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
216
275
|
// src/core/result.ts
|
|
217
276
|
function ok(value) {
|
|
218
277
|
return { ok: true, value };
|
|
@@ -235,41 +294,298 @@ function describeUnknownError(cause) {
|
|
|
235
294
|
}
|
|
236
295
|
}
|
|
237
296
|
|
|
238
|
-
// src/core/
|
|
239
|
-
var
|
|
240
|
-
var
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
297
|
+
// src/core/views.ts
|
|
298
|
+
var BASE_VIEW_KEY = "base";
|
|
299
|
+
var MAX_WIDGET_VIEWS = 8;
|
|
300
|
+
var VIEW_CONVERSIONS = {
|
|
301
|
+
tableToBarList: { from: "TABLE", to: "BAR_LIST" },
|
|
302
|
+
tableToGraph: { from: "TABLE", to: "GRAPH" },
|
|
303
|
+
barListToTable: { from: "BAR_LIST", to: "TABLE" },
|
|
304
|
+
graphToTable: { from: "GRAPH", to: "TABLE" },
|
|
305
|
+
barListToGraph: { from: "BAR_LIST", to: "GRAPH" }
|
|
306
|
+
};
|
|
307
|
+
var KIND_LABELS = {
|
|
308
|
+
TEXT: "Text",
|
|
309
|
+
KPI: "Number",
|
|
310
|
+
GAUGE: "Gauge",
|
|
311
|
+
TABLE: "Table",
|
|
312
|
+
BAR_LIST: "Bars",
|
|
313
|
+
ALERT_LIST: "List",
|
|
314
|
+
GRAPH: "Chart"
|
|
315
|
+
};
|
|
316
|
+
function kindLabel(kind) {
|
|
317
|
+
return KIND_LABELS[kind];
|
|
246
318
|
}
|
|
247
|
-
function
|
|
248
|
-
|
|
249
|
-
return false;
|
|
250
|
-
}
|
|
251
|
-
const scheme = schemeOf(url);
|
|
252
|
-
return scheme === null || scheme === "http" || scheme === "https" || scheme === "mailto";
|
|
319
|
+
function hasViews(item) {
|
|
320
|
+
return item.views !== void 0 && item.views.length > 0;
|
|
253
321
|
}
|
|
254
|
-
function
|
|
255
|
-
|
|
256
|
-
return false;
|
|
257
|
-
}
|
|
258
|
-
const scheme = schemeOf(url);
|
|
259
|
-
return scheme === null || scheme === "http" || scheme === "https";
|
|
322
|
+
function findView(item, key) {
|
|
323
|
+
return item.views?.find((view) => view.key === key);
|
|
260
324
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
325
|
+
function viewChoices(item) {
|
|
326
|
+
if (!hasViews(item)) return [];
|
|
327
|
+
return [
|
|
328
|
+
{
|
|
329
|
+
key: BASE_VIEW_KEY,
|
|
330
|
+
label: item.baseViewLabel ?? kindLabel(item.kind),
|
|
331
|
+
kind: item.kind
|
|
332
|
+
},
|
|
333
|
+
...(item.views ?? []).map(({ key, label, kind }) => ({
|
|
334
|
+
key,
|
|
335
|
+
label,
|
|
336
|
+
kind
|
|
337
|
+
}))
|
|
338
|
+
];
|
|
339
|
+
}
|
|
340
|
+
function isKnownView(item, key) {
|
|
341
|
+
return key !== void 0 && (key === BASE_VIEW_KEY || findView(item, key) !== void 0);
|
|
342
|
+
}
|
|
343
|
+
function effectiveViewKey(item, chosen) {
|
|
344
|
+
if (!hasViews(item)) return BASE_VIEW_KEY;
|
|
345
|
+
if (isKnownView(item, chosen)) return chosen;
|
|
346
|
+
if (isKnownView(item, item.defaultView)) return item.defaultView;
|
|
347
|
+
return BASE_VIEW_KEY;
|
|
348
|
+
}
|
|
349
|
+
function viewKind(item, chosen) {
|
|
350
|
+
return findView(item, effectiveViewKey(item, chosen))?.kind ?? item.kind;
|
|
351
|
+
}
|
|
352
|
+
function cellValue(cell) {
|
|
353
|
+
const value = cell?.value;
|
|
354
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
355
|
+
}
|
|
356
|
+
function columnName(table, index) {
|
|
357
|
+
return table.columns[index]?.label ?? `#${index + 1}`;
|
|
358
|
+
}
|
|
359
|
+
function checkColumn(table, index, field, context) {
|
|
360
|
+
if (!Number.isInteger(index) || index < 0 || index >= table.columns.length) {
|
|
361
|
+
return `${context.where}: ${field} column ${index} does not exist (the table has ${table.columns.length} columns, numbered from 0).`;
|
|
362
|
+
}
|
|
363
|
+
return void 0;
|
|
364
|
+
}
|
|
365
|
+
function numericColumn(table, index, context) {
|
|
366
|
+
const numbers = [];
|
|
367
|
+
for (const [rowIndex, row] of table.rows.entries()) {
|
|
368
|
+
const value = cellValue(row[index]);
|
|
369
|
+
if (value === void 0) {
|
|
370
|
+
return err(
|
|
371
|
+
`${context.where}: row ${rowIndex + 1}, column ${index + 1} ("${columnName(table, index)}") has no numeric value. Add value to the cells.`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
numbers.push(value);
|
|
375
|
+
}
|
|
376
|
+
return ok(numbers);
|
|
377
|
+
}
|
|
378
|
+
function tableToBarList(table, conversion, context) {
|
|
379
|
+
const bad = checkColumn(table, conversion.label, "label", context) ?? checkColumn(table, conversion.value, "value", context);
|
|
380
|
+
if (bad !== void 0) return err(bad);
|
|
381
|
+
const numbers = numericColumn(table, conversion.value, context);
|
|
382
|
+
if (!numbers.ok) return numbers;
|
|
383
|
+
const items = table.rows.map((row, index) => ({
|
|
384
|
+
label: row[conversion.label]?.text ?? "",
|
|
385
|
+
value: numbers.value[index],
|
|
386
|
+
display: row[conversion.value]?.text ?? String(numbers.value[index])
|
|
387
|
+
}));
|
|
388
|
+
return ok({ kind: "BAR_LIST", items });
|
|
389
|
+
}
|
|
390
|
+
function tableToGraph(table, conversion, context) {
|
|
391
|
+
if (conversion.values.length === 0 || conversion.values.length > MAX_GRAPH_SERIES) {
|
|
392
|
+
return err(
|
|
393
|
+
`${context.where}: a chart needs 1 to ${MAX_GRAPH_SERIES} series columns, got ${conversion.values.length}.`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
let bad = checkColumn(
|
|
397
|
+
table,
|
|
398
|
+
conversion.label,
|
|
399
|
+
"label",
|
|
400
|
+
context
|
|
401
|
+
);
|
|
402
|
+
for (const index of conversion.values) {
|
|
403
|
+
bad ??= checkColumn(table, index, "series", context);
|
|
404
|
+
}
|
|
405
|
+
if (bad !== void 0) return err(bad);
|
|
406
|
+
const series = [];
|
|
407
|
+
for (const index of conversion.values) {
|
|
408
|
+
const numbers = numericColumn(table, index, context);
|
|
409
|
+
if (!numbers.ok) return numbers;
|
|
410
|
+
series.push({
|
|
411
|
+
name: columnName(table, index),
|
|
412
|
+
points: table.rows.map((row, rowIndex) => ({
|
|
413
|
+
label: row[conversion.label]?.text ?? "",
|
|
414
|
+
value: numbers.value[rowIndex]
|
|
415
|
+
}))
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
return ok({
|
|
419
|
+
kind: "GRAPH",
|
|
420
|
+
series,
|
|
421
|
+
chartType: conversion.chartType,
|
|
422
|
+
valueFormat: conversion.valueFormat ?? "number",
|
|
423
|
+
...conversion.currency === void 0 ? {} : { currency: conversion.currency },
|
|
424
|
+
xLabel: columnName(table, conversion.label)
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function barListToTable(list, conversion) {
|
|
428
|
+
return ok({
|
|
429
|
+
kind: "TABLE",
|
|
430
|
+
columns: [
|
|
431
|
+
{ label: conversion.labelHeading ?? "Name" },
|
|
432
|
+
{ label: conversion.valueHeading ?? "Value", numeric: true }
|
|
433
|
+
],
|
|
434
|
+
rows: list.items.map((item) => [
|
|
435
|
+
{ text: item.label },
|
|
436
|
+
{ text: item.display ?? String(item.value), value: item.value }
|
|
437
|
+
])
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function graphToTable(graph, context) {
|
|
441
|
+
const first = graph.series[0];
|
|
442
|
+
if (first === void 0)
|
|
443
|
+
return err(`${context.where}: the chart has no series.`);
|
|
444
|
+
const format = {
|
|
445
|
+
...graph.currency === void 0 ? {} : { currency: graph.currency },
|
|
446
|
+
...context.locale === void 0 ? {} : { locale: context.locale }
|
|
447
|
+
};
|
|
448
|
+
return ok({
|
|
449
|
+
kind: "TABLE",
|
|
450
|
+
columns: [
|
|
451
|
+
{ label: graph.xLabel ?? "Label" },
|
|
452
|
+
...graph.series.map((series) => ({ label: series.name, numeric: true }))
|
|
453
|
+
],
|
|
454
|
+
rows: first.points.map((point, index) => [
|
|
455
|
+
{ text: point.label },
|
|
456
|
+
...graph.series.map((series) => {
|
|
457
|
+
const value = series.points[index]?.value;
|
|
458
|
+
return value === void 0 ? { text: "\u2014" } : { text: formatValue(value, graph.valueFormat, format), value };
|
|
459
|
+
})
|
|
460
|
+
])
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
function barListToGraph(list, conversion) {
|
|
464
|
+
return ok({
|
|
465
|
+
kind: "GRAPH",
|
|
466
|
+
series: [
|
|
467
|
+
{
|
|
468
|
+
name: conversion.seriesName ?? "Value",
|
|
469
|
+
points: list.items.map((item) => ({
|
|
470
|
+
label: item.label,
|
|
471
|
+
value: item.value
|
|
472
|
+
}))
|
|
473
|
+
}
|
|
474
|
+
],
|
|
475
|
+
chartType: conversion.chartType,
|
|
476
|
+
valueFormat: conversion.valueFormat ?? "number",
|
|
477
|
+
...conversion.currency === void 0 ? {} : { currency: conversion.currency }
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
function convertView(widgetKey, view, data, options = {}) {
|
|
481
|
+
const conversion = view.convert;
|
|
482
|
+
const context = {
|
|
483
|
+
where: `Widget ${JSON.stringify(widgetKey)} view ${JSON.stringify(view.key)}`,
|
|
484
|
+
...options.locale === void 0 ? {} : { locale: options.locale }
|
|
485
|
+
};
|
|
486
|
+
const expected = VIEW_CONVERSIONS[conversion.type]?.from;
|
|
487
|
+
if (expected === void 0) {
|
|
488
|
+
return err(
|
|
489
|
+
`${context.where}: unknown conversion type ${JSON.stringify(conversion.type)}.`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
if (data.kind !== expected) {
|
|
493
|
+
return err(
|
|
494
|
+
`${context.where}: ${conversion.type} reads a ${expected} payload, but the widget returned ${data.kind}.`
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
switch (conversion.type) {
|
|
498
|
+
case "tableToBarList":
|
|
499
|
+
return tableToBarList(data, conversion, context);
|
|
500
|
+
case "tableToGraph":
|
|
501
|
+
return tableToGraph(data, conversion, context);
|
|
502
|
+
case "barListToTable":
|
|
503
|
+
return barListToTable(data, conversion);
|
|
504
|
+
case "graphToTable":
|
|
505
|
+
return graphToTable(data, context);
|
|
506
|
+
case "barListToGraph":
|
|
507
|
+
return barListToGraph(data, conversion);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function viewNote(view, data) {
|
|
511
|
+
if (data?.kind !== "TABLE") return "";
|
|
512
|
+
const conversion = view.convert;
|
|
513
|
+
let indexes;
|
|
514
|
+
if (conversion.type === "tableToBarList") {
|
|
515
|
+
indexes = [conversion.label, conversion.value];
|
|
516
|
+
} else if (conversion.type === "tableToGraph") {
|
|
517
|
+
indexes = [conversion.label, ...conversion.values];
|
|
518
|
+
} else {
|
|
519
|
+
return "";
|
|
520
|
+
}
|
|
521
|
+
const names = indexes.map(
|
|
522
|
+
(index) => data.columns[index]?.label ?? `#${index + 1}`
|
|
523
|
+
);
|
|
524
|
+
return `Shows ${names.join(", ")} only.`;
|
|
525
|
+
}
|
|
526
|
+
function applyView(widget, chosen, options = {}) {
|
|
527
|
+
const key = effectiveViewKey(widget.definition, chosen);
|
|
528
|
+
const view = findView(widget.definition, key);
|
|
529
|
+
if (view === void 0) return widget;
|
|
530
|
+
const definition = {
|
|
531
|
+
...widget.definition,
|
|
532
|
+
kind: view.kind
|
|
533
|
+
};
|
|
534
|
+
if (widget.status === "loading") return { ...widget, definition };
|
|
535
|
+
if (widget.status === "empty") return { ...widget, definition };
|
|
536
|
+
if (widget.status === "error") return { ...widget, definition };
|
|
537
|
+
const converted = convertView(
|
|
538
|
+
widget.definition.key,
|
|
539
|
+
view,
|
|
540
|
+
widget.data,
|
|
541
|
+
options
|
|
542
|
+
);
|
|
543
|
+
if (!converted.ok) {
|
|
544
|
+
return {
|
|
545
|
+
definition,
|
|
546
|
+
status: "error",
|
|
547
|
+
error: converted.error,
|
|
548
|
+
...widget.options === void 0 ? {} : { options: widget.options }
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
return { ...widget, definition, data: converted.value };
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/core/url.ts
|
|
555
|
+
var SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):/i;
|
|
556
|
+
var IGNORED_IN_SCHEME = /[\u0000- \u007f]/g;
|
|
557
|
+
function schemeOf(url) {
|
|
558
|
+
const match = SCHEME_PATTERN.exec(
|
|
559
|
+
url.replace(IGNORED_IN_SCHEME, "")
|
|
560
|
+
);
|
|
561
|
+
return match === null ? null : (match[1] ?? "").toLowerCase();
|
|
562
|
+
}
|
|
563
|
+
function isSafeHref(url) {
|
|
564
|
+
if (url.trim() === "") {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
const scheme = schemeOf(url);
|
|
568
|
+
return scheme === null || scheme === "http" || scheme === "https" || scheme === "mailto";
|
|
569
|
+
}
|
|
570
|
+
function isSafeImageUrl(url) {
|
|
571
|
+
if (url.trim() === "") {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
const scheme = schemeOf(url);
|
|
575
|
+
return scheme === null || scheme === "http" || scheme === "https";
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/core/detail.ts
|
|
579
|
+
var DETAIL_MODES = ["client", "server"];
|
|
580
|
+
var DEFAULT_DETAIL_PAGE_SIZE = 25;
|
|
581
|
+
var MAX_DETAIL_PAGE_SIZE = 200;
|
|
582
|
+
function resolveDetailOptions(definition) {
|
|
583
|
+
const detail = definition.detail;
|
|
584
|
+
if (detail === void 0 || detail === false) return void 0;
|
|
585
|
+
const options = detail === true ? {} : detail;
|
|
586
|
+
return {
|
|
587
|
+
title: options.title ?? definition.title,
|
|
588
|
+
pageSize: options.pageSize ?? DEFAULT_DETAIL_PAGE_SIZE,
|
|
273
589
|
mode: options.mode ?? "client"
|
|
274
590
|
};
|
|
275
591
|
}
|
|
@@ -289,15 +605,15 @@ function defaultDetailQuery(pageSize = DEFAULT_DETAIL_PAGE_SIZE) {
|
|
|
289
605
|
function fold(text) {
|
|
290
606
|
return text.normalize("NFD").replace(/\p{M}/gu, "").toLowerCase();
|
|
291
607
|
}
|
|
292
|
-
function
|
|
608
|
+
function cellValue2(cell) {
|
|
293
609
|
return cell.value ?? cell.text;
|
|
294
610
|
}
|
|
295
611
|
function cellMatches(cell, needle) {
|
|
296
|
-
return fold(String(
|
|
612
|
+
return fold(String(cellValue2(cell))).includes(needle);
|
|
297
613
|
}
|
|
298
614
|
function compareCells(a, b, collator) {
|
|
299
|
-
const left =
|
|
300
|
-
const right =
|
|
615
|
+
const left = cellValue2(a);
|
|
616
|
+
const right = cellValue2(b);
|
|
301
617
|
if (typeof left === "number" && typeof right === "number") {
|
|
302
618
|
return left - right;
|
|
303
619
|
}
|
|
@@ -589,103 +905,6 @@ function deriveDetailData(data, locale) {
|
|
|
589
905
|
return void 0;
|
|
590
906
|
}
|
|
591
907
|
|
|
592
|
-
// src/core/grid.ts
|
|
593
|
-
var WIDTH_COLUMNS = 12;
|
|
594
|
-
var MIN_WIDGET_WIDTH = 2;
|
|
595
|
-
var MAX_WIDGET_WIDTH = 12;
|
|
596
|
-
function isWidgetWidth(value) {
|
|
597
|
-
return typeof value === "number" && Number.isInteger(value) && value >= MIN_WIDGET_WIDTH && value <= MAX_WIDGET_WIDTH;
|
|
598
|
-
}
|
|
599
|
-
function widthForSize(size) {
|
|
600
|
-
switch (size) {
|
|
601
|
-
case "small":
|
|
602
|
-
return 3;
|
|
603
|
-
case "large":
|
|
604
|
-
return 6;
|
|
605
|
-
case "full":
|
|
606
|
-
return 12;
|
|
607
|
-
default:
|
|
608
|
-
return 4;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
function itemWidth(item) {
|
|
612
|
-
return isWidgetWidth(item.width) ? item.width : widthForSize(item.defaultSize);
|
|
613
|
-
}
|
|
614
|
-
function usesWidthColumns(items) {
|
|
615
|
-
return items.some((item) => item.width !== void 0);
|
|
616
|
-
}
|
|
617
|
-
function fillsWidth(fill) {
|
|
618
|
-
return fill === "width" || fill === "both";
|
|
619
|
-
}
|
|
620
|
-
function fillsHeight(fill) {
|
|
621
|
-
return fill === "height" || fill === "both";
|
|
622
|
-
}
|
|
623
|
-
function baseColumnSpan(size, columns, largeSpan = 2) {
|
|
624
|
-
if (size === "full") return columns;
|
|
625
|
-
if (size === "large") return Math.min(largeSpan, columns);
|
|
626
|
-
return 1;
|
|
627
|
-
}
|
|
628
|
-
function fillColumnSpans(items, columns, largeSpan = 2) {
|
|
629
|
-
return placeAndFill(
|
|
630
|
-
items,
|
|
631
|
-
columns,
|
|
632
|
-
(item, trackCount) => baseColumnSpan(item.defaultSize, trackCount, largeSpan)
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
|
-
function fillWidthSpans(items) {
|
|
636
|
-
return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
|
|
637
|
-
}
|
|
638
|
-
function placeRows(items) {
|
|
639
|
-
return placeAndFillRows(items, WIDTH_COLUMNS, itemWidth).rows;
|
|
640
|
-
}
|
|
641
|
-
function fitCount(items, maxRows) {
|
|
642
|
-
const rows = placeRows(items);
|
|
643
|
-
let count = 0;
|
|
644
|
-
for (const row of rows.slice(0, Math.max(0, Math.floor(maxRows)))) {
|
|
645
|
-
count += row.keys.length;
|
|
646
|
-
}
|
|
647
|
-
return count;
|
|
648
|
-
}
|
|
649
|
-
function placeAndFill(items, columns, spanOf) {
|
|
650
|
-
return placeAndFillRows(items, columns, spanOf).spans;
|
|
651
|
-
}
|
|
652
|
-
function placeAndFillRows(items, columns, spanOf) {
|
|
653
|
-
const trackCount = Math.max(1, Math.floor(columns));
|
|
654
|
-
const spans = /* @__PURE__ */ new Map();
|
|
655
|
-
const rows = [];
|
|
656
|
-
let row = [];
|
|
657
|
-
let used = 0;
|
|
658
|
-
function closeRow() {
|
|
659
|
-
if (row.length === 0) return;
|
|
660
|
-
const fillers = row.filter(({ item }) => fillsWidth(item.fill));
|
|
661
|
-
const leftover = trackCount - used;
|
|
662
|
-
const finalSpans = /* @__PURE__ */ new Map();
|
|
663
|
-
if (fillers.length > 0) {
|
|
664
|
-
const share = Math.floor(leftover / fillers.length);
|
|
665
|
-
const remainder = leftover % fillers.length;
|
|
666
|
-
fillers.forEach(({ item, span }, index) => {
|
|
667
|
-
const grown = span + share + (index < remainder ? 1 : 0);
|
|
668
|
-
spans.set(item.key, grown);
|
|
669
|
-
finalSpans.set(item.key, grown);
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
rows.push({
|
|
673
|
-
keys: row.map(({ item }) => item.key),
|
|
674
|
-
spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
|
|
675
|
-
});
|
|
676
|
-
row = [];
|
|
677
|
-
used = 0;
|
|
678
|
-
}
|
|
679
|
-
for (const item of items) {
|
|
680
|
-
const span = Math.min(spanOf(item, trackCount), trackCount);
|
|
681
|
-
if (used + span > trackCount && row.length > 0) closeRow();
|
|
682
|
-
row.push({ item, span });
|
|
683
|
-
used += span;
|
|
684
|
-
}
|
|
685
|
-
closeRow();
|
|
686
|
-
return { spans, rows };
|
|
687
|
-
}
|
|
688
|
-
|
|
689
908
|
// src/core/options.ts
|
|
690
909
|
var DATE_RANGE_PRESETS = [
|
|
691
910
|
"today",
|
|
@@ -991,6 +1210,7 @@ function parseOne(raw) {
|
|
|
991
1210
|
const width = raw["width"];
|
|
992
1211
|
const flexible = raw["flexible"];
|
|
993
1212
|
const options = raw["options"];
|
|
1213
|
+
const view = raw["view"];
|
|
994
1214
|
const cleanTitle = typeof title === "string" ? title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH) : "";
|
|
995
1215
|
const chosen = {};
|
|
996
1216
|
if (isRecord(options)) {
|
|
@@ -1003,7 +1223,8 @@ function parseOne(raw) {
|
|
|
1003
1223
|
...cleanTitle === "" ? {} : { title: cleanTitle },
|
|
1004
1224
|
...isWidgetWidth(width) ? { width } : {},
|
|
1005
1225
|
...typeof flexible === "boolean" ? { flexible } : {},
|
|
1006
|
-
...Object.keys(chosen).length === 0 ? {} : { options: chosen }
|
|
1226
|
+
...Object.keys(chosen).length === 0 ? {} : { options: chosen },
|
|
1227
|
+
...typeof view === "string" && view !== "" && view.length <= 80 ? { view } : {}
|
|
1007
1228
|
};
|
|
1008
1229
|
return Object.keys(settings).length === 0 ? void 0 : settings;
|
|
1009
1230
|
}
|
|
@@ -1030,7 +1251,8 @@ function serializeSettings(settings) {
|
|
|
1030
1251
|
...one.title === void 0 ? {} : { title: one.title },
|
|
1031
1252
|
...one.width === void 0 ? {} : { width: one.width },
|
|
1032
1253
|
...one.flexible === void 0 ? {} : { flexible: one.flexible },
|
|
1033
|
-
...one.options === void 0 || Object.keys(one.options).length === 0 ? {} : { options: { ...one.options } }
|
|
1254
|
+
...one.options === void 0 || Object.keys(one.options).length === 0 ? {} : { options: { ...one.options } },
|
|
1255
|
+
...one.view === void 0 ? {} : { view: one.view }
|
|
1034
1256
|
};
|
|
1035
1257
|
if (Object.keys(piece).length > 0) out[key] = piece;
|
|
1036
1258
|
}
|
|
@@ -1122,12 +1344,18 @@ function optionValuesFromLayout(definitions, layout, options = {}) {
|
|
|
1122
1344
|
}
|
|
1123
1345
|
function sameSettings(a, b) {
|
|
1124
1346
|
if (a === b) return true;
|
|
1125
|
-
return a?.title === b?.title && a?.width === b?.width && a?.flexible === b?.flexible && sameOptionValues(a?.options ?? {}, b?.options ?? {});
|
|
1347
|
+
return a?.title === b?.title && a?.width === b?.width && a?.flexible === b?.flexible && a?.view === b?.view && sameOptionValues(a?.options ?? {}, b?.options ?? {});
|
|
1126
1348
|
}
|
|
1127
1349
|
function setWidgetSettings(layout, key, patch) {
|
|
1128
1350
|
const before = layout.settings?.[key];
|
|
1129
1351
|
const draft = { ...before };
|
|
1130
|
-
for (const field of [
|
|
1352
|
+
for (const field of [
|
|
1353
|
+
"title",
|
|
1354
|
+
"width",
|
|
1355
|
+
"flexible",
|
|
1356
|
+
"options",
|
|
1357
|
+
"view"
|
|
1358
|
+
]) {
|
|
1131
1359
|
if (!(field in patch)) continue;
|
|
1132
1360
|
const value = patch[field];
|
|
1133
1361
|
if (value === void 0) {
|
|
@@ -1153,7 +1381,8 @@ function clearWidgetSettings(layout, key) {
|
|
|
1153
1381
|
title: void 0,
|
|
1154
1382
|
width: void 0,
|
|
1155
1383
|
flexible: void 0,
|
|
1156
|
-
options: void 0
|
|
1384
|
+
options: void 0,
|
|
1385
|
+
view: void 0
|
|
1157
1386
|
});
|
|
1158
1387
|
}
|
|
1159
1388
|
function normalizeTitle(title) {
|
|
@@ -1213,7 +1442,8 @@ function normalizeSettings(definitions, layout, options = {}) {
|
|
|
1213
1442
|
...title === void 0 ? {} : { title },
|
|
1214
1443
|
...one.width === void 0 ? {} : { width: Math.min(range.max, Math.max(range.min, one.width)) },
|
|
1215
1444
|
...one.flexible === void 0 ? {} : { flexible: one.flexible },
|
|
1216
|
-
...Object.keys(chosen).length === 0 ? {} : { options: chosen }
|
|
1445
|
+
...Object.keys(chosen).length === 0 ? {} : { options: chosen },
|
|
1446
|
+
...one.view !== void 0 && (one.view === BASE_VIEW_KEY || findView(definition, one.view) !== void 0) ? { view: one.view } : {}
|
|
1217
1447
|
};
|
|
1218
1448
|
if (Object.keys(next).length > 0) {
|
|
1219
1449
|
cleaned[definition.key] = sameSettings(one, next) ? one : next;
|
|
@@ -1226,57 +1456,184 @@ function normalizeSettings(definitions, layout, options = {}) {
|
|
|
1226
1456
|
return keys.length === 0 ? others : { ...others, settings: cleaned };
|
|
1227
1457
|
}
|
|
1228
1458
|
|
|
1229
|
-
// src/core/
|
|
1230
|
-
var
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
minimized: Object.freeze([])
|
|
1237
|
-
});
|
|
1238
|
-
function uniqueStrings(value) {
|
|
1239
|
-
if (!Array.isArray(value)) return [];
|
|
1459
|
+
// src/core/clones.ts
|
|
1460
|
+
var MAX_PARSED_CLONES = 500;
|
|
1461
|
+
function isRecord2(value) {
|
|
1462
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1463
|
+
}
|
|
1464
|
+
function parseClones(raw) {
|
|
1465
|
+
if (!Array.isArray(raw)) return void 0;
|
|
1240
1466
|
const seen = /* @__PURE__ */ new Set();
|
|
1241
|
-
|
|
1242
|
-
|
|
1467
|
+
const clones = [];
|
|
1468
|
+
for (const entry of raw.slice(0, MAX_PARSED_CLONES)) {
|
|
1469
|
+
if (!isRecord2(entry)) continue;
|
|
1470
|
+
const key = entry["key"];
|
|
1471
|
+
const from = entry["from"];
|
|
1472
|
+
if (typeof key !== "string" || typeof from !== "string" || key === "" || from === "" || key === from || seen.has(key)) {
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
seen.add(key);
|
|
1476
|
+
clones.push({ key, from });
|
|
1243
1477
|
}
|
|
1244
|
-
return
|
|
1478
|
+
return clones.length === 0 ? void 0 : clones;
|
|
1245
1479
|
}
|
|
1246
|
-
function
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1480
|
+
function serializeClones(clones) {
|
|
1481
|
+
if (clones === void 0 || clones.length === 0) return void 0;
|
|
1482
|
+
return clones.map(({ key, from }) => ({ key, from }));
|
|
1483
|
+
}
|
|
1484
|
+
function cloneNumber(key) {
|
|
1485
|
+
const match = /#(\d+)$/.exec(key);
|
|
1486
|
+
return match === null ? void 0 : Number(match[1]);
|
|
1487
|
+
}
|
|
1488
|
+
function cleanClones(definitions, clones, options = {}) {
|
|
1489
|
+
if (clones === void 0) return void 0;
|
|
1490
|
+
const byKey = new Map(definitions.map((d) => [d.key, d]));
|
|
1491
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1492
|
+
const kept = [];
|
|
1493
|
+
for (const clone of clones) {
|
|
1494
|
+
if (seen.has(clone.key)) continue;
|
|
1495
|
+
const original = byKey.get(clone.from);
|
|
1496
|
+
if (original === void 0 || original.sourceKey !== void 0) continue;
|
|
1497
|
+
const existing = byKey.get(clone.key);
|
|
1498
|
+
if (existing !== void 0 && existing.sourceKey !== clone.from) continue;
|
|
1499
|
+
if (options.overrideLocks !== true && resolveWidgetLock(original).clone) {
|
|
1500
|
+
continue;
|
|
1253
1501
|
}
|
|
1502
|
+
seen.add(clone.key);
|
|
1503
|
+
kept.push(clone);
|
|
1504
|
+
}
|
|
1505
|
+
if (kept.length === 0) return void 0;
|
|
1506
|
+
return kept.length === clones.length && kept.every((c, i) => c === clones[i]) ? clones : kept;
|
|
1507
|
+
}
|
|
1508
|
+
function defaultCloneTitle(original, clone, index) {
|
|
1509
|
+
const base = original.title ?? original.key;
|
|
1510
|
+
const suffix = ` (#${cloneNumber(clone.key) ?? index + 1})`;
|
|
1511
|
+
return `${base.slice(0, MAX_WIDGET_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
1512
|
+
}
|
|
1513
|
+
function withClones(definitions, layout, options = {}) {
|
|
1514
|
+
const valid = cleanClones(
|
|
1515
|
+
definitions,
|
|
1516
|
+
layout.clones,
|
|
1517
|
+
options
|
|
1518
|
+
);
|
|
1519
|
+
if (valid === void 0) return definitions;
|
|
1520
|
+
const present = new Set(definitions.map((d) => d.key));
|
|
1521
|
+
const toAdd = valid.filter((c) => !present.has(c.key));
|
|
1522
|
+
if (toAdd.length === 0) return definitions;
|
|
1523
|
+
const out = [];
|
|
1524
|
+
for (const definition of definitions) {
|
|
1525
|
+
out.push(definition);
|
|
1526
|
+
toAdd.forEach((clone, index) => {
|
|
1527
|
+
if (clone.from !== definition.key) return;
|
|
1528
|
+
const copy = { ...definition };
|
|
1529
|
+
copy.key = clone.key;
|
|
1530
|
+
copy.sourceKey = definition.key;
|
|
1531
|
+
copy.title = defaultCloneTitle(definition, clone, index);
|
|
1532
|
+
delete copy.locked;
|
|
1533
|
+
out.push(copy);
|
|
1534
|
+
});
|
|
1254
1535
|
}
|
|
1536
|
+
return out;
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/core/compat.ts
|
|
1540
|
+
function isStringArray(value) {
|
|
1541
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
1542
|
+
}
|
|
1543
|
+
function upgradeLegacyWidgetData(value) {
|
|
1255
1544
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1256
|
-
return
|
|
1545
|
+
return value;
|
|
1257
1546
|
}
|
|
1258
1547
|
const record = value;
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
if (
|
|
1548
|
+
if (record["kind"] === "TABLE" && isStringArray(record["columns"]) && Array.isArray(record["rows"]) && record["rows"].every(isStringArray)) {
|
|
1549
|
+
const rows = record["rows"];
|
|
1550
|
+
return {
|
|
1551
|
+
...record,
|
|
1552
|
+
columns: record["columns"].map((label) => ({ label })),
|
|
1553
|
+
rows: rows.map((row) => row.map((text) => ({ text })))
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
if (record["kind"] === "GRAPH" && Array.isArray(record["points"]) && record["series"] === void 0) {
|
|
1557
|
+
const { points, ...rest } = record;
|
|
1558
|
+
return {
|
|
1559
|
+
...rest,
|
|
1560
|
+
series: [{ name: "Value", points }],
|
|
1561
|
+
chartType: "bar",
|
|
1562
|
+
valueFormat: "number"
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
return value;
|
|
1566
|
+
}
|
|
1567
|
+
function fromLegacyDefinition(legacy) {
|
|
1568
|
+
if (!isWidgetKind(legacy.viewType)) {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
return {
|
|
1572
|
+
key: legacy.widgetKey,
|
|
1573
|
+
title: legacy.title,
|
|
1574
|
+
...legacy.description === null ? {} : { description: legacy.description },
|
|
1575
|
+
kind: legacy.viewType,
|
|
1576
|
+
sortOrder: legacy.sortOrder
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// src/core/layout.ts
|
|
1581
|
+
var MAX_PAGE_TITLE_LENGTH = 30;
|
|
1582
|
+
var MAX_PARSED_PAGES = 50;
|
|
1583
|
+
var MAX_PAGE_ROWS = 20;
|
|
1584
|
+
var EMPTY_LAYOUT = Object.freeze({
|
|
1585
|
+
order: Object.freeze([]),
|
|
1586
|
+
hidden: Object.freeze([]),
|
|
1587
|
+
minimized: Object.freeze([])
|
|
1588
|
+
});
|
|
1589
|
+
function uniqueStrings(value) {
|
|
1590
|
+
if (!Array.isArray(value)) return [];
|
|
1591
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1592
|
+
for (const entry of value) {
|
|
1593
|
+
if (typeof entry === "string" && entry !== "") seen.add(entry);
|
|
1594
|
+
}
|
|
1595
|
+
return [...seen];
|
|
1596
|
+
}
|
|
1597
|
+
function parseLayout(raw) {
|
|
1598
|
+
let value = raw;
|
|
1599
|
+
if (typeof raw === "string") {
|
|
1600
|
+
try {
|
|
1601
|
+
value = JSON.parse(raw);
|
|
1602
|
+
} catch {
|
|
1603
|
+
return EMPTY_LAYOUT;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1607
|
+
return EMPTY_LAYOUT;
|
|
1608
|
+
}
|
|
1609
|
+
const record = value;
|
|
1610
|
+
const order = uniqueStrings(record["order"]);
|
|
1611
|
+
const hidden = uniqueStrings(record["hidden"]);
|
|
1612
|
+
const minimized = uniqueStrings(record["minimized"]);
|
|
1613
|
+
const pages = parsePages(record["pages"]);
|
|
1614
|
+
const settings = parseSettings(
|
|
1615
|
+
record["settings"]
|
|
1616
|
+
);
|
|
1617
|
+
const clones = parseClones(
|
|
1618
|
+
record["clones"]
|
|
1619
|
+
);
|
|
1620
|
+
const extras = {
|
|
1621
|
+
...settings === void 0 ? {} : { settings },
|
|
1622
|
+
...clones === void 0 ? {} : { clones }
|
|
1623
|
+
};
|
|
1624
|
+
if (pages.length > 0) {
|
|
1268
1625
|
return {
|
|
1269
1626
|
order: EMPTY_LAYOUT.order,
|
|
1270
1627
|
hidden: EMPTY_LAYOUT.hidden,
|
|
1271
1628
|
minimized: EMPTY_LAYOUT.minimized,
|
|
1272
1629
|
pages,
|
|
1273
|
-
...
|
|
1630
|
+
...extras
|
|
1274
1631
|
};
|
|
1275
1632
|
}
|
|
1276
1633
|
if (order.length === 0 && hidden.length === 0 && minimized.length === 0) {
|
|
1277
|
-
return
|
|
1634
|
+
return Object.keys(extras).length === 0 ? EMPTY_LAYOUT : { ...EMPTY_LAYOUT, ...extras };
|
|
1278
1635
|
}
|
|
1279
|
-
return { order, hidden, minimized, ...
|
|
1636
|
+
return { order, hidden, minimized, ...extras };
|
|
1280
1637
|
}
|
|
1281
1638
|
function parsePages(value) {
|
|
1282
1639
|
if (!Array.isArray(value)) return [];
|
|
@@ -1324,7 +1681,8 @@ function serializeLayout(layout) {
|
|
|
1324
1681
|
...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
|
|
1325
1682
|
}))
|
|
1326
1683
|
},
|
|
1327
|
-
...serializeSettings(layout.settings) === void 0 ? {} : { settings: serializeSettings(layout.settings) }
|
|
1684
|
+
...serializeSettings(layout.settings) === void 0 ? {} : { settings: serializeSettings(layout.settings) },
|
|
1685
|
+
...serializeClones(layout.clones) === void 0 ? {} : { clones: serializeClones(layout.clones) }
|
|
1328
1686
|
});
|
|
1329
1687
|
}
|
|
1330
1688
|
function bySortOrder(items) {
|
|
@@ -1489,1482 +1847,1746 @@ function enforceLocks(definitions, layout, options = {}) {
|
|
|
1489
1847
|
}));
|
|
1490
1848
|
}
|
|
1491
1849
|
|
|
1492
|
-
// src/core/
|
|
1493
|
-
var
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
return String(value);
|
|
1499
|
-
}
|
|
1500
|
-
return typeof value;
|
|
1501
|
-
}
|
|
1502
|
-
function isRecord2(value) {
|
|
1503
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1850
|
+
// src/core/pages.ts
|
|
1851
|
+
var DEFAULT_PAGE_KEY = "default";
|
|
1852
|
+
var DEFAULT_PAGE_ROWS = 4;
|
|
1853
|
+
function rowsAllowed(page, options) {
|
|
1854
|
+
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
1855
|
+
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
1504
1856
|
}
|
|
1505
|
-
function
|
|
1506
|
-
return
|
|
1857
|
+
function hasPages(layout) {
|
|
1858
|
+
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
1507
1859
|
}
|
|
1508
|
-
function
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
problems.push(
|
|
1512
|
-
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
1513
|
-
);
|
|
1860
|
+
function pageList(layout) {
|
|
1861
|
+
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
1862
|
+
return layout.pages;
|
|
1514
1863
|
}
|
|
1864
|
+
return [
|
|
1865
|
+
{
|
|
1866
|
+
key: DEFAULT_PAGE_KEY,
|
|
1867
|
+
title: "Page 1",
|
|
1868
|
+
order: layout.order,
|
|
1869
|
+
hidden: layout.hidden,
|
|
1870
|
+
minimized: layout.minimized
|
|
1871
|
+
}
|
|
1872
|
+
];
|
|
1515
1873
|
}
|
|
1516
|
-
function
|
|
1517
|
-
if (
|
|
1518
|
-
|
|
1519
|
-
|
|
1874
|
+
function withExplicitPages(layout) {
|
|
1875
|
+
if (hasPages(layout)) return layout;
|
|
1876
|
+
return {
|
|
1877
|
+
...layout,
|
|
1878
|
+
order: [],
|
|
1879
|
+
hidden: [],
|
|
1880
|
+
minimized: [],
|
|
1881
|
+
pages: pageList(layout)
|
|
1882
|
+
};
|
|
1520
1883
|
}
|
|
1521
|
-
function
|
|
1522
|
-
|
|
1523
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1524
|
-
problems.push(
|
|
1525
|
-
`${join(path, field)} must be a finite number, got ${describeType2(value)}.`
|
|
1526
|
-
);
|
|
1527
|
-
}
|
|
1884
|
+
function sameKeys(a, b) {
|
|
1885
|
+
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
1528
1886
|
}
|
|
1529
|
-
function
|
|
1530
|
-
if (
|
|
1531
|
-
|
|
1532
|
-
|
|
1887
|
+
function pageLayout(layout, pageKey) {
|
|
1888
|
+
if (!hasPages(layout))
|
|
1889
|
+
return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
|
|
1890
|
+
const page = layout.pages?.find(
|
|
1891
|
+
(p) => p.key === pageKey
|
|
1892
|
+
);
|
|
1893
|
+
if (page === void 0) return void 0;
|
|
1894
|
+
const { pages, ...rest } = layout;
|
|
1895
|
+
return {
|
|
1896
|
+
...rest,
|
|
1897
|
+
order: page.order,
|
|
1898
|
+
hidden: page.hidden,
|
|
1899
|
+
minimized: page.minimized
|
|
1900
|
+
};
|
|
1533
1901
|
}
|
|
1534
|
-
function
|
|
1535
|
-
const
|
|
1536
|
-
if (
|
|
1537
|
-
|
|
1538
|
-
`${path}.value must be a string or a finite number, got ${describeType2(value)}.`
|
|
1539
|
-
);
|
|
1902
|
+
function withPageLayout(layout, pageKey, next) {
|
|
1903
|
+
const current = pageLayout(layout, pageKey);
|
|
1904
|
+
if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
|
|
1905
|
+
return layout;
|
|
1540
1906
|
}
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1907
|
+
if (!hasPages(layout)) {
|
|
1908
|
+
return {
|
|
1909
|
+
...layout,
|
|
1910
|
+
order: next.order,
|
|
1911
|
+
hidden: next.hidden,
|
|
1912
|
+
minimized: next.minimized
|
|
1913
|
+
};
|
|
1548
1914
|
}
|
|
1915
|
+
return {
|
|
1916
|
+
...layout,
|
|
1917
|
+
pages: (layout.pages ?? []).map(
|
|
1918
|
+
(page) => page.key === pageKey ? {
|
|
1919
|
+
...page,
|
|
1920
|
+
order: next.order,
|
|
1921
|
+
hidden: next.hidden,
|
|
1922
|
+
minimized: next.minimized
|
|
1923
|
+
} : page
|
|
1924
|
+
)
|
|
1925
|
+
};
|
|
1549
1926
|
}
|
|
1550
|
-
function
|
|
1551
|
-
|
|
1552
|
-
if (value === void 0) return;
|
|
1553
|
-
if (typeof value !== "string") {
|
|
1554
|
-
problems.push(
|
|
1555
|
-
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
1556
|
-
);
|
|
1557
|
-
} else if (!isSafe(value)) {
|
|
1558
|
-
problems.push(
|
|
1559
|
-
`${join(path, field)} must be ${allowed}, got ${JSON.stringify(value.slice(0, 80))}.`
|
|
1560
|
-
);
|
|
1561
|
-
}
|
|
1927
|
+
function normalizedTitle(title) {
|
|
1928
|
+
return title.trim().normalize("NFKC").toLowerCase();
|
|
1562
1929
|
}
|
|
1563
|
-
function
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1930
|
+
function resolvePageKey(layout, reference) {
|
|
1931
|
+
if (reference === void 0 || reference === "") return void 0;
|
|
1932
|
+
const pages = pageList(layout);
|
|
1933
|
+
const byKey = pages.find((p) => p.key === reference);
|
|
1934
|
+
if (byKey !== void 0) return byKey.key;
|
|
1935
|
+
const wanted = normalizedTitle(reference);
|
|
1936
|
+
return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
|
|
1937
|
+
}
|
|
1938
|
+
function homePageKey(layout, item) {
|
|
1939
|
+
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
1940
|
+
}
|
|
1941
|
+
function assignPages(definitions, layout) {
|
|
1942
|
+
const pages = pageList(layout);
|
|
1943
|
+
const assigned = new Map(pages.map((p) => [p.key, []]));
|
|
1944
|
+
for (const definition of definitions) {
|
|
1945
|
+
let pageKey;
|
|
1946
|
+
if (!resolveWidgetLock(definition).move) {
|
|
1947
|
+
pageKey = pages.find(
|
|
1948
|
+
(p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
|
|
1949
|
+
)?.key;
|
|
1950
|
+
}
|
|
1951
|
+
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
1570
1952
|
}
|
|
1571
|
-
return
|
|
1953
|
+
return assigned;
|
|
1572
1954
|
}
|
|
1573
|
-
function
|
|
1574
|
-
const
|
|
1575
|
-
|
|
1576
|
-
problems.push(
|
|
1577
|
-
`${join(path, field)} must be one of ${options.map((o) => `"${o}"`).join(", ")}, got ${typeof value === "string" ? JSON.stringify(value) : describeType2(value)}.`
|
|
1578
|
-
);
|
|
1955
|
+
function pageOf(definitions, layout, key) {
|
|
1956
|
+
for (const [pageKey, widgets] of assignPages(definitions, layout)) {
|
|
1957
|
+
if (widgets.some((w) => w.key === key)) return pageKey;
|
|
1579
1958
|
}
|
|
1959
|
+
return void 0;
|
|
1580
1960
|
}
|
|
1581
|
-
function
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
check(item, itemPath);
|
|
1586
|
-
} else {
|
|
1587
|
-
problems.push(
|
|
1588
|
-
`${itemPath} must be an object, got ${describeType2(item)}.`
|
|
1589
|
-
);
|
|
1590
|
-
}
|
|
1591
|
-
});
|
|
1961
|
+
function pageWidgets(definitions, layout, pageKey) {
|
|
1962
|
+
const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
|
|
1963
|
+
const view = pageLayout(layout, pageKey);
|
|
1964
|
+
return view === void 0 ? [] : visibleWidgets(onPage, view);
|
|
1592
1965
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
if (data["previous"] !== null) {
|
|
1604
|
-
optionalNumber(data, "previous", "", problems);
|
|
1605
|
-
}
|
|
1606
|
-
requireOneOf(data, "format", VALUE_FORMATS, "", problems);
|
|
1607
|
-
optionalString(data, "currency", "", problems);
|
|
1608
|
-
requireString(data, "label", "", problems);
|
|
1609
|
-
optionalString(data, "hint", "", problems);
|
|
1610
|
-
optionalBoolean(data, "higherIsBetter", "", problems);
|
|
1611
|
-
return;
|
|
1612
|
-
case "GAUGE":
|
|
1613
|
-
requireNumber(data, "value", "", problems);
|
|
1614
|
-
requireNumber(data, "max", "", problems);
|
|
1615
|
-
requireString(data, "label", "", problems);
|
|
1616
|
-
if (typeof data["max"] === "number" && data["max"] <= 0) {
|
|
1617
|
-
problems.push(`max must be greater than 0, got ${data["max"]}.`);
|
|
1618
|
-
}
|
|
1619
|
-
return;
|
|
1620
|
-
case "TABLE": {
|
|
1621
|
-
const columns = requireArray(data, "columns", "", problems);
|
|
1622
|
-
if (columns !== null) {
|
|
1623
|
-
eachRecord(columns, "columns", problems, (column, path) => {
|
|
1624
|
-
requireString(column, "label", path, problems);
|
|
1625
|
-
optionalBoolean(column, "numeric", path, problems);
|
|
1626
|
-
});
|
|
1627
|
-
}
|
|
1628
|
-
const rows = requireArray(data, "rows", "", problems);
|
|
1629
|
-
if (rows !== null) {
|
|
1630
|
-
rows.forEach((row, rowIndex) => {
|
|
1631
|
-
const rowPath = `rows[${rowIndex}]`;
|
|
1632
|
-
if (!Array.isArray(row)) {
|
|
1633
|
-
problems.push(
|
|
1634
|
-
`${rowPath} must be an array of cells, got ${describeType2(row)}.`
|
|
1635
|
-
);
|
|
1636
|
-
return;
|
|
1637
|
-
}
|
|
1638
|
-
if (columns !== null && row.length !== columns.length) {
|
|
1639
|
-
problems.push(
|
|
1640
|
-
`${rowPath} has ${row.length} cell(s) but there are ${columns.length} column(s).`
|
|
1641
|
-
);
|
|
1642
|
-
}
|
|
1643
|
-
eachRecord(row, rowPath, problems, (cell, cellPath) => {
|
|
1644
|
-
requireString(cell, "text", cellPath, problems);
|
|
1645
|
-
optionalSortValue(cell, cellPath, problems);
|
|
1646
|
-
optionalUrl(
|
|
1647
|
-
cell,
|
|
1648
|
-
"href",
|
|
1649
|
-
cellPath,
|
|
1650
|
-
problems,
|
|
1651
|
-
isSafeHref,
|
|
1652
|
-
HREF_RULE
|
|
1653
|
-
);
|
|
1654
|
-
});
|
|
1655
|
-
});
|
|
1656
|
-
}
|
|
1657
|
-
optionalString(data, "footer", "", problems);
|
|
1658
|
-
return;
|
|
1659
|
-
}
|
|
1660
|
-
case "BAR_LIST": {
|
|
1661
|
-
const items = requireArray(data, "items", "", problems);
|
|
1662
|
-
if (items !== null) {
|
|
1663
|
-
eachRecord(items, "items", problems, (item, path) => {
|
|
1664
|
-
requireString(item, "label", path, problems);
|
|
1665
|
-
requireNumber(item, "value", path, problems);
|
|
1666
|
-
optionalString(item, "display", path, problems);
|
|
1667
|
-
});
|
|
1668
|
-
}
|
|
1669
|
-
optionalNumber(data, "total", "", problems);
|
|
1670
|
-
return;
|
|
1671
|
-
}
|
|
1672
|
-
case "ALERT_LIST": {
|
|
1673
|
-
const items = requireArray(data, "items", "", problems);
|
|
1674
|
-
if (items !== null) {
|
|
1675
|
-
eachRecord(items, "items", problems, (item, path) => {
|
|
1676
|
-
requireString(item, "title", path, problems);
|
|
1677
|
-
optionalUrl(item, "href", path, problems, isSafeHref, HREF_RULE);
|
|
1678
|
-
optionalUrl(
|
|
1679
|
-
item,
|
|
1680
|
-
"thumbnailUrl",
|
|
1681
|
-
path,
|
|
1682
|
-
problems,
|
|
1683
|
-
isSafeImageUrl,
|
|
1684
|
-
IMAGE_RULE
|
|
1685
|
-
);
|
|
1686
|
-
requireString(item, "valueLabel", path, problems);
|
|
1687
|
-
optionalString(item, "detail", path, problems);
|
|
1688
|
-
});
|
|
1689
|
-
}
|
|
1690
|
-
requireNumber(data, "total", "", problems);
|
|
1691
|
-
requireString(data, "emptyText", "", problems);
|
|
1692
|
-
if (items !== null && typeof data["total"] === "number" && data["total"] < items.length) {
|
|
1693
|
-
problems.push(
|
|
1694
|
-
`total (${data["total"]}) must be at least the number of items (${items.length}).`
|
|
1695
|
-
);
|
|
1696
|
-
}
|
|
1697
|
-
return;
|
|
1698
|
-
}
|
|
1699
|
-
case "GRAPH": {
|
|
1700
|
-
const series = requireArray(data, "series", "", problems);
|
|
1701
|
-
if (series !== null) {
|
|
1702
|
-
if (series.length > MAX_GRAPH_SERIES) {
|
|
1703
|
-
problems.push(
|
|
1704
|
-
`series has ${series.length} entries; at most ${MAX_GRAPH_SERIES} are allowed (fold the rest into "Other").`
|
|
1705
|
-
);
|
|
1706
|
-
}
|
|
1707
|
-
eachRecord(series, "series", problems, (entry, path) => {
|
|
1708
|
-
requireString(entry, "name", path, problems);
|
|
1709
|
-
const points = requireArray(entry, "points", path, problems);
|
|
1710
|
-
if (points !== null) {
|
|
1711
|
-
eachRecord(
|
|
1712
|
-
points,
|
|
1713
|
-
`${path}.points`,
|
|
1714
|
-
problems,
|
|
1715
|
-
(point, pointPath) => {
|
|
1716
|
-
requireString(point, "label", pointPath, problems);
|
|
1717
|
-
requireNumber(point, "value", pointPath, problems);
|
|
1718
|
-
}
|
|
1719
|
-
);
|
|
1720
|
-
}
|
|
1721
|
-
});
|
|
1722
|
-
}
|
|
1723
|
-
requireOneOf(data, "chartType", ["bar", "line"], "", problems);
|
|
1724
|
-
requireOneOf(data, "valueFormat", VALUE_FORMATS, "", problems);
|
|
1725
|
-
optionalString(data, "currency", "", problems);
|
|
1726
|
-
optionalString(data, "xLabel", "", problems);
|
|
1727
|
-
return;
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1966
|
+
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
1967
|
+
const page = pageList(layout).find(
|
|
1968
|
+
(p) => p.key === pageKey
|
|
1969
|
+
);
|
|
1970
|
+
if (page === void 0) return void 0;
|
|
1971
|
+
const maxRows = rowsAllowed(page, options);
|
|
1972
|
+
const rowsUsed = placeRows(
|
|
1973
|
+
pageWidgets(definitions, layout, pageKey)
|
|
1974
|
+
).length;
|
|
1975
|
+
return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
|
|
1730
1976
|
}
|
|
1731
|
-
function
|
|
1732
|
-
|
|
1733
|
-
if (!isRecord2(detail)) {
|
|
1734
|
-
problems.push(
|
|
1735
|
-
`detail must be true, false or an options object, got ${describeType2(detail)}.`
|
|
1736
|
-
);
|
|
1737
|
-
return;
|
|
1738
|
-
}
|
|
1739
|
-
optionalString(detail, "title", "detail", problems);
|
|
1740
|
-
const pageSize = detail["pageSize"];
|
|
1741
|
-
if (pageSize !== void 0 && !(typeof pageSize === "number" && Number.isInteger(pageSize) && pageSize >= 1 && pageSize <= MAX_DETAIL_PAGE_SIZE)) {
|
|
1742
|
-
problems.push(
|
|
1743
|
-
`detail.pageSize must be a whole number from 1 to ${MAX_DETAIL_PAGE_SIZE}, got ${typeof pageSize === "number" ? String(pageSize) : describeType2(pageSize)}.`
|
|
1744
|
-
);
|
|
1745
|
-
}
|
|
1746
|
-
if (detail["mode"] !== void 0) {
|
|
1747
|
-
requireOneOf(detail, "mode", DETAIL_MODES, "detail", problems);
|
|
1748
|
-
}
|
|
1977
|
+
function describePage(page) {
|
|
1978
|
+
return `Page "${page.title}"`;
|
|
1749
1979
|
}
|
|
1750
|
-
function
|
|
1751
|
-
|
|
1752
|
-
if (typeof controls !== "boolean" && !isRecord2(controls)) {
|
|
1753
|
-
problems.push(
|
|
1754
|
-
`tableControls must be true, false or an options object, got ${describeType2(controls)}.`
|
|
1755
|
-
);
|
|
1756
|
-
return;
|
|
1757
|
-
}
|
|
1758
|
-
if (isRecord2(controls)) {
|
|
1759
|
-
optionalBoolean(controls, "search", "tableControls", problems);
|
|
1760
|
-
optionalBoolean(controls, "sort", "tableControls", problems);
|
|
1761
|
-
}
|
|
1762
|
-
if (typeof kind === "string" && kind !== "TABLE") {
|
|
1763
|
-
problems.push(
|
|
1764
|
-
`tableControls only applies to TABLE widgets, but kind is "${kind}".`
|
|
1765
|
-
);
|
|
1766
|
-
}
|
|
1980
|
+
function titleOf(item) {
|
|
1981
|
+
return item.title ?? item.key;
|
|
1767
1982
|
}
|
|
1768
|
-
function
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
);
|
|
1775
|
-
return;
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
const { minWidth, maxWidth, width } = value;
|
|
1779
|
-
if (isWidgetWidth(minWidth) && isWidgetWidth(maxWidth) && minWidth > maxWidth) {
|
|
1780
|
-
problems.push(
|
|
1781
|
-
`minWidth (${minWidth}) must not be more than maxWidth (${maxWidth}).`
|
|
1983
|
+
function checkTitle(layout, title, exceptKey) {
|
|
1984
|
+
const trimmed = title.trim();
|
|
1985
|
+
if (trimmed === "") return err("A page title must not be empty.");
|
|
1986
|
+
if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
|
|
1987
|
+
return err(
|
|
1988
|
+
`Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
|
|
1782
1989
|
);
|
|
1783
|
-
return;
|
|
1784
1990
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
}
|
|
1791
|
-
if (isWidgetWidth(maxWidth) && width > maxWidth) {
|
|
1792
|
-
problems.push(
|
|
1793
|
-
`width (${width}) must not be more than maxWidth (${maxWidth}).`
|
|
1794
|
-
);
|
|
1795
|
-
}
|
|
1991
|
+
const wanted = normalizedTitle(trimmed);
|
|
1992
|
+
const clash = pageList(layout).find(
|
|
1993
|
+
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
1994
|
+
);
|
|
1995
|
+
if (clash !== void 0) {
|
|
1996
|
+
return err(`Another page is already called "${clash.title}".`);
|
|
1796
1997
|
}
|
|
1998
|
+
return ok(trimmed);
|
|
1797
1999
|
}
|
|
1798
|
-
function
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
);
|
|
1804
|
-
return;
|
|
1805
|
-
}
|
|
1806
|
-
optionalBoolean(locked, "move", "locked", problems);
|
|
1807
|
-
optionalBoolean(locked, "hide", "locked", problems);
|
|
1808
|
-
optionalBoolean(locked, "minimize", "locked", problems);
|
|
1809
|
-
optionalBoolean(locked, "options", "locked", problems);
|
|
2000
|
+
function nextPageKey(layout) {
|
|
2001
|
+
const keys = new Set(pageList(layout).map((p) => p.key));
|
|
2002
|
+
let n = 1;
|
|
2003
|
+
while (keys.has(`page-${n}`)) n += 1;
|
|
2004
|
+
return `page-${n}`;
|
|
1810
2005
|
}
|
|
1811
|
-
function
|
|
1812
|
-
const
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
2006
|
+
function nextPageTitle(layout) {
|
|
2007
|
+
const used = new Set(
|
|
2008
|
+
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
2009
|
+
);
|
|
2010
|
+
let n = pageList(layout).length + 1;
|
|
2011
|
+
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
2012
|
+
return `Page ${n}`;
|
|
2013
|
+
}
|
|
2014
|
+
function addPage(layout, options = {}) {
|
|
2015
|
+
if (pageList(layout).length >= MAX_PARSED_PAGES) {
|
|
2016
|
+
return err(
|
|
2017
|
+
`The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
|
|
1816
2018
|
);
|
|
1817
|
-
return [];
|
|
1818
2019
|
}
|
|
1819
|
-
const
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
2020
|
+
const key = options.key ?? nextPageKey(layout);
|
|
2021
|
+
if (pageList(layout).some((p) => p.key === key)) {
|
|
2022
|
+
return err(`A page with the key "${key}" already exists.`);
|
|
2023
|
+
}
|
|
2024
|
+
const title = checkTitle(
|
|
2025
|
+
layout,
|
|
2026
|
+
options.title ?? nextPageTitle(layout)
|
|
2027
|
+
);
|
|
2028
|
+
if (!title.ok) return title;
|
|
2029
|
+
const base = withExplicitPages(layout);
|
|
2030
|
+
const page = {
|
|
2031
|
+
key,
|
|
2032
|
+
title: title.value,
|
|
2033
|
+
order: [],
|
|
2034
|
+
hidden: [],
|
|
2035
|
+
minimized: []
|
|
2036
|
+
};
|
|
2037
|
+
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
2038
|
+
}
|
|
2039
|
+
function renamePage(layout, pageKey, title) {
|
|
2040
|
+
const page = pageList(layout).find(
|
|
2041
|
+
(p) => p.key === pageKey
|
|
2042
|
+
);
|
|
2043
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2044
|
+
const checked = checkTitle(layout, title, pageKey);
|
|
2045
|
+
if (!checked.ok) return checked;
|
|
2046
|
+
if (checked.value === page.title) return ok(layout);
|
|
2047
|
+
const base = withExplicitPages(layout);
|
|
2048
|
+
return ok({
|
|
2049
|
+
...base,
|
|
2050
|
+
pages: (base.pages ?? []).map(
|
|
2051
|
+
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
2052
|
+
)
|
|
1843
2053
|
});
|
|
1844
|
-
return keys;
|
|
1845
2054
|
}
|
|
1846
|
-
function
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
2055
|
+
function movePage(layout, pageKey, offset) {
|
|
2056
|
+
const pages = layout.pages ?? [];
|
|
2057
|
+
const from = pages.findIndex((p) => p.key === pageKey);
|
|
2058
|
+
if (from === -1) return layout;
|
|
2059
|
+
const to = Math.max(
|
|
2060
|
+
0,
|
|
2061
|
+
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
2062
|
+
);
|
|
2063
|
+
if (to === from) return layout;
|
|
2064
|
+
const next = [...pages];
|
|
2065
|
+
const [moved] = next.splice(from, 1);
|
|
2066
|
+
next.splice(to, 0, moved);
|
|
2067
|
+
return { ...layout, pages: next };
|
|
2068
|
+
}
|
|
2069
|
+
function overflowing(definitions, layout, options) {
|
|
2070
|
+
return pageList(layout).find(
|
|
2071
|
+
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
2072
|
+
);
|
|
2073
|
+
}
|
|
2074
|
+
function removePage(definitions, layout, pageKey, options = {}) {
|
|
2075
|
+
const pages = pageList(layout);
|
|
2076
|
+
const index = pages.findIndex((p) => p.key === pageKey);
|
|
2077
|
+
const page = pages[index];
|
|
2078
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2079
|
+
if (pages.length === 1) {
|
|
2080
|
+
return err(
|
|
2081
|
+
`${describePage(page)} is the only page, so it can't be removed.`
|
|
2082
|
+
);
|
|
1850
2083
|
}
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
2084
|
+
const target = pages[index > 0 ? index - 1 : 1];
|
|
2085
|
+
const merged = {
|
|
2086
|
+
...layout,
|
|
2087
|
+
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
2088
|
+
(p) => p.key === target.key ? {
|
|
2089
|
+
...p,
|
|
2090
|
+
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
2091
|
+
hidden: [...p.hidden, ...page.hidden],
|
|
2092
|
+
minimized: [...p.minimized, ...page.minimized]
|
|
2093
|
+
} : p
|
|
2094
|
+
)
|
|
2095
|
+
};
|
|
2096
|
+
const full = overflowing(
|
|
2097
|
+
definitions,
|
|
2098
|
+
merged,
|
|
2099
|
+
options
|
|
2100
|
+
);
|
|
2101
|
+
if (full !== void 0) {
|
|
2102
|
+
return err(
|
|
2103
|
+
`${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.`
|
|
2104
|
+
);
|
|
1855
2105
|
}
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
2106
|
+
return ok(merged);
|
|
2107
|
+
}
|
|
2108
|
+
function without(keys, key) {
|
|
2109
|
+
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
2110
|
+
}
|
|
2111
|
+
function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
|
|
2112
|
+
const widget = definitions.find((d) => d.key === widgetKey);
|
|
2113
|
+
if (widget === void 0) {
|
|
2114
|
+
return err(
|
|
2115
|
+
`Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
|
|
1860
2116
|
);
|
|
1861
|
-
} else if (seenKeys.has(key)) {
|
|
1862
|
-
problems.push(`${path}.key "${key}" is used by another option.`);
|
|
1863
|
-
} else {
|
|
1864
|
-
seenKeys.add(key);
|
|
1865
2117
|
}
|
|
1866
|
-
|
|
1867
|
-
const
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
if (values.length > 0)
|
|
1899
|
-
requireOneOf(option, "default", values, path, problems);
|
|
1900
|
-
break;
|
|
1901
|
-
}
|
|
1902
|
-
case "number": {
|
|
1903
|
-
const { min, max, step } = option;
|
|
1904
|
-
requireNumber(option, "min", path, problems);
|
|
1905
|
-
requireNumber(option, "max", path, problems);
|
|
1906
|
-
if (typeof min === "number" && typeof max === "number" && min > max) {
|
|
1907
|
-
problems.push(
|
|
1908
|
-
`${path}.min (${min}) must not be more than max (${max}).`
|
|
1909
|
-
);
|
|
1910
|
-
}
|
|
1911
|
-
if (step !== void 0 && !(typeof step === "number" && Number.isFinite(step) && step > 0)) {
|
|
1912
|
-
problems.push(
|
|
1913
|
-
`${path}.step must be a number above 0, got ${describeType2(step)}.`
|
|
1914
|
-
);
|
|
2118
|
+
const pages = pageList(layout);
|
|
2119
|
+
const target = pages.find((p) => p.key === targetKey);
|
|
2120
|
+
if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
|
|
2121
|
+
const sourceKey = pageOf(definitions, layout, widgetKey);
|
|
2122
|
+
if (sourceKey === targetKey) return ok(layout);
|
|
2123
|
+
if (resolveWidgetLock(widget).move) {
|
|
2124
|
+
return err(
|
|
2125
|
+
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
2126
|
+
);
|
|
2127
|
+
}
|
|
2128
|
+
const base = withExplicitPages(layout);
|
|
2129
|
+
const source = (base.pages ?? []).find(
|
|
2130
|
+
(p) => p.key === sourceKey
|
|
2131
|
+
);
|
|
2132
|
+
const hidden = source.hidden.includes(widgetKey);
|
|
2133
|
+
const minimized = source.minimized.includes(widgetKey);
|
|
2134
|
+
const targetOrder = pageWidgets(
|
|
2135
|
+
definitions,
|
|
2136
|
+
base,
|
|
2137
|
+
targetKey
|
|
2138
|
+
).map((d) => d.key);
|
|
2139
|
+
const moved = {
|
|
2140
|
+
...base,
|
|
2141
|
+
pages: (base.pages ?? []).map((p) => {
|
|
2142
|
+
if (p.key === sourceKey) {
|
|
2143
|
+
return {
|
|
2144
|
+
...p,
|
|
2145
|
+
order: without(p.order, widgetKey),
|
|
2146
|
+
hidden: without(p.hidden, widgetKey),
|
|
2147
|
+
minimized: without(p.minimized, widgetKey)
|
|
2148
|
+
};
|
|
1915
2149
|
}
|
|
1916
|
-
if (
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
);
|
|
1924
|
-
} else if (typeof min === "number" && typeof step === "number" && step > 0 && Math.abs(
|
|
1925
|
-
(fallback - min) / step - Math.round((fallback - min) / step)
|
|
1926
|
-
) > 1e-9) {
|
|
1927
|
-
problems.push(
|
|
1928
|
-
`${path}.default (${fallback}) must be ${min} plus a whole number of steps of ${step}.`
|
|
1929
|
-
);
|
|
2150
|
+
if (p.key === targetKey) {
|
|
2151
|
+
return {
|
|
2152
|
+
...p,
|
|
2153
|
+
order: hidden ? p.order : [...targetOrder, widgetKey],
|
|
2154
|
+
hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
|
|
2155
|
+
minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
|
|
2156
|
+
};
|
|
1930
2157
|
}
|
|
1931
|
-
|
|
1932
|
-
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
problems.push(
|
|
1944
|
-
`${path}.maxLength must be a whole number from 1 to ${MAX_TEXT_OPTION_LENGTH}, got ${typeof maxLength === "number" ? maxLength : describeType2(maxLength)}.`
|
|
1945
|
-
);
|
|
1946
|
-
}
|
|
1947
|
-
if (typeof fallback !== "string") {
|
|
1948
|
-
problems.push(
|
|
1949
|
-
`${path}.default must be a string, got ${describeType2(fallback)}.`
|
|
1950
|
-
);
|
|
1951
|
-
} else if (typeof maxLength === "number" && fallback.length > maxLength) {
|
|
1952
|
-
problems.push(
|
|
1953
|
-
`${path}.default is ${fallback.length} characters; maxLength is ${maxLength}.`
|
|
1954
|
-
);
|
|
1955
|
-
}
|
|
1956
|
-
break;
|
|
1957
|
-
}
|
|
1958
|
-
case "dateRange": {
|
|
1959
|
-
const presets = option["presets"];
|
|
1960
|
-
const offered = [];
|
|
1961
|
-
if (presets !== void 0) {
|
|
1962
|
-
if (!Array.isArray(presets) || presets.length === 0) {
|
|
1963
|
-
problems.push(
|
|
1964
|
-
`${path}.presets must be a non-empty array of { value, label }, got ${describeType2(presets)}.`
|
|
1965
|
-
);
|
|
1966
|
-
} else {
|
|
1967
|
-
presets.forEach((preset, index) => {
|
|
1968
|
-
const presetPath = `${path}.presets[${index}]`;
|
|
1969
|
-
if (!isRecord2(preset)) {
|
|
1970
|
-
problems.push(
|
|
1971
|
-
`${presetPath} must be an object, got ${describeType2(preset)}.`
|
|
1972
|
-
);
|
|
1973
|
-
return;
|
|
1974
|
-
}
|
|
1975
|
-
requireString(preset, "label", presetPath, problems);
|
|
1976
|
-
requireOneOf(
|
|
1977
|
-
preset,
|
|
1978
|
-
"value",
|
|
1979
|
-
DATE_RANGE_PRESETS,
|
|
1980
|
-
presetPath,
|
|
1981
|
-
problems
|
|
1982
|
-
);
|
|
1983
|
-
if (typeof preset["value"] === "string")
|
|
1984
|
-
offered.push(preset["value"]);
|
|
1985
|
-
});
|
|
1986
|
-
}
|
|
1987
|
-
}
|
|
1988
|
-
const allowed = offered.length > 0 ? offered : DATE_RANGE_PRESETS;
|
|
1989
|
-
if (typeof fallback !== "string" || !(allowed.includes(fallback) || isIsoInterval(fallback))) {
|
|
1990
|
-
problems.push(
|
|
1991
|
-
`${path}.default must be one of ${allowed.map((p) => `"${p}"`).join(", ")} or an interval like "2026-01-01/2026-01-31", got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
1992
|
-
);
|
|
1993
|
-
}
|
|
1994
|
-
break;
|
|
1995
|
-
}
|
|
1996
|
-
case "color":
|
|
1997
|
-
if (typeof fallback !== "string" || !/^#[0-9a-fA-F]{6}$/.test(fallback)) {
|
|
1998
|
-
problems.push(
|
|
1999
|
-
`${path}.default must be a color like "#1c5cab", got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
2000
|
-
);
|
|
2001
|
-
}
|
|
2002
|
-
break;
|
|
2003
|
-
case "sort":
|
|
2004
|
-
case "columns": {
|
|
2005
|
-
const supported = type === "sort" ? kind === "TABLE" || kind === "BAR_LIST" : kind === "TABLE";
|
|
2006
|
-
if (!supported && typeof kind === "string") {
|
|
2007
|
-
problems.push(
|
|
2008
|
-
`${path} is a ${type} option, which only applies to ${type === "sort" ? "TABLE and BAR_LIST" : "TABLE"} widgets, but kind is "${kind}".`
|
|
2009
|
-
);
|
|
2010
|
-
}
|
|
2011
|
-
if (seenTypes.has(type)) {
|
|
2012
|
-
problems.push(
|
|
2013
|
-
`${path} is a second ${type} option; a widget has at most one.`
|
|
2014
|
-
);
|
|
2015
|
-
}
|
|
2016
|
-
seenTypes.add(type);
|
|
2017
|
-
const columnKeys = checkOptionColumns(
|
|
2018
|
-
option,
|
|
2019
|
-
path,
|
|
2020
|
-
supported ? kind : void 0,
|
|
2021
|
-
problems
|
|
2022
|
-
);
|
|
2023
|
-
if (option["apply"] !== void 0) {
|
|
2024
|
-
requireOneOf(option, "apply", ["provider", "client"], path, problems);
|
|
2025
|
-
}
|
|
2026
|
-
if (type === "sort") {
|
|
2027
|
-
if (fallback !== void 0) {
|
|
2028
|
-
const sort = typeof fallback === "string" ? parseSortValue(fallback) : void 0;
|
|
2029
|
-
if (sort === void 0) {
|
|
2030
|
-
problems.push(
|
|
2031
|
-
`${path}.default must look like "c1:desc" ("column:asc" or "column:desc"), got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
2032
|
-
);
|
|
2033
|
-
} else if (columnKeys.length > 0 && !columnKeys.includes(sort.column)) {
|
|
2034
|
-
requireOneOf(
|
|
2035
|
-
{ default: sort.column },
|
|
2036
|
-
"default",
|
|
2037
|
-
columnKeys,
|
|
2038
|
-
path,
|
|
2039
|
-
problems
|
|
2040
|
-
);
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
} else if (!Array.isArray(fallback) || fallback.length === 0 || fallback.some((k) => typeof k !== "string")) {
|
|
2044
|
-
problems.push(
|
|
2045
|
-
`${path}.default must be a non-empty array of column keys, got ${describeType2(fallback)}.`
|
|
2046
|
-
);
|
|
2047
|
-
} else {
|
|
2048
|
-
const shown = /* @__PURE__ */ new Set();
|
|
2049
|
-
for (const columnKey of fallback) {
|
|
2050
|
-
if (columnKeys.length > 0 && !columnKeys.includes(columnKey)) {
|
|
2051
|
-
problems.push(
|
|
2052
|
-
`${path}.default has "${columnKey}", which is not one of the columns (${columnKeys.map((c) => `"${c}"`).join(", ")}).`
|
|
2053
|
-
);
|
|
2054
|
-
} else if (shown.has(columnKey)) {
|
|
2055
|
-
problems.push(`${path}.default lists "${columnKey}" twice.`);
|
|
2056
|
-
}
|
|
2057
|
-
shown.add(columnKey);
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
break;
|
|
2158
|
+
return p;
|
|
2159
|
+
})
|
|
2160
|
+
};
|
|
2161
|
+
if (!hidden) {
|
|
2162
|
+
const allowed = rowsAllowed(target, options);
|
|
2163
|
+
const needed = placeRows(
|
|
2164
|
+
pageWidgets(definitions, moved, targetKey)
|
|
2165
|
+
).length;
|
|
2166
|
+
if (needed > allowed) {
|
|
2167
|
+
return err(
|
|
2168
|
+
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
2169
|
+
);
|
|
2061
2170
|
}
|
|
2062
2171
|
}
|
|
2172
|
+
return ok(moved);
|
|
2063
2173
|
}
|
|
2064
|
-
function
|
|
2065
|
-
if (
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2174
|
+
function withoutLockField(definition) {
|
|
2175
|
+
if (definition.locked === void 0) return definition;
|
|
2176
|
+
const copy = { ...definition };
|
|
2177
|
+
delete copy.locked;
|
|
2178
|
+
return copy;
|
|
2179
|
+
}
|
|
2180
|
+
function uniqueTitle2(taken, title, index) {
|
|
2181
|
+
let base = title.trim().slice(0, MAX_PAGE_TITLE_LENGTH);
|
|
2182
|
+
if (base === "") base = `Page ${index + 1}`;
|
|
2183
|
+
let candidate = base;
|
|
2184
|
+
let n = 1;
|
|
2185
|
+
while (taken.has(normalizedTitle(candidate))) {
|
|
2186
|
+
const suffix = ` (#${n})`;
|
|
2187
|
+
candidate = `${base.slice(0, MAX_PAGE_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
2188
|
+
n += 1;
|
|
2075
2189
|
}
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
seenKeys,
|
|
2084
|
-
seenTypes,
|
|
2085
|
-
problems
|
|
2086
|
-
);
|
|
2190
|
+
taken.add(normalizedTitle(candidate));
|
|
2191
|
+
return candidate;
|
|
2192
|
+
}
|
|
2193
|
+
function samePages(a, b) {
|
|
2194
|
+
return a.length === b.length && a.every((p, i) => {
|
|
2195
|
+
const q = b[i];
|
|
2196
|
+
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);
|
|
2087
2197
|
});
|
|
2088
2198
|
}
|
|
2089
|
-
function
|
|
2090
|
-
const
|
|
2091
|
-
|
|
2092
|
-
|
|
2199
|
+
function normalizeLayout(definitions, layout, options = {}) {
|
|
2200
|
+
const clones = cleanClones(definitions, layout.clones, options);
|
|
2201
|
+
let base = layout;
|
|
2202
|
+
if (clones !== layout.clones) {
|
|
2203
|
+
const { clones: _dropped, ...others } = layout;
|
|
2204
|
+
base = clones === void 0 ? others : { ...others, clones };
|
|
2205
|
+
}
|
|
2206
|
+
return normalizeWidgets(
|
|
2207
|
+
withClones(definitions, base, options),
|
|
2208
|
+
base,
|
|
2209
|
+
options
|
|
2210
|
+
);
|
|
2093
2211
|
}
|
|
2094
|
-
function
|
|
2095
|
-
const
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2212
|
+
function normalizeWidgets(definitions, layout, options) {
|
|
2213
|
+
const known = pruneLayout(definitions, layout);
|
|
2214
|
+
const enforced = enforceLocks(definitions, known, options);
|
|
2215
|
+
const locked = normalizeSettings(
|
|
2216
|
+
definitions,
|
|
2217
|
+
enforced,
|
|
2218
|
+
options
|
|
2219
|
+
);
|
|
2220
|
+
if (!hasPages(locked)) return locked === layout ? layout : locked;
|
|
2221
|
+
const defs = effectiveDefinitions(
|
|
2222
|
+
options.overrideLocks === true ? definitions.map(withoutLockField) : definitions,
|
|
2223
|
+
locked,
|
|
2224
|
+
options
|
|
2225
|
+
);
|
|
2226
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2227
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
2228
|
+
const held = /* @__PURE__ */ new Set();
|
|
2229
|
+
let pages = [];
|
|
2230
|
+
(locked.pages ?? []).forEach((page, index) => {
|
|
2231
|
+
if (seenKeys.has(page.key)) return;
|
|
2232
|
+
seenKeys.add(page.key);
|
|
2233
|
+
const keep = (list) => [
|
|
2234
|
+
...new Set(list.filter((k) => !held.has(k)))
|
|
2235
|
+
];
|
|
2236
|
+
const next = {
|
|
2237
|
+
...page,
|
|
2238
|
+
title: uniqueTitle2(taken, page.title, index),
|
|
2239
|
+
order: keep(page.order),
|
|
2240
|
+
hidden: keep(page.hidden),
|
|
2241
|
+
minimized: keep(page.minimized)
|
|
2242
|
+
};
|
|
2243
|
+
for (const k of [...next.order, ...next.hidden]) held.add(k);
|
|
2244
|
+
pages.push(next);
|
|
2245
|
+
});
|
|
2246
|
+
const work = () => ({ ...locked, pages });
|
|
2247
|
+
const pinned = defs.filter((d) => resolveWidgetLock(d).move);
|
|
2248
|
+
for (const widget of pinned) {
|
|
2249
|
+
const home = homePageKey(work(), widget);
|
|
2250
|
+
const keysHere = (p, list) => p.key !== home && p[list].includes(widget.key);
|
|
2251
|
+
const wasHidden = pages.some((p) => keysHere(p, "hidden"));
|
|
2252
|
+
const wasMinimized = pages.some((p) => keysHere(p, "minimized"));
|
|
2253
|
+
if (!wasHidden && !wasMinimized) continue;
|
|
2254
|
+
pages = pages.map((p) => {
|
|
2255
|
+
let next = p;
|
|
2256
|
+
if (keysHere(p, "hidden")) {
|
|
2257
|
+
next = { ...next, hidden: without(next.hidden, widget.key) };
|
|
2258
|
+
}
|
|
2259
|
+
if (keysHere(p, "minimized")) {
|
|
2260
|
+
next = { ...next, minimized: without(next.minimized, widget.key) };
|
|
2261
|
+
}
|
|
2262
|
+
if (p.key === home) {
|
|
2263
|
+
next = {
|
|
2264
|
+
...next,
|
|
2265
|
+
hidden: wasHidden && !next.hidden.includes(widget.key) ? [...next.hidden, widget.key] : next.hidden,
|
|
2266
|
+
minimized: wasMinimized && !next.minimized.includes(widget.key) ? [...next.minimized, widget.key] : next.minimized
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
return next;
|
|
2270
|
+
});
|
|
2106
2271
|
}
|
|
2107
|
-
const
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
`${subject}: kind must be one of ${WIDGET_KINDS.join(", ")} (or the payload must be {"empty": true, "text": ...}), got ${typeof kind === "string" ? JSON.stringify(kind) : describeType2(kind)}.`
|
|
2111
|
-
);
|
|
2272
|
+
const placedKeys = /* @__PURE__ */ new Set();
|
|
2273
|
+
for (const p of pages) {
|
|
2274
|
+
for (const k of [...p.order, ...p.hidden]) placedKeys.add(k);
|
|
2112
2275
|
}
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2276
|
+
const pinnedKeys = new Set(pinned.map((d) => d.key));
|
|
2277
|
+
const unplaced = defs.filter((d) => !placedKeys.has(d.key) && !pinnedKeys.has(d.key)).map((definition, index) => ({ definition, index })).sort(
|
|
2278
|
+
(a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
|
|
2279
|
+
).map(({ definition }) => definition);
|
|
2280
|
+
for (const widget of unplaced) {
|
|
2281
|
+
const home = homePageKey(work(), widget);
|
|
2282
|
+
pages = pages.map(
|
|
2283
|
+
(p) => p.key === home ? { ...p, order: [...p.order, widget.key] } : p
|
|
2116
2284
|
);
|
|
2117
2285
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
`Widget definition: must be an object, got ${describeType2(value)}.`
|
|
2286
|
+
for (let i = 0; i < pages.length; i += 1) {
|
|
2287
|
+
const page = pages[i];
|
|
2288
|
+
const shown = pageWidgets(defs, work(), page.key);
|
|
2289
|
+
const fits = fitCount(shown, rowsAllowed(page, options));
|
|
2290
|
+
if (fits >= shown.length) continue;
|
|
2291
|
+
const moving = shown.slice(fits).filter((d) => !pinnedKeys.has(d.key)).map((d) => d.key);
|
|
2292
|
+
if (moving.length === 0) continue;
|
|
2293
|
+
const moveSet = new Set(moving);
|
|
2294
|
+
const movedMinimized = page.minimized.filter(
|
|
2295
|
+
(k) => moveSet.has(k)
|
|
2129
2296
|
);
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2297
|
+
pages = pages.map(
|
|
2298
|
+
(p) => p.key === page.key ? {
|
|
2299
|
+
...p,
|
|
2300
|
+
order: p.order.filter((k) => !moveSet.has(k)),
|
|
2301
|
+
minimized: p.minimized.filter((k) => !moveSet.has(k))
|
|
2302
|
+
} : p
|
|
2303
|
+
);
|
|
2304
|
+
let placed = false;
|
|
2305
|
+
for (let j = i + 1; j < pages.length && !placed; j += 1) {
|
|
2306
|
+
const candidate = pages[j];
|
|
2307
|
+
const trial = pages.map(
|
|
2308
|
+
(p) => p.key === candidate.key ? {
|
|
2309
|
+
...p,
|
|
2310
|
+
order: [...moving, ...p.order],
|
|
2311
|
+
minimized: [...p.minimized, ...movedMinimized]
|
|
2312
|
+
} : p
|
|
2313
|
+
);
|
|
2314
|
+
const items = pageWidgets(
|
|
2315
|
+
defs,
|
|
2316
|
+
{ ...locked, pages: trial },
|
|
2317
|
+
candidate.key
|
|
2318
|
+
);
|
|
2319
|
+
if (placeRows(items).length <= rowsAllowed(candidate, options)) {
|
|
2320
|
+
pages = trial;
|
|
2321
|
+
placed = true;
|
|
2148
2322
|
}
|
|
2149
|
-
}
|
|
2323
|
+
}
|
|
2324
|
+
if (!placed) {
|
|
2325
|
+
const key = nextPageKey(work());
|
|
2326
|
+
const title = uniqueTitle2(
|
|
2327
|
+
new Set(pages.map((p) => normalizedTitle(p.title))),
|
|
2328
|
+
`Page ${pages.length + 1}`,
|
|
2329
|
+
pages.length
|
|
2330
|
+
);
|
|
2331
|
+
pages = [
|
|
2332
|
+
...pages,
|
|
2333
|
+
{ key, title, order: moving, hidden: [], minimized: movedMinimized }
|
|
2334
|
+
];
|
|
2335
|
+
}
|
|
2150
2336
|
}
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2337
|
+
const result = work();
|
|
2338
|
+
return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) && locked.settings === layout.settings ? layout : result;
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
// src/core/duplicate.ts
|
|
2342
|
+
function canDuplicate(definitions, key, options = {}) {
|
|
2343
|
+
const definition = definitions.find(
|
|
2344
|
+
(d) => d.key === key
|
|
2345
|
+
);
|
|
2346
|
+
if (definition === void 0) return false;
|
|
2347
|
+
if (options.overrideLocks === true) return true;
|
|
2348
|
+
const sourceKey = definition.sourceKey ?? definition.key;
|
|
2349
|
+
const original = definitions.find(
|
|
2350
|
+
(d) => d.key === sourceKey
|
|
2351
|
+
);
|
|
2352
|
+
return original !== void 0 && !resolveWidgetLock(original).clone;
|
|
2353
|
+
}
|
|
2354
|
+
function nextCloneKey(definitions, layout, fromKey) {
|
|
2355
|
+
const taken = /* @__PURE__ */ new Set([
|
|
2356
|
+
...definitions.map((d) => d.key),
|
|
2357
|
+
...(layout.clones ?? []).map((c) => c.key)
|
|
2358
|
+
]);
|
|
2359
|
+
let highest = 0;
|
|
2360
|
+
for (const clone of layout.clones ?? []) {
|
|
2361
|
+
if (clone.from === fromKey) {
|
|
2362
|
+
highest = Math.max(highest, cloneNumber(clone.key) ?? 0);
|
|
2363
|
+
}
|
|
2154
2364
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2365
|
+
let n = highest + 1;
|
|
2366
|
+
while (taken.has(`${fromKey}#${n}`)) n += 1;
|
|
2367
|
+
return `${fromKey}#${n}`;
|
|
2368
|
+
}
|
|
2369
|
+
function suggestedViewTitle(title, viewLabel) {
|
|
2370
|
+
return `${title} ${viewLabel}`.trim().slice(0, MAX_WIDGET_TITLE_LENGTH);
|
|
2371
|
+
}
|
|
2372
|
+
function duplicateWidget(definitions, layout, key, options = {}) {
|
|
2373
|
+
const widget = definitions.find((d) => d.key === key);
|
|
2374
|
+
if (widget === void 0) {
|
|
2375
|
+
return err(`Widget ${JSON.stringify(key)} isn't on this dashboard.`);
|
|
2376
|
+
}
|
|
2377
|
+
const originalKey = widget.sourceKey ?? widget.key;
|
|
2378
|
+
if (!canDuplicate(definitions, key, options)) {
|
|
2379
|
+
return err(
|
|
2380
|
+
`Widget ${JSON.stringify(originalKey)} is locked against duplicating.`
|
|
2158
2381
|
);
|
|
2159
2382
|
}
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2383
|
+
const cloneKey = nextCloneKey(definitions, layout, originalKey);
|
|
2384
|
+
const copiedSettings = (() => {
|
|
2385
|
+
const mine = layout.settings?.[key];
|
|
2386
|
+
if (mine === void 0) return void 0;
|
|
2387
|
+
const { title: _title, ...rest } = mine;
|
|
2388
|
+
return Object.keys(rest).length === 0 ? void 0 : rest;
|
|
2389
|
+
})();
|
|
2390
|
+
const settings = copiedSettings === void 0 ? layout.settings : { ...layout.settings, [cloneKey]: copiedSettings };
|
|
2391
|
+
let next = {
|
|
2392
|
+
...layout,
|
|
2393
|
+
clones: [...layout.clones ?? [], { key: cloneKey, from: originalKey }],
|
|
2394
|
+
...settings === void 0 ? {} : { settings }
|
|
2395
|
+
};
|
|
2396
|
+
const all = withClones(definitions, next, options);
|
|
2397
|
+
const assigned = assignPages(all, next);
|
|
2398
|
+
const pageKey = [...assigned].find(
|
|
2399
|
+
([, widgets]) => widgets.some((w) => w.key === key)
|
|
2400
|
+
)?.[0];
|
|
2401
|
+
if (pageKey !== void 0) {
|
|
2402
|
+
const view = pageLayout(next, pageKey);
|
|
2403
|
+
const onPage = assigned.get(pageKey) ?? [];
|
|
2404
|
+
if (view !== void 0) {
|
|
2405
|
+
const shown = visibleWidgets(onPage, view).filter((d) => !resolveWidgetLock(d).move && d.key !== cloneKey).map((d) => d.key);
|
|
2406
|
+
const at = shown.indexOf(key);
|
|
2407
|
+
const order = at === -1 ? [...shown, cloneKey] : [...shown.slice(0, at + 1), cloneKey, ...shown.slice(at + 1)];
|
|
2408
|
+
next = withPageLayout(next, pageKey, { ...view, order });
|
|
2170
2409
|
}
|
|
2171
2410
|
}
|
|
2172
|
-
if (
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2411
|
+
if (hasPages(next) && pageKey !== void 0) {
|
|
2412
|
+
const effective = effectiveDefinitions(all, next, options);
|
|
2413
|
+
const room = pageRoom(effective, next, pageKey, options);
|
|
2414
|
+
if (room !== void 0 && room.rowsUsed > room.maxRows) {
|
|
2415
|
+
const page = next.pages?.find(
|
|
2416
|
+
(p) => p.key === pageKey
|
|
2417
|
+
);
|
|
2418
|
+
const width = effective.find(
|
|
2419
|
+
(d) => d.key === cloneKey
|
|
2420
|
+
)?.width;
|
|
2421
|
+
return err(
|
|
2422
|
+
`Page "${page?.title ?? pageKey}" has no room for a widget${width === void 0 ? "" : ` ${width} wide`} (it holds ${room.maxRows} rows).`
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2177
2425
|
}
|
|
2178
|
-
return ok(
|
|
2426
|
+
return ok({ layout: next, key: cloneKey });
|
|
2427
|
+
}
|
|
2428
|
+
function deleteClone(layout, key) {
|
|
2429
|
+
if (layout.clones?.some((clone) => clone.key === key) !== true) return layout;
|
|
2430
|
+
const without2 = (keys) => keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
2431
|
+
const { clones, settings, ...rest } = layout;
|
|
2432
|
+
const remaining = (clones ?? []).filter((c) => c.key !== key);
|
|
2433
|
+
const { [key]: _gone, ...otherSettings } = settings ?? {};
|
|
2434
|
+
return {
|
|
2435
|
+
...rest,
|
|
2436
|
+
order: without2(layout.order),
|
|
2437
|
+
hidden: without2(layout.hidden),
|
|
2438
|
+
minimized: without2(layout.minimized),
|
|
2439
|
+
...layout.pages === void 0 ? {} : {
|
|
2440
|
+
pages: layout.pages.map((page) => ({
|
|
2441
|
+
...page,
|
|
2442
|
+
order: without2(page.order),
|
|
2443
|
+
hidden: without2(page.hidden),
|
|
2444
|
+
minimized: without2(page.minimized)
|
|
2445
|
+
}))
|
|
2446
|
+
},
|
|
2447
|
+
...Object.keys(otherSettings).length === 0 ? {} : { settings: otherSettings },
|
|
2448
|
+
...remaining.length === 0 ? {} : { clones: remaining }
|
|
2449
|
+
};
|
|
2179
2450
|
}
|
|
2180
2451
|
|
|
2181
|
-
// src/core/
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
if (
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
widgetKey: definition.key,
|
|
2189
|
-
expectedKind: definition.kind
|
|
2190
|
-
});
|
|
2191
|
-
if (!result.ok) {
|
|
2192
|
-
return { definition, status: "error", error: result.error };
|
|
2193
|
-
}
|
|
2194
|
-
checked = result.value;
|
|
2195
|
-
}
|
|
2196
|
-
const emptyText = emptyTextFor(checked);
|
|
2197
|
-
if (emptyText !== null) {
|
|
2198
|
-
return { definition, status: "empty", emptyText };
|
|
2452
|
+
// src/core/validate.ts
|
|
2453
|
+
var MAX_REPORTED_PROBLEMS = 10;
|
|
2454
|
+
function describeType2(value) {
|
|
2455
|
+
if (value === null) return "null";
|
|
2456
|
+
if (Array.isArray(value)) return "array";
|
|
2457
|
+
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
2458
|
+
return String(value);
|
|
2199
2459
|
}
|
|
2200
|
-
return
|
|
2460
|
+
return typeof value;
|
|
2201
2461
|
}
|
|
2202
|
-
function
|
|
2203
|
-
return
|
|
2462
|
+
function isRecord3(value) {
|
|
2463
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2204
2464
|
}
|
|
2205
|
-
function
|
|
2206
|
-
return
|
|
2465
|
+
function join(path, field) {
|
|
2466
|
+
return path === "" ? field : `${path}.${field}`;
|
|
2207
2467
|
}
|
|
2208
|
-
function
|
|
2209
|
-
const
|
|
2210
|
-
|
|
2468
|
+
function requireString(record, field, path, problems) {
|
|
2469
|
+
const value = record[field];
|
|
2470
|
+
if (typeof value !== "string") {
|
|
2471
|
+
problems.push(
|
|
2472
|
+
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
2473
|
+
);
|
|
2474
|
+
}
|
|
2211
2475
|
}
|
|
2212
|
-
function
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
options
|
|
2217
|
-
);
|
|
2476
|
+
function optionalString(record, field, path, problems) {
|
|
2477
|
+
if (record[field] !== void 0) {
|
|
2478
|
+
requireString(record, field, path, problems);
|
|
2479
|
+
}
|
|
2218
2480
|
}
|
|
2219
|
-
function
|
|
2220
|
-
|
|
2221
|
-
if (
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
widget.data,
|
|
2225
|
-
values
|
|
2481
|
+
function requireNumber(record, field, path, problems) {
|
|
2482
|
+
const value = record[field];
|
|
2483
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2484
|
+
problems.push(
|
|
2485
|
+
`${join(path, field)} must be a finite number, got ${describeType2(value)}.`
|
|
2226
2486
|
);
|
|
2227
|
-
return { ...widget, data, options: values };
|
|
2228
2487
|
}
|
|
2229
|
-
return { ...widget, options: values };
|
|
2230
2488
|
}
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
return finalizeWidget(
|
|
2236
|
-
await resolveWidgetUnapplied(definition, provider, context, options),
|
|
2237
|
-
values
|
|
2238
|
-
);
|
|
2489
|
+
function optionalNumber(record, field, path, problems) {
|
|
2490
|
+
if (record[field] !== void 0) {
|
|
2491
|
+
requireNumber(record, field, path, problems);
|
|
2492
|
+
}
|
|
2239
2493
|
}
|
|
2240
|
-
|
|
2241
|
-
const
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
definition,
|
|
2246
|
-
`Widget ${quotedKey}: no provider is registered for this key.`
|
|
2494
|
+
function optionalSortValue(record, path, problems) {
|
|
2495
|
+
const value = record["value"];
|
|
2496
|
+
if (value !== void 0 && typeof value !== "string" && !(typeof value === "number" && Number.isFinite(value))) {
|
|
2497
|
+
problems.push(
|
|
2498
|
+
`${path}.value must be a string or a finite number, got ${describeType2(value)}.`
|
|
2247
2499
|
);
|
|
2248
2500
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2501
|
+
}
|
|
2502
|
+
function optionalBoolean(record, field, path, problems) {
|
|
2503
|
+
const value = record[field];
|
|
2504
|
+
if (value !== void 0 && typeof value !== "boolean") {
|
|
2505
|
+
problems.push(
|
|
2506
|
+
`${join(path, field)} must be a boolean, got ${describeType2(value)}.`
|
|
2253
2507
|
);
|
|
2254
2508
|
}
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
controller.abort(new Error(`Timed out after ${timeoutMs} ms.`));
|
|
2263
|
-
}, timeoutMs);
|
|
2264
|
-
const interrupted = new Promise((_resolve, reject) => {
|
|
2265
|
-
controller.signal.addEventListener(
|
|
2266
|
-
"abort",
|
|
2267
|
-
() => reject(new LoadInterruption()),
|
|
2268
|
-
{ once: true }
|
|
2509
|
+
}
|
|
2510
|
+
function optionalUrl(record, field, path, problems, isSafe, allowed) {
|
|
2511
|
+
const value = record[field];
|
|
2512
|
+
if (value === void 0) return;
|
|
2513
|
+
if (typeof value !== "string") {
|
|
2514
|
+
problems.push(
|
|
2515
|
+
`${join(path, field)} must be a string, got ${describeType2(value)}.`
|
|
2269
2516
|
);
|
|
2270
|
-
})
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
payload = await Promise.race([
|
|
2274
|
-
Promise.resolve().then(() => {
|
|
2275
|
-
if (controller.signal.aborted) throw new LoadInterruption();
|
|
2276
|
-
return provider(context, definition, {
|
|
2277
|
-
signal: controller.signal,
|
|
2278
|
-
options: values
|
|
2279
|
-
});
|
|
2280
|
-
}),
|
|
2281
|
-
interrupted
|
|
2282
|
-
]);
|
|
2283
|
-
} catch (cause) {
|
|
2284
|
-
if (cause instanceof LoadInterruption) {
|
|
2285
|
-
return failedWidget(
|
|
2286
|
-
definition,
|
|
2287
|
-
timedOut ? `Widget ${quotedKey}: provider timed out after ${timeoutMs} ms.` : `Widget ${quotedKey}: loading was canceled.`
|
|
2288
|
-
);
|
|
2289
|
-
}
|
|
2290
|
-
return failedWidget(
|
|
2291
|
-
definition,
|
|
2292
|
-
`Widget ${quotedKey}: provider failed: ${describeUnknownError(cause)}`
|
|
2517
|
+
} else if (!isSafe(value)) {
|
|
2518
|
+
problems.push(
|
|
2519
|
+
`${join(path, field)} must be ${allowed}, got ${JSON.stringify(value.slice(0, 80))}.`
|
|
2293
2520
|
);
|
|
2294
|
-
} finally {
|
|
2295
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
2296
|
-
options.signal?.removeEventListener("abort", onExternalAbort);
|
|
2297
2521
|
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
const
|
|
2301
|
-
if (
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
});
|
|
2307
|
-
} catch {
|
|
2308
|
-
}
|
|
2522
|
+
}
|
|
2523
|
+
function requireArray(record, field, path, problems) {
|
|
2524
|
+
const value = record[field];
|
|
2525
|
+
if (!Array.isArray(value)) {
|
|
2526
|
+
problems.push(
|
|
2527
|
+
`${join(path, field)} must be an array, got ${describeType2(value)}.`
|
|
2528
|
+
);
|
|
2529
|
+
return null;
|
|
2309
2530
|
}
|
|
2310
|
-
return
|
|
2531
|
+
return value;
|
|
2311
2532
|
}
|
|
2312
|
-
function
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2533
|
+
function requireOneOf(record, field, options, path, problems) {
|
|
2534
|
+
const value = record[field];
|
|
2535
|
+
if (typeof value !== "string" || !options.includes(value)) {
|
|
2536
|
+
problems.push(
|
|
2537
|
+
`${join(path, field)} must be one of ${options.map((o) => `"${o}"`).join(", ")}, got ${typeof value === "string" ? JSON.stringify(value) : describeType2(value)}.`
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2316
2540
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
(
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
);
|
|
2541
|
+
function eachRecord(items, path, problems, check) {
|
|
2542
|
+
items.forEach((item, index) => {
|
|
2543
|
+
const itemPath = `${path}[${index}]`;
|
|
2544
|
+
if (isRecord3(item)) {
|
|
2545
|
+
check(item, itemPath);
|
|
2546
|
+
} else {
|
|
2547
|
+
problems.push(
|
|
2548
|
+
`${itemPath} must be an object, got ${describeType2(item)}.`
|
|
2549
|
+
);
|
|
2550
|
+
}
|
|
2551
|
+
});
|
|
2329
2552
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
function
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
(
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
}
|
|
2343
|
-
const unapplied = /* @__PURE__ */ new Map();
|
|
2344
|
-
const visible = widgetsFor(definitions, context.roles);
|
|
2345
|
-
let snapshot = loadingWidgets(visible);
|
|
2346
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
2347
|
-
const started = /* @__PURE__ */ new Set();
|
|
2348
|
-
const generations = /* @__PURE__ */ new Map();
|
|
2349
|
-
const inFlight = /* @__PURE__ */ new Map();
|
|
2350
|
-
let disposed = false;
|
|
2351
|
-
function publish(next) {
|
|
2352
|
-
snapshot = next;
|
|
2353
|
-
for (const listener of [...listeners]) {
|
|
2354
|
-
try {
|
|
2355
|
-
listener();
|
|
2356
|
-
} catch {
|
|
2553
|
+
var HREF_RULE = "an http(s), mailto or relative URL";
|
|
2554
|
+
var IMAGE_RULE = "an http(s) or relative URL";
|
|
2555
|
+
function checkPayloadFields(kind, data, problems) {
|
|
2556
|
+
switch (kind) {
|
|
2557
|
+
case "TEXT":
|
|
2558
|
+
requireString(data, "value", "", problems);
|
|
2559
|
+
requireString(data, "label", "", problems);
|
|
2560
|
+
return;
|
|
2561
|
+
case "KPI":
|
|
2562
|
+
requireNumber(data, "value", "", problems);
|
|
2563
|
+
if (data["previous"] !== null) {
|
|
2564
|
+
optionalNumber(data, "previous", "", problems);
|
|
2357
2565
|
}
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
"
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
cacheKeyFor(definition, runOptions, values)
|
|
2390
|
-
);
|
|
2391
|
-
if (entry !== void 0 && isCurrent()) {
|
|
2392
|
-
const cached = resolvePayload(definition, entry.payload, runOptions);
|
|
2393
|
-
if (cached.status === "ok") {
|
|
2394
|
-
const stale = { ...cached, updatedAt: entry.storedAt, stale: true };
|
|
2395
|
-
unapplied.set(key, stale);
|
|
2396
|
-
replace(key, finalizeWidget(stale, values));
|
|
2566
|
+
requireOneOf(data, "format", VALUE_FORMATS, "", problems);
|
|
2567
|
+
optionalString(data, "currency", "", problems);
|
|
2568
|
+
requireString(data, "label", "", problems);
|
|
2569
|
+
optionalString(data, "hint", "", problems);
|
|
2570
|
+
optionalBoolean(data, "higherIsBetter", "", problems);
|
|
2571
|
+
return;
|
|
2572
|
+
case "GAUGE":
|
|
2573
|
+
requireNumber(data, "value", "", problems);
|
|
2574
|
+
requireNumber(data, "max", "", problems);
|
|
2575
|
+
requireString(data, "label", "", problems);
|
|
2576
|
+
if (typeof data["max"] === "number" && data["max"] <= 0) {
|
|
2577
|
+
problems.push(`max must be greater than 0, got ${data["max"]}.`);
|
|
2578
|
+
}
|
|
2579
|
+
return;
|
|
2580
|
+
case "TABLE": {
|
|
2581
|
+
const columns = requireArray(data, "columns", "", problems);
|
|
2582
|
+
if (columns !== null) {
|
|
2583
|
+
eachRecord(columns, "columns", problems, (column, path) => {
|
|
2584
|
+
requireString(column, "label", path, problems);
|
|
2585
|
+
optionalBoolean(column, "numeric", path, problems);
|
|
2586
|
+
});
|
|
2587
|
+
}
|
|
2588
|
+
const rows = requireArray(data, "rows", "", problems);
|
|
2589
|
+
if (rows !== null) {
|
|
2590
|
+
rows.forEach((row, rowIndex) => {
|
|
2591
|
+
const rowPath = `rows[${rowIndex}]`;
|
|
2592
|
+
if (!Array.isArray(row)) {
|
|
2593
|
+
problems.push(
|
|
2594
|
+
`${rowPath} must be an array of cells, got ${describeType2(row)}.`
|
|
2595
|
+
);
|
|
2596
|
+
return;
|
|
2397
2597
|
}
|
|
2398
|
-
|
|
2399
|
-
|
|
2598
|
+
if (columns !== null && row.length !== columns.length) {
|
|
2599
|
+
problems.push(
|
|
2600
|
+
`${rowPath} has ${row.length} cell(s) but there are ${columns.length} column(s).`
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
eachRecord(row, rowPath, problems, (cell, cellPath) => {
|
|
2604
|
+
requireString(cell, "text", cellPath, problems);
|
|
2605
|
+
optionalSortValue(cell, cellPath, problems);
|
|
2606
|
+
optionalUrl(
|
|
2607
|
+
cell,
|
|
2608
|
+
"href",
|
|
2609
|
+
cellPath,
|
|
2610
|
+
problems,
|
|
2611
|
+
isSafeHref,
|
|
2612
|
+
HREF_RULE
|
|
2613
|
+
);
|
|
2614
|
+
});
|
|
2615
|
+
});
|
|
2400
2616
|
}
|
|
2617
|
+
optionalString(data, "footer", "", problems);
|
|
2618
|
+
return;
|
|
2401
2619
|
}
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
if (result.status === "ok") unapplied.set(key, result);
|
|
2411
|
-
else unapplied.delete(key);
|
|
2412
|
-
replace(key, finalizeWidget(result, values));
|
|
2413
|
-
}
|
|
2414
|
-
function start(definition, readCache) {
|
|
2415
|
-
started.add(definition.key);
|
|
2416
|
-
void run(definition, readCache);
|
|
2417
|
-
}
|
|
2418
|
-
function definitionFor(key) {
|
|
2419
|
-
return visible.find((definition) => definition.key === key);
|
|
2420
|
-
}
|
|
2421
|
-
function load(key) {
|
|
2422
|
-
if (disposed || started.has(key)) return;
|
|
2423
|
-
const definition = definitionFor(key);
|
|
2424
|
-
if (definition !== void 0) start(definition, true);
|
|
2425
|
-
}
|
|
2426
|
-
function loadAll() {
|
|
2427
|
-
for (const definition of visible) load(definition.key);
|
|
2428
|
-
}
|
|
2429
|
-
function refresh(key, refreshOptions) {
|
|
2430
|
-
if (disposed) return;
|
|
2431
|
-
const keys = key === void 0 ? [...started] : [key];
|
|
2432
|
-
for (const target of keys) {
|
|
2433
|
-
const definition = definitionFor(target);
|
|
2434
|
-
if (definition === void 0) continue;
|
|
2435
|
-
if (refreshOptions?.skipInFlight === true && inFlight.has(target)) {
|
|
2436
|
-
continue;
|
|
2437
|
-
}
|
|
2438
|
-
const current = snapshot.find(
|
|
2439
|
-
(widget) => widget.definition.key === target
|
|
2440
|
-
);
|
|
2441
|
-
if (current?.status === "error") {
|
|
2442
|
-
replace(target, { definition, status: "loading" });
|
|
2620
|
+
case "BAR_LIST": {
|
|
2621
|
+
const items = requireArray(data, "items", "", problems);
|
|
2622
|
+
if (items !== null) {
|
|
2623
|
+
eachRecord(items, "items", problems, (item, path) => {
|
|
2624
|
+
requireString(item, "label", path, problems);
|
|
2625
|
+
requireNumber(item, "value", path, problems);
|
|
2626
|
+
optionalString(item, "display", path, problems);
|
|
2627
|
+
});
|
|
2443
2628
|
}
|
|
2444
|
-
|
|
2629
|
+
optionalNumber(data, "total", "", problems);
|
|
2630
|
+
return;
|
|
2445
2631
|
}
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2632
|
+
case "ALERT_LIST": {
|
|
2633
|
+
const items = requireArray(data, "items", "", problems);
|
|
2634
|
+
if (items !== null) {
|
|
2635
|
+
eachRecord(items, "items", problems, (item, path) => {
|
|
2636
|
+
requireString(item, "title", path, problems);
|
|
2637
|
+
optionalUrl(item, "href", path, problems, isSafeHref, HREF_RULE);
|
|
2638
|
+
optionalUrl(
|
|
2639
|
+
item,
|
|
2640
|
+
"thumbnailUrl",
|
|
2641
|
+
path,
|
|
2642
|
+
problems,
|
|
2643
|
+
isSafeImageUrl,
|
|
2644
|
+
IMAGE_RULE
|
|
2645
|
+
);
|
|
2646
|
+
requireString(item, "valueLabel", path, problems);
|
|
2647
|
+
optionalString(item, "detail", path, problems);
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
requireNumber(data, "total", "", problems);
|
|
2651
|
+
requireString(data, "emptyText", "", problems);
|
|
2652
|
+
if (items !== null && typeof data["total"] === "number" && data["total"] < items.length) {
|
|
2653
|
+
problems.push(
|
|
2654
|
+
`total (${data["total"]}) must be at least the number of items (${items.length}).`
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2459
2657
|
return;
|
|
2460
2658
|
}
|
|
2461
|
-
|
|
2462
|
-
(
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2659
|
+
case "GRAPH": {
|
|
2660
|
+
const series = requireArray(data, "series", "", problems);
|
|
2661
|
+
if (series !== null) {
|
|
2662
|
+
if (series.length > MAX_GRAPH_SERIES) {
|
|
2663
|
+
problems.push(
|
|
2664
|
+
`series has ${series.length} entries; at most ${MAX_GRAPH_SERIES} are allowed (fold the rest into "Other").`
|
|
2665
|
+
);
|
|
2666
|
+
}
|
|
2667
|
+
eachRecord(series, "series", problems, (entry, path) => {
|
|
2668
|
+
requireString(entry, "name", path, problems);
|
|
2669
|
+
const points = requireArray(entry, "points", path, problems);
|
|
2670
|
+
if (points !== null) {
|
|
2671
|
+
eachRecord(
|
|
2672
|
+
points,
|
|
2673
|
+
`${path}.points`,
|
|
2674
|
+
problems,
|
|
2675
|
+
(point, pointPath) => {
|
|
2676
|
+
requireString(point, "label", pointPath, problems);
|
|
2677
|
+
requireNumber(point, "value", pointPath, problems);
|
|
2678
|
+
}
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
requireOneOf(data, "chartType", ["bar", "line"], "", problems);
|
|
2684
|
+
requireOneOf(data, "valueFormat", VALUE_FORMATS, "", problems);
|
|
2685
|
+
optionalString(data, "currency", "", problems);
|
|
2686
|
+
optionalString(data, "xLabel", "", problems);
|
|
2687
|
+
return;
|
|
2467
2688
|
}
|
|
2468
|
-
start(definition, true);
|
|
2469
2689
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2690
|
+
}
|
|
2691
|
+
function checkDetailOption(detail, problems) {
|
|
2692
|
+
if (detail === void 0 || typeof detail === "boolean") return;
|
|
2693
|
+
if (!isRecord3(detail)) {
|
|
2694
|
+
problems.push(
|
|
2695
|
+
`detail must be true, false or an options object, got ${describeType2(detail)}.`
|
|
2696
|
+
);
|
|
2697
|
+
return;
|
|
2474
2698
|
}
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2699
|
+
optionalString(detail, "title", "detail", problems);
|
|
2700
|
+
const pageSize = detail["pageSize"];
|
|
2701
|
+
if (pageSize !== void 0 && !(typeof pageSize === "number" && Number.isInteger(pageSize) && pageSize >= 1 && pageSize <= MAX_DETAIL_PAGE_SIZE)) {
|
|
2702
|
+
problems.push(
|
|
2703
|
+
`detail.pageSize must be a whole number from 1 to ${MAX_DETAIL_PAGE_SIZE}, got ${typeof pageSize === "number" ? String(pageSize) : describeType2(pageSize)}.`
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2706
|
+
if (detail["mode"] !== void 0) {
|
|
2707
|
+
requireOneOf(detail, "mode", DETAIL_MODES, "detail", problems);
|
|
2480
2708
|
}
|
|
2481
|
-
return {
|
|
2482
|
-
getSnapshot: () => snapshot,
|
|
2483
|
-
subscribe,
|
|
2484
|
-
load,
|
|
2485
|
-
loadAll,
|
|
2486
|
-
refresh,
|
|
2487
|
-
setOptions,
|
|
2488
|
-
dispose
|
|
2489
|
-
};
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
// src/core/pages.ts
|
|
2493
|
-
var DEFAULT_PAGE_KEY = "default";
|
|
2494
|
-
var DEFAULT_PAGE_ROWS = 4;
|
|
2495
|
-
function rowsAllowed(page, options) {
|
|
2496
|
-
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
2497
|
-
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
2498
2709
|
}
|
|
2499
|
-
function
|
|
2500
|
-
|
|
2710
|
+
function checkTableControlsOption(controls, kind, problems) {
|
|
2711
|
+
if (controls === void 0 || controls === false) return;
|
|
2712
|
+
if (typeof controls !== "boolean" && !isRecord3(controls)) {
|
|
2713
|
+
problems.push(
|
|
2714
|
+
`tableControls must be true, false or an options object, got ${describeType2(controls)}.`
|
|
2715
|
+
);
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
if (isRecord3(controls)) {
|
|
2719
|
+
optionalBoolean(controls, "search", "tableControls", problems);
|
|
2720
|
+
optionalBoolean(controls, "sort", "tableControls", problems);
|
|
2721
|
+
}
|
|
2722
|
+
if (typeof kind === "string" && kind !== "TABLE") {
|
|
2723
|
+
problems.push(
|
|
2724
|
+
`tableControls only applies to TABLE widgets, but kind is "${kind}".`
|
|
2725
|
+
);
|
|
2726
|
+
}
|
|
2501
2727
|
}
|
|
2502
|
-
function
|
|
2503
|
-
|
|
2504
|
-
|
|
2728
|
+
function checkWidthLimits(value, problems) {
|
|
2729
|
+
for (const field of ["minWidth", "maxWidth"]) {
|
|
2730
|
+
const limit = value[field];
|
|
2731
|
+
if (limit !== void 0 && !isWidgetWidth(limit)) {
|
|
2732
|
+
problems.push(
|
|
2733
|
+
`${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)}.`
|
|
2734
|
+
);
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2505
2737
|
}
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2738
|
+
const { minWidth, maxWidth, width } = value;
|
|
2739
|
+
if (isWidgetWidth(minWidth) && isWidgetWidth(maxWidth) && minWidth > maxWidth) {
|
|
2740
|
+
problems.push(
|
|
2741
|
+
`minWidth (${minWidth}) must not be more than maxWidth (${maxWidth}).`
|
|
2742
|
+
);
|
|
2743
|
+
return;
|
|
2744
|
+
}
|
|
2745
|
+
if (isWidgetWidth(width)) {
|
|
2746
|
+
if (isWidgetWidth(minWidth) && width < minWidth) {
|
|
2747
|
+
problems.push(
|
|
2748
|
+
`width (${width}) must not be less than minWidth (${minWidth}).`
|
|
2749
|
+
);
|
|
2513
2750
|
}
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
order: [],
|
|
2521
|
-
hidden: [],
|
|
2522
|
-
minimized: [],
|
|
2523
|
-
pages: pageList(layout)
|
|
2524
|
-
};
|
|
2751
|
+
if (isWidgetWidth(maxWidth) && width > maxWidth) {
|
|
2752
|
+
problems.push(
|
|
2753
|
+
`width (${width}) must not be more than maxWidth (${maxWidth}).`
|
|
2754
|
+
);
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2525
2757
|
}
|
|
2526
|
-
function
|
|
2527
|
-
|
|
2758
|
+
function checkLockedOption(locked, problems) {
|
|
2759
|
+
if (locked === void 0 || typeof locked === "boolean") return;
|
|
2760
|
+
if (!isRecord3(locked)) {
|
|
2761
|
+
problems.push(
|
|
2762
|
+
`locked must be true, false or an object, got ${describeType2(locked)}.`
|
|
2763
|
+
);
|
|
2764
|
+
return;
|
|
2765
|
+
}
|
|
2766
|
+
optionalBoolean(locked, "move", "locked", problems);
|
|
2767
|
+
optionalBoolean(locked, "hide", "locked", problems);
|
|
2768
|
+
optionalBoolean(locked, "minimize", "locked", problems);
|
|
2769
|
+
optionalBoolean(locked, "options", "locked", problems);
|
|
2770
|
+
optionalBoolean(locked, "clone", "locked", problems);
|
|
2528
2771
|
}
|
|
2529
|
-
function
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2772
|
+
function checkOptionColumns(option, path, kind, problems) {
|
|
2773
|
+
const columns = option["columns"];
|
|
2774
|
+
if (!Array.isArray(columns) || columns.length === 0) {
|
|
2775
|
+
problems.push(
|
|
2776
|
+
`${join(path, "columns")} must be a non-empty array of { key, label }, got ${describeType2(columns)}.`
|
|
2777
|
+
);
|
|
2778
|
+
return [];
|
|
2779
|
+
}
|
|
2780
|
+
const keys = [];
|
|
2781
|
+
columns.forEach((entry, index) => {
|
|
2782
|
+
const entryPath = `${path}.columns[${index}]`;
|
|
2783
|
+
if (!isRecord3(entry)) {
|
|
2784
|
+
problems.push(
|
|
2785
|
+
`${entryPath} must be an object, got ${describeType2(entry)}.`
|
|
2786
|
+
);
|
|
2787
|
+
return;
|
|
2788
|
+
}
|
|
2789
|
+
requireString(entry, "label", entryPath, problems);
|
|
2790
|
+
const key = entry["key"];
|
|
2791
|
+
if (typeof key !== "string") {
|
|
2792
|
+
problems.push(
|
|
2793
|
+
`${entryPath}.key must be a string, got ${describeType2(key)}.`
|
|
2794
|
+
);
|
|
2795
|
+
} else if (typeof kind === "string" && !isColumnKeyFor(kind, key)) {
|
|
2796
|
+
problems.push(
|
|
2797
|
+
`${entryPath}.key "${key}" is not a column of a ${kind} widget (${kind === "TABLE" ? '"c0", "c1", ...' : '"label" or "value"'}).`
|
|
2798
|
+
);
|
|
2799
|
+
} else if (keys.includes(key)) {
|
|
2800
|
+
problems.push(`${entryPath}.key "${key}" is used twice.`);
|
|
2801
|
+
} else {
|
|
2802
|
+
keys.push(key);
|
|
2803
|
+
}
|
|
2804
|
+
});
|
|
2805
|
+
return keys;
|
|
2543
2806
|
}
|
|
2544
|
-
function
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
return
|
|
2807
|
+
function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
2808
|
+
if (!isRecord3(option)) {
|
|
2809
|
+
problems.push(`${path} must be an object, got ${describeType2(option)}.`);
|
|
2810
|
+
return;
|
|
2548
2811
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
hidden: next.hidden,
|
|
2554
|
-
minimized: next.minimized
|
|
2555
|
-
};
|
|
2812
|
+
const type = option["type"];
|
|
2813
|
+
if (typeof type !== "string" || !WIDGET_OPTION_TYPES.includes(type)) {
|
|
2814
|
+
requireOneOf(option, "type", WIDGET_OPTION_TYPES, path, problems);
|
|
2815
|
+
return;
|
|
2556
2816
|
}
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
(
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2817
|
+
const key = option["key"];
|
|
2818
|
+
if (typeof key !== "string" || !OPTION_KEY_PATTERN.test(key)) {
|
|
2819
|
+
problems.push(
|
|
2820
|
+
`${path}.key must be letters, digits, "_" or "-", starting with a letter (40 characters at most), got ${typeof key === "string" ? JSON.stringify(key) : describeType2(key)}.`
|
|
2821
|
+
);
|
|
2822
|
+
} else if (seenKeys.has(key)) {
|
|
2823
|
+
problems.push(`${path}.key "${key}" is used by another option.`);
|
|
2824
|
+
} else {
|
|
2825
|
+
seenKeys.add(key);
|
|
2826
|
+
}
|
|
2827
|
+
requireString(option, "label", path, problems);
|
|
2828
|
+
const fallback = option["default"];
|
|
2829
|
+
switch (type) {
|
|
2830
|
+
case "choice": {
|
|
2831
|
+
const choices = option["choices"];
|
|
2832
|
+
const values = [];
|
|
2833
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
2834
|
+
problems.push(
|
|
2835
|
+
`${path}.choices must be a non-empty array of { value, label }, got ${describeType2(choices)}.`
|
|
2836
|
+
);
|
|
2837
|
+
} else {
|
|
2838
|
+
choices.forEach((choice, index) => {
|
|
2839
|
+
const choicePath = `${path}.choices[${index}]`;
|
|
2840
|
+
if (!isRecord3(choice)) {
|
|
2841
|
+
problems.push(
|
|
2842
|
+
`${choicePath} must be an object, got ${describeType2(choice)}.`
|
|
2843
|
+
);
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
requireString(choice, "label", choicePath, problems);
|
|
2847
|
+
const value = choice["value"];
|
|
2848
|
+
if (typeof value !== "string" || value === "") {
|
|
2849
|
+
problems.push(
|
|
2850
|
+
`${choicePath}.value must be a non-empty string, got ${describeType2(value)}.`
|
|
2851
|
+
);
|
|
2852
|
+
} else if (values.includes(value)) {
|
|
2853
|
+
problems.push(`${choicePath}.value "${value}" is used twice.`);
|
|
2854
|
+
} else {
|
|
2855
|
+
values.push(value);
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
if (values.length > 0)
|
|
2860
|
+
requireOneOf(option, "default", values, path, problems);
|
|
2861
|
+
break;
|
|
2862
|
+
}
|
|
2863
|
+
case "number": {
|
|
2864
|
+
const { min, max, step } = option;
|
|
2865
|
+
requireNumber(option, "min", path, problems);
|
|
2866
|
+
requireNumber(option, "max", path, problems);
|
|
2867
|
+
if (typeof min === "number" && typeof max === "number" && min > max) {
|
|
2868
|
+
problems.push(
|
|
2869
|
+
`${path}.min (${min}) must not be more than max (${max}).`
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2872
|
+
if (step !== void 0 && !(typeof step === "number" && Number.isFinite(step) && step > 0)) {
|
|
2873
|
+
problems.push(
|
|
2874
|
+
`${path}.step must be a number above 0, got ${describeType2(step)}.`
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
if (typeof fallback !== "number" || !Number.isFinite(fallback)) {
|
|
2878
|
+
problems.push(
|
|
2879
|
+
`${path}.default must be a finite number, got ${describeType2(fallback)}.`
|
|
2880
|
+
);
|
|
2881
|
+
} else if (typeof min === "number" && typeof max === "number" && (fallback < min || fallback > max)) {
|
|
2882
|
+
problems.push(
|
|
2883
|
+
`${path}.default (${fallback}) must be between min (${min}) and max (${max}).`
|
|
2884
|
+
);
|
|
2885
|
+
} else if (typeof min === "number" && typeof step === "number" && step > 0 && Math.abs(
|
|
2886
|
+
(fallback - min) / step - Math.round((fallback - min) / step)
|
|
2887
|
+
) > 1e-9) {
|
|
2888
|
+
problems.push(
|
|
2889
|
+
`${path}.default (${fallback}) must be ${min} plus a whole number of steps of ${step}.`
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
case "boolean":
|
|
2895
|
+
if (typeof fallback !== "boolean") {
|
|
2896
|
+
problems.push(
|
|
2897
|
+
`${path}.default must be a boolean, got ${describeType2(fallback)}.`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
break;
|
|
2901
|
+
case "text": {
|
|
2902
|
+
const maxLength = option["maxLength"];
|
|
2903
|
+
if (typeof maxLength !== "number" || !Number.isInteger(maxLength) || maxLength < 1 || maxLength > MAX_TEXT_OPTION_LENGTH) {
|
|
2904
|
+
problems.push(
|
|
2905
|
+
`${path}.maxLength must be a whole number from 1 to ${MAX_TEXT_OPTION_LENGTH}, got ${typeof maxLength === "number" ? maxLength : describeType2(maxLength)}.`
|
|
2906
|
+
);
|
|
2907
|
+
}
|
|
2908
|
+
if (typeof fallback !== "string") {
|
|
2909
|
+
problems.push(
|
|
2910
|
+
`${path}.default must be a string, got ${describeType2(fallback)}.`
|
|
2911
|
+
);
|
|
2912
|
+
} else if (typeof maxLength === "number" && fallback.length > maxLength) {
|
|
2913
|
+
problems.push(
|
|
2914
|
+
`${path}.default is ${fallback.length} characters; maxLength is ${maxLength}.`
|
|
2915
|
+
);
|
|
2916
|
+
}
|
|
2917
|
+
break;
|
|
2918
|
+
}
|
|
2919
|
+
case "dateRange": {
|
|
2920
|
+
const presets = option["presets"];
|
|
2921
|
+
const offered = [];
|
|
2922
|
+
if (presets !== void 0) {
|
|
2923
|
+
if (!Array.isArray(presets) || presets.length === 0) {
|
|
2924
|
+
problems.push(
|
|
2925
|
+
`${path}.presets must be a non-empty array of { value, label }, got ${describeType2(presets)}.`
|
|
2926
|
+
);
|
|
2927
|
+
} else {
|
|
2928
|
+
presets.forEach((preset, index) => {
|
|
2929
|
+
const presetPath = `${path}.presets[${index}]`;
|
|
2930
|
+
if (!isRecord3(preset)) {
|
|
2931
|
+
problems.push(
|
|
2932
|
+
`${presetPath} must be an object, got ${describeType2(preset)}.`
|
|
2933
|
+
);
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
requireString(preset, "label", presetPath, problems);
|
|
2937
|
+
requireOneOf(
|
|
2938
|
+
preset,
|
|
2939
|
+
"value",
|
|
2940
|
+
DATE_RANGE_PRESETS,
|
|
2941
|
+
presetPath,
|
|
2942
|
+
problems
|
|
2943
|
+
);
|
|
2944
|
+
if (typeof preset["value"] === "string")
|
|
2945
|
+
offered.push(preset["value"]);
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
const allowed = offered.length > 0 ? offered : DATE_RANGE_PRESETS;
|
|
2950
|
+
if (typeof fallback !== "string" || !(allowed.includes(fallback) || isIsoInterval(fallback))) {
|
|
2951
|
+
problems.push(
|
|
2952
|
+
`${path}.default must be one of ${allowed.map((p) => `"${p}"`).join(", ")} or an interval like "2026-01-01/2026-01-31", got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
2953
|
+
);
|
|
2954
|
+
}
|
|
2955
|
+
break;
|
|
2956
|
+
}
|
|
2957
|
+
case "color":
|
|
2958
|
+
if (typeof fallback !== "string" || !/^#[0-9a-fA-F]{6}$/.test(fallback)) {
|
|
2959
|
+
problems.push(
|
|
2960
|
+
`${path}.default must be a color like "#1c5cab", got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
break;
|
|
2964
|
+
case "sort":
|
|
2965
|
+
case "columns": {
|
|
2966
|
+
const supported = type === "sort" ? kind === "TABLE" || kind === "BAR_LIST" : kind === "TABLE";
|
|
2967
|
+
if (!supported && typeof kind === "string") {
|
|
2968
|
+
problems.push(
|
|
2969
|
+
`${path} is a ${type} option, which only applies to ${type === "sort" ? "TABLE and BAR_LIST" : "TABLE"} widgets, but kind is "${kind}".`
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
if (seenTypes.has(type)) {
|
|
2973
|
+
problems.push(
|
|
2974
|
+
`${path} is a second ${type} option; a widget has at most one.`
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
seenTypes.add(type);
|
|
2978
|
+
const columnKeys = checkOptionColumns(
|
|
2979
|
+
option,
|
|
2980
|
+
path,
|
|
2981
|
+
supported ? kind : void 0,
|
|
2982
|
+
problems
|
|
2983
|
+
);
|
|
2984
|
+
if (option["apply"] !== void 0) {
|
|
2985
|
+
requireOneOf(option, "apply", ["provider", "client"], path, problems);
|
|
2986
|
+
}
|
|
2987
|
+
if (type === "sort") {
|
|
2988
|
+
if (fallback !== void 0) {
|
|
2989
|
+
const sort = typeof fallback === "string" ? parseSortValue(fallback) : void 0;
|
|
2990
|
+
if (sort === void 0) {
|
|
2991
|
+
problems.push(
|
|
2992
|
+
`${path}.default must look like "c1:desc" ("column:asc" or "column:desc"), got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
2993
|
+
);
|
|
2994
|
+
} else if (columnKeys.length > 0 && !columnKeys.includes(sort.column)) {
|
|
2995
|
+
requireOneOf(
|
|
2996
|
+
{ default: sort.column },
|
|
2997
|
+
"default",
|
|
2998
|
+
columnKeys,
|
|
2999
|
+
path,
|
|
3000
|
+
problems
|
|
3001
|
+
);
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
} else if (!Array.isArray(fallback) || fallback.length === 0 || fallback.some((k) => typeof k !== "string")) {
|
|
3005
|
+
problems.push(
|
|
3006
|
+
`${path}.default must be a non-empty array of column keys, got ${describeType2(fallback)}.`
|
|
3007
|
+
);
|
|
3008
|
+
} else {
|
|
3009
|
+
const shown = /* @__PURE__ */ new Set();
|
|
3010
|
+
for (const columnKey of fallback) {
|
|
3011
|
+
if (columnKeys.length > 0 && !columnKeys.includes(columnKey)) {
|
|
3012
|
+
problems.push(
|
|
3013
|
+
`${path}.default has "${columnKey}", which is not one of the columns (${columnKeys.map((c) => `"${c}"`).join(", ")}).`
|
|
3014
|
+
);
|
|
3015
|
+
} else if (shown.has(columnKey)) {
|
|
3016
|
+
problems.push(`${path}.default lists "${columnKey}" twice.`);
|
|
3017
|
+
}
|
|
3018
|
+
shown.add(columnKey);
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
break;
|
|
2592
3022
|
}
|
|
2593
|
-
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
2594
3023
|
}
|
|
2595
|
-
return assigned;
|
|
2596
3024
|
}
|
|
2597
|
-
function
|
|
2598
|
-
|
|
2599
|
-
|
|
3025
|
+
function checkOptions(options, kind, problems) {
|
|
3026
|
+
if (options === void 0) return;
|
|
3027
|
+
if (!Array.isArray(options)) {
|
|
3028
|
+
problems.push(`options must be an array, got ${describeType2(options)}.`);
|
|
3029
|
+
return;
|
|
2600
3030
|
}
|
|
2601
|
-
|
|
3031
|
+
if (options.length > MAX_WIDGET_OPTIONS) {
|
|
3032
|
+
problems.push(
|
|
3033
|
+
`options lists ${options.length} options; the most a widget may declare is ${MAX_WIDGET_OPTIONS}.`
|
|
3034
|
+
);
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
3038
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
3039
|
+
options.forEach((option, index) => {
|
|
3040
|
+
checkOption(
|
|
3041
|
+
option,
|
|
3042
|
+
`options[${index}]`,
|
|
3043
|
+
kind,
|
|
3044
|
+
seenKeys,
|
|
3045
|
+
seenTypes,
|
|
3046
|
+
problems
|
|
3047
|
+
);
|
|
3048
|
+
});
|
|
2602
3049
|
}
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
3050
|
+
var MAX_VIEW_COLUMN = 1e3;
|
|
3051
|
+
function checkColumnIndex(value, path, problems) {
|
|
3052
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > MAX_VIEW_COLUMN) {
|
|
3053
|
+
problems.push(
|
|
3054
|
+
`${path} must be a column index (a whole number from 0), got ${typeof value === "number" ? String(value) : describeType2(value)}.`
|
|
3055
|
+
);
|
|
3056
|
+
}
|
|
2607
3057
|
}
|
|
2608
|
-
function
|
|
2609
|
-
|
|
2610
|
-
(
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
)
|
|
2617
|
-
|
|
3058
|
+
function checkConversion(conversion, path, baseKind, viewKind2, problems) {
|
|
3059
|
+
if (!isRecord3(conversion)) {
|
|
3060
|
+
problems.push(
|
|
3061
|
+
`${path} must be an object with a type, got ${describeType2(conversion)}.`
|
|
3062
|
+
);
|
|
3063
|
+
return;
|
|
3064
|
+
}
|
|
3065
|
+
const type = conversion["type"];
|
|
3066
|
+
const known = typeof type === "string" && Object.hasOwn(VIEW_CONVERSIONS, type) ? VIEW_CONVERSIONS[type] : void 0;
|
|
3067
|
+
if (known === void 0) {
|
|
3068
|
+
problems.push(
|
|
3069
|
+
`${path}.type must be one of ${Object.keys(VIEW_CONVERSIONS).map((name) => `"${name}"`).join(", ")}, got ${describeType2(type)}.`
|
|
3070
|
+
);
|
|
3071
|
+
return;
|
|
3072
|
+
}
|
|
3073
|
+
if (baseKind !== known.from) {
|
|
3074
|
+
problems.push(
|
|
3075
|
+
`${path}.type "${String(type)}" reads a ${known.from} widget, but this one is ${String(baseKind)}.`
|
|
3076
|
+
);
|
|
3077
|
+
}
|
|
3078
|
+
if (viewKind2 !== known.to) {
|
|
3079
|
+
problems.push(
|
|
3080
|
+
`${path}.type "${String(type)}" produces ${known.to}, but the view's kind is ${String(viewKind2)}.`
|
|
3081
|
+
);
|
|
3082
|
+
}
|
|
3083
|
+
switch (type) {
|
|
3084
|
+
case "tableToBarList":
|
|
3085
|
+
checkColumnIndex(conversion["label"], `${path}.label`, problems);
|
|
3086
|
+
checkColumnIndex(conversion["value"], `${path}.value`, problems);
|
|
3087
|
+
break;
|
|
3088
|
+
case "tableToGraph": {
|
|
3089
|
+
checkColumnIndex(conversion["label"], `${path}.label`, problems);
|
|
3090
|
+
const values = conversion["values"];
|
|
3091
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
3092
|
+
problems.push(`${path}.values must list 1 or more column indexes.`);
|
|
3093
|
+
} else if (values.length > MAX_GRAPH_SERIES) {
|
|
3094
|
+
problems.push(
|
|
3095
|
+
`${path}.values lists ${values.length} series; a chart carries at most ${MAX_GRAPH_SERIES}.`
|
|
3096
|
+
);
|
|
3097
|
+
} else {
|
|
3098
|
+
values.forEach(
|
|
3099
|
+
(index, at) => checkColumnIndex(index, `${path}.values[${at}]`, problems)
|
|
3100
|
+
);
|
|
3101
|
+
}
|
|
3102
|
+
if (conversion["chartType"] !== "bar" && conversion["chartType"] !== "line") {
|
|
3103
|
+
problems.push(`${path}.chartType must be "bar" or "line".`);
|
|
3104
|
+
}
|
|
3105
|
+
break;
|
|
3106
|
+
}
|
|
3107
|
+
case "barListToGraph":
|
|
3108
|
+
if (conversion["chartType"] !== "bar" && conversion["chartType"] !== "line") {
|
|
3109
|
+
problems.push(`${path}.chartType must be "bar" or "line".`);
|
|
3110
|
+
}
|
|
3111
|
+
break;
|
|
3112
|
+
}
|
|
3113
|
+
if (conversion["valueFormat"] !== void 0 && !VALUE_FORMATS.includes(conversion["valueFormat"])) {
|
|
3114
|
+
problems.push(
|
|
3115
|
+
`${path}.valueFormat must be one of ${VALUE_FORMATS.map((f) => `"${f}"`).join(", ")}.`
|
|
3116
|
+
);
|
|
3117
|
+
}
|
|
2618
3118
|
}
|
|
2619
|
-
function
|
|
2620
|
-
|
|
3119
|
+
function checkViews(value, problems) {
|
|
3120
|
+
const views = value["views"];
|
|
3121
|
+
const defaultView = value["defaultView"];
|
|
3122
|
+
optionalString(value, "baseViewLabel", "", problems);
|
|
3123
|
+
if (value["baseViewLabel"] === "") {
|
|
3124
|
+
problems.push("baseViewLabel must not be empty.");
|
|
3125
|
+
}
|
|
3126
|
+
const declared = /* @__PURE__ */ new Set([BASE_VIEW_KEY]);
|
|
3127
|
+
if (views !== void 0) {
|
|
3128
|
+
if (!Array.isArray(views)) {
|
|
3129
|
+
problems.push(`views must be an array, got ${describeType2(views)}.`);
|
|
3130
|
+
} else if (views.length > MAX_WIDGET_VIEWS) {
|
|
3131
|
+
problems.push(
|
|
3132
|
+
`views lists ${views.length} views; the most a widget may declare is ${MAX_WIDGET_VIEWS}.`
|
|
3133
|
+
);
|
|
3134
|
+
} else {
|
|
3135
|
+
views.forEach((view, index) => {
|
|
3136
|
+
const path = `views[${index}]`;
|
|
3137
|
+
if (!isRecord3(view)) {
|
|
3138
|
+
problems.push(
|
|
3139
|
+
`${path} must be an object, got ${describeType2(view)}.`
|
|
3140
|
+
);
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
const key = view["key"];
|
|
3144
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
3145
|
+
problems.push(`${path}.key must be a non-empty string.`);
|
|
3146
|
+
} else if (declared.has(key)) {
|
|
3147
|
+
problems.push(
|
|
3148
|
+
key === BASE_VIEW_KEY ? `${path}.key "${BASE_VIEW_KEY}" is reserved for the widget's own kind.` : `${path}.key "${key}" is used twice.`
|
|
3149
|
+
);
|
|
3150
|
+
} else {
|
|
3151
|
+
declared.add(key);
|
|
3152
|
+
}
|
|
3153
|
+
if (typeof view["label"] !== "string" || view["label"].trim() === "") {
|
|
3154
|
+
problems.push(`${path}.label must be a non-empty string.`);
|
|
3155
|
+
}
|
|
3156
|
+
requireOneOf(view, "kind", WIDGET_KINDS, path, problems);
|
|
3157
|
+
if (view["convert"] === void 0) {
|
|
3158
|
+
problems.push(
|
|
3159
|
+
`${path}.convert is required: views supplied by a provider are not supported yet.`
|
|
3160
|
+
);
|
|
3161
|
+
} else {
|
|
3162
|
+
checkConversion(
|
|
3163
|
+
view["convert"],
|
|
3164
|
+
`${path}.convert`,
|
|
3165
|
+
value["kind"],
|
|
3166
|
+
view["kind"],
|
|
3167
|
+
problems
|
|
3168
|
+
);
|
|
3169
|
+
}
|
|
3170
|
+
});
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
if (defaultView !== void 0) {
|
|
3174
|
+
if (typeof defaultView !== "string" || !declared.has(defaultView)) {
|
|
3175
|
+
problems.push(
|
|
3176
|
+
`defaultView must be "${BASE_VIEW_KEY}" or the key of a declared view, got ${typeof defaultView === "string" ? JSON.stringify(defaultView) : describeType2(defaultView)}.`
|
|
3177
|
+
);
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
2621
3180
|
}
|
|
2622
|
-
function
|
|
2623
|
-
|
|
3181
|
+
function formatProblems(prefix, problems) {
|
|
3182
|
+
const shown = problems.slice(0, MAX_REPORTED_PROBLEMS);
|
|
3183
|
+
const more = problems.length - shown.length;
|
|
3184
|
+
return `${prefix}: ${shown.join(" ")}${more > 0 ? ` (and ${more} more problem(s))` : ""}`;
|
|
2624
3185
|
}
|
|
2625
|
-
function
|
|
2626
|
-
const
|
|
2627
|
-
if (
|
|
2628
|
-
|
|
3186
|
+
function validateWidgetData(value, options = {}) {
|
|
3187
|
+
const subject = options.widgetKey === void 0 ? "Widget data" : `Widget ${JSON.stringify(options.widgetKey)}`;
|
|
3188
|
+
if (!isRecord3(value)) {
|
|
3189
|
+
return err(`${subject}: must be an object, got ${describeType2(value)}.`);
|
|
3190
|
+
}
|
|
3191
|
+
if (value["empty"] === true) {
|
|
3192
|
+
if (typeof value["text"] !== "string") {
|
|
3193
|
+
return err(
|
|
3194
|
+
`${subject} (empty): text must be a string, got ${describeType2(value["text"])}.`
|
|
3195
|
+
);
|
|
3196
|
+
}
|
|
3197
|
+
return ok(value);
|
|
3198
|
+
}
|
|
3199
|
+
const kind = value["kind"];
|
|
3200
|
+
if (!isWidgetKind(kind)) {
|
|
2629
3201
|
return err(
|
|
2630
|
-
|
|
3202
|
+
`${subject}: kind must be one of ${WIDGET_KINDS.join(", ")} (or the payload must be {"empty": true, "text": ...}), got ${typeof kind === "string" ? JSON.stringify(kind) : describeType2(kind)}.`
|
|
2631
3203
|
);
|
|
2632
3204
|
}
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
if (clash !== void 0) {
|
|
2638
|
-
return err(`Another page is already called "${clash.title}".`);
|
|
3205
|
+
if (options.expectedKind !== void 0 && kind !== options.expectedKind) {
|
|
3206
|
+
return err(
|
|
3207
|
+
`${subject}: expected kind ${options.expectedKind} from its definition, got ${kind}.`
|
|
3208
|
+
);
|
|
2639
3209
|
}
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
return `page-${n}`;
|
|
2647
|
-
}
|
|
2648
|
-
function nextPageTitle(layout) {
|
|
2649
|
-
const used = new Set(
|
|
2650
|
-
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
2651
|
-
);
|
|
2652
|
-
let n = pageList(layout).length + 1;
|
|
2653
|
-
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
2654
|
-
return `Page ${n}`;
|
|
3210
|
+
const problems = [];
|
|
3211
|
+
checkPayloadFields(kind, value, problems);
|
|
3212
|
+
if (problems.length > 0) {
|
|
3213
|
+
return err(formatProblems(`${subject} (${kind})`, problems));
|
|
3214
|
+
}
|
|
3215
|
+
return ok(value);
|
|
2655
3216
|
}
|
|
2656
|
-
function
|
|
2657
|
-
if (
|
|
3217
|
+
function validateWidgetDefinition(value) {
|
|
3218
|
+
if (!isRecord3(value)) {
|
|
2658
3219
|
return err(
|
|
2659
|
-
`
|
|
3220
|
+
`Widget definition: must be an object, got ${describeType2(value)}.`
|
|
2660
3221
|
);
|
|
2661
3222
|
}
|
|
2662
|
-
const
|
|
2663
|
-
|
|
2664
|
-
|
|
3223
|
+
const subject = typeof value["key"] === "string" ? `Widget definition ${JSON.stringify(value["key"])}` : "Widget definition";
|
|
3224
|
+
const problems = [];
|
|
3225
|
+
requireString(value, "key", "", problems);
|
|
3226
|
+
if (value["key"] === "") {
|
|
3227
|
+
problems.push("key must not be empty.");
|
|
2665
3228
|
}
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
);
|
|
2670
|
-
if (
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
3229
|
+
requireString(value, "title", "", problems);
|
|
3230
|
+
optionalString(value, "description", "", problems);
|
|
3231
|
+
requireOneOf(value, "kind", WIDGET_KINDS, "", problems);
|
|
3232
|
+
optionalNumber(value, "sortOrder", "", problems);
|
|
3233
|
+
if (value["roles"] !== void 0) {
|
|
3234
|
+
const roles = requireArray(value, "roles", "", problems);
|
|
3235
|
+
roles?.forEach((role, index) => {
|
|
3236
|
+
if (typeof role !== "string") {
|
|
3237
|
+
problems.push(
|
|
3238
|
+
`roles[${index}] must be a string, got ${describeType2(role)}.`
|
|
3239
|
+
);
|
|
3240
|
+
}
|
|
3241
|
+
});
|
|
3242
|
+
}
|
|
3243
|
+
optionalBoolean(value, "active", "", problems);
|
|
3244
|
+
if (value["defaultSize"] !== void 0) {
|
|
3245
|
+
requireOneOf(value, "defaultSize", WIDGET_SIZES, "", problems);
|
|
3246
|
+
}
|
|
3247
|
+
if (value["width"] !== void 0 && !isWidgetWidth(value["width"])) {
|
|
3248
|
+
problems.push(
|
|
3249
|
+
`width must be an integer from ${MIN_WIDGET_WIDTH} to ${MAX_WIDGET_WIDTH} (twelfths of the row), got ${typeof value["width"] === "number" ? String(value["width"]) : describeType2(value["width"])}.`
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
checkWidthLimits(value, problems);
|
|
3253
|
+
checkDetailOption(value["detail"], problems);
|
|
3254
|
+
checkTableControlsOption(value["tableControls"], value["kind"], problems);
|
|
3255
|
+
checkLockedOption(value["locked"], problems);
|
|
3256
|
+
checkOptions(value["options"], value["kind"], problems);
|
|
3257
|
+
checkViews(value, problems);
|
|
3258
|
+
if (value["page"] !== void 0) {
|
|
3259
|
+
if (typeof value["page"] !== "string" || value["page"].trim() === "") {
|
|
3260
|
+
problems.push(
|
|
3261
|
+
`page must be a non-empty page key or title, got ${describeType2(value["page"])}.`
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
if (value["fill"] !== void 0) {
|
|
3266
|
+
requireOneOf(value, "fill", WIDGET_FILLS, "", problems);
|
|
3267
|
+
}
|
|
3268
|
+
if (problems.length > 0) {
|
|
3269
|
+
return err(formatProblems(subject, problems));
|
|
3270
|
+
}
|
|
3271
|
+
return ok(value);
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
// src/core/resolve.ts
|
|
3275
|
+
function resolvePayload(definition, payload, options = {}) {
|
|
3276
|
+
let checked;
|
|
3277
|
+
if (options.validate === false) {
|
|
3278
|
+
checked = payload;
|
|
3279
|
+
} else {
|
|
3280
|
+
const result = validateWidgetData(payload, {
|
|
3281
|
+
widgetKey: definition.key,
|
|
3282
|
+
expectedKind: definition.kind
|
|
3283
|
+
});
|
|
3284
|
+
if (!result.ok) {
|
|
3285
|
+
return { definition, status: "error", error: result.error };
|
|
3286
|
+
}
|
|
3287
|
+
checked = result.value;
|
|
3288
|
+
}
|
|
3289
|
+
const emptyText = emptyTextFor(checked);
|
|
3290
|
+
if (emptyText !== null) {
|
|
3291
|
+
return { definition, status: "empty", emptyText };
|
|
3292
|
+
}
|
|
3293
|
+
return { definition, status: "ok", data: checked };
|
|
2680
3294
|
}
|
|
2681
|
-
function
|
|
2682
|
-
|
|
2683
|
-
(p) => p.key === pageKey
|
|
2684
|
-
);
|
|
2685
|
-
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2686
|
-
const checked = checkTitle(layout, title, pageKey);
|
|
2687
|
-
if (!checked.ok) return checked;
|
|
2688
|
-
if (checked.value === page.title) return ok(layout);
|
|
2689
|
-
const base = withExplicitPages(layout);
|
|
2690
|
-
return ok({
|
|
2691
|
-
...base,
|
|
2692
|
-
pages: (base.pages ?? []).map(
|
|
2693
|
-
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
2694
|
-
)
|
|
2695
|
-
});
|
|
3295
|
+
function failedWidget(definition, error) {
|
|
3296
|
+
return { definition, status: "error", error };
|
|
2696
3297
|
}
|
|
2697
|
-
function
|
|
2698
|
-
|
|
2699
|
-
const from = pages.findIndex((p) => p.key === pageKey);
|
|
2700
|
-
if (from === -1) return layout;
|
|
2701
|
-
const to = Math.max(
|
|
2702
|
-
0,
|
|
2703
|
-
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
2704
|
-
);
|
|
2705
|
-
if (to === from) return layout;
|
|
2706
|
-
const next = [...pages];
|
|
2707
|
-
const [moved] = next.splice(from, 1);
|
|
2708
|
-
next.splice(to, 0, moved);
|
|
2709
|
-
return { ...layout, pages: next };
|
|
3298
|
+
function loadingWidgets(definitions) {
|
|
3299
|
+
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
2710
3300
|
}
|
|
2711
|
-
function
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
);
|
|
3301
|
+
function cacheKeyFor(definition, options, values) {
|
|
3302
|
+
const base = options.cacheKey === void 0 ? definition.sourceKey ?? definition.key : options.cacheKey(definition);
|
|
3303
|
+
return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
|
|
2715
3304
|
}
|
|
2716
|
-
function
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2721
|
-
if (pages.length === 1) {
|
|
2722
|
-
return err(
|
|
2723
|
-
`${describePage(page)} is the only page, so it can't be removed.`
|
|
2724
|
-
);
|
|
2725
|
-
}
|
|
2726
|
-
const target = pages[index > 0 ? index - 1 : 1];
|
|
2727
|
-
const merged = {
|
|
2728
|
-
...layout,
|
|
2729
|
-
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
2730
|
-
(p) => p.key === target.key ? {
|
|
2731
|
-
...p,
|
|
2732
|
-
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
2733
|
-
hidden: [...p.hidden, ...page.hidden],
|
|
2734
|
-
minimized: [...p.minimized, ...page.minimized]
|
|
2735
|
-
} : p
|
|
2736
|
-
)
|
|
2737
|
-
};
|
|
2738
|
-
const full = overflowing(
|
|
2739
|
-
definitions,
|
|
2740
|
-
merged,
|
|
3305
|
+
function optionValuesFor(definition, options) {
|
|
3306
|
+
return resolveOptionValues(
|
|
3307
|
+
definition,
|
|
3308
|
+
options.optionValues?.[definition.key],
|
|
2741
3309
|
options
|
|
2742
3310
|
);
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
3311
|
+
}
|
|
3312
|
+
function finalizeWidget(widget, values) {
|
|
3313
|
+
if (Object.keys(values).length === 0) return widget;
|
|
3314
|
+
if (widget.status === "ok") {
|
|
3315
|
+
const data = applyClientOptions(
|
|
3316
|
+
widget.definition,
|
|
3317
|
+
widget.data,
|
|
3318
|
+
values
|
|
2746
3319
|
);
|
|
3320
|
+
return { ...widget, data, options: values };
|
|
2747
3321
|
}
|
|
2748
|
-
return
|
|
3322
|
+
return { ...widget, options: values };
|
|
2749
3323
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
3324
|
+
var LoadInterruption = class extends Error {
|
|
3325
|
+
};
|
|
3326
|
+
async function resolveWidget(definition, provider, context, options = {}) {
|
|
3327
|
+
const values = optionValuesFor(definition, options);
|
|
3328
|
+
return finalizeWidget(
|
|
3329
|
+
await resolveWidgetUnapplied(definition, provider, context, options),
|
|
3330
|
+
values
|
|
3331
|
+
);
|
|
2752
3332
|
}
|
|
2753
|
-
function
|
|
2754
|
-
const
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
3333
|
+
async function resolveWidgetUnapplied(definition, provider, context, options = {}) {
|
|
3334
|
+
const values = optionValuesFor(definition, options);
|
|
3335
|
+
const quotedKey = JSON.stringify(definition.key);
|
|
3336
|
+
if (provider === void 0) {
|
|
3337
|
+
return failedWidget(
|
|
3338
|
+
definition,
|
|
3339
|
+
`Widget ${quotedKey}: no provider is registered for this key.`
|
|
2758
3340
|
);
|
|
2759
3341
|
}
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
if (sourceKey === targetKey) return ok(layout);
|
|
2765
|
-
if (resolveWidgetLock(widget).move) {
|
|
2766
|
-
return err(
|
|
2767
|
-
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
3342
|
+
if (options.signal?.aborted === true) {
|
|
3343
|
+
return failedWidget(
|
|
3344
|
+
definition,
|
|
3345
|
+
`Widget ${quotedKey}: loading was canceled before it started.`
|
|
2768
3346
|
);
|
|
2769
3347
|
}
|
|
2770
|
-
const
|
|
2771
|
-
const
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
const
|
|
2775
|
-
const
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
return
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
if (!hidden) {
|
|
2804
|
-
const allowed = rowsAllowed(target, options);
|
|
2805
|
-
const needed = placeRows(
|
|
2806
|
-
pageWidgets(definitions, moved, targetKey)
|
|
2807
|
-
).length;
|
|
2808
|
-
if (needed > allowed) {
|
|
2809
|
-
return err(
|
|
2810
|
-
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
3348
|
+
const controller = new AbortController();
|
|
3349
|
+
const onExternalAbort = () => controller.abort(options.signal?.reason);
|
|
3350
|
+
options.signal?.addEventListener("abort", onExternalAbort, { once: true });
|
|
3351
|
+
let timedOut = false;
|
|
3352
|
+
const timeoutMs = options.timeoutMs !== void 0 && options.timeoutMs > 0 ? options.timeoutMs : void 0;
|
|
3353
|
+
const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
3354
|
+
timedOut = true;
|
|
3355
|
+
controller.abort(new Error(`Timed out after ${timeoutMs} ms.`));
|
|
3356
|
+
}, timeoutMs);
|
|
3357
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
3358
|
+
controller.signal.addEventListener(
|
|
3359
|
+
"abort",
|
|
3360
|
+
() => reject(new LoadInterruption()),
|
|
3361
|
+
{ once: true }
|
|
3362
|
+
);
|
|
3363
|
+
});
|
|
3364
|
+
let payload;
|
|
3365
|
+
try {
|
|
3366
|
+
payload = await Promise.race([
|
|
3367
|
+
Promise.resolve().then(() => {
|
|
3368
|
+
if (controller.signal.aborted) throw new LoadInterruption();
|
|
3369
|
+
return provider(context, definition, {
|
|
3370
|
+
signal: controller.signal,
|
|
3371
|
+
options: values
|
|
3372
|
+
});
|
|
3373
|
+
}),
|
|
3374
|
+
interrupted
|
|
3375
|
+
]);
|
|
3376
|
+
} catch (cause) {
|
|
3377
|
+
if (cause instanceof LoadInterruption) {
|
|
3378
|
+
return failedWidget(
|
|
3379
|
+
definition,
|
|
3380
|
+
timedOut ? `Widget ${quotedKey}: provider timed out after ${timeoutMs} ms.` : `Widget ${quotedKey}: loading was canceled.`
|
|
2811
3381
|
);
|
|
2812
3382
|
}
|
|
3383
|
+
return failedWidget(
|
|
3384
|
+
definition,
|
|
3385
|
+
`Widget ${quotedKey}: provider failed: ${describeUnknownError(cause)}`
|
|
3386
|
+
);
|
|
3387
|
+
} finally {
|
|
3388
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
3389
|
+
options.signal?.removeEventListener("abort", onExternalAbort);
|
|
2813
3390
|
}
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
if (
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
let candidate = base;
|
|
2826
|
-
let n = 1;
|
|
2827
|
-
while (taken.has(normalizedTitle(candidate))) {
|
|
2828
|
-
const suffix = ` (#${n})`;
|
|
2829
|
-
candidate = `${base.slice(0, MAX_PAGE_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
2830
|
-
n += 1;
|
|
3391
|
+
const resolved = resolvePayload(definition, payload, options);
|
|
3392
|
+
if (resolved.status !== "ok") return resolved;
|
|
3393
|
+
const storedAt = Date.now();
|
|
3394
|
+
if (options.cache !== void 0) {
|
|
3395
|
+
try {
|
|
3396
|
+
await options.cache.set(cacheKeyFor(definition, options, values), {
|
|
3397
|
+
payload: resolved.data,
|
|
3398
|
+
storedAt
|
|
3399
|
+
});
|
|
3400
|
+
} catch {
|
|
3401
|
+
}
|
|
2831
3402
|
}
|
|
2832
|
-
|
|
2833
|
-
return candidate;
|
|
3403
|
+
return { ...resolved, updatedAt: storedAt };
|
|
2834
3404
|
}
|
|
2835
|
-
function
|
|
2836
|
-
return
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
});
|
|
3405
|
+
function widgetsFor(definitions, roles) {
|
|
3406
|
+
return sortDefinitions(
|
|
3407
|
+
definitions.filter((definition) => isWidgetVisibleTo(definition, roles))
|
|
3408
|
+
);
|
|
2840
3409
|
}
|
|
2841
|
-
function
|
|
2842
|
-
const
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
3410
|
+
async function resolveWidgets(definitions, providers, context, options = {}) {
|
|
3411
|
+
const visible = widgetsFor(definitions, context.roles);
|
|
3412
|
+
return Promise.all(
|
|
3413
|
+
visible.map(
|
|
3414
|
+
(definition) => resolveWidget(
|
|
3415
|
+
definition,
|
|
3416
|
+
Object.prototype.hasOwnProperty.call(
|
|
3417
|
+
providers,
|
|
3418
|
+
definition.sourceKey ?? definition.key
|
|
3419
|
+
) ? providers[definition.sourceKey ?? definition.key] : void 0,
|
|
3420
|
+
context,
|
|
3421
|
+
options
|
|
3422
|
+
)
|
|
3423
|
+
)
|
|
2848
3424
|
);
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3427
|
+
// src/core/loader.ts
|
|
3428
|
+
function createWidgetLoader(options) {
|
|
3429
|
+
const { definitions, providers, context, ...baseOptions } = options;
|
|
3430
|
+
const chosen = new Map(
|
|
3431
|
+
Object.entries(baseOptions.optionValues ?? {}).flatMap(
|
|
3432
|
+
([key, values]) => values === void 0 ? [] : [[key, values]]
|
|
3433
|
+
)
|
|
2854
3434
|
);
|
|
2855
|
-
const
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
pages.push(next);
|
|
2874
|
-
});
|
|
2875
|
-
const work = () => ({ ...locked, pages });
|
|
2876
|
-
const pinned = defs.filter((d) => resolveWidgetLock(d).move);
|
|
2877
|
-
for (const widget of pinned) {
|
|
2878
|
-
const home = homePageKey(work(), widget);
|
|
2879
|
-
const keysHere = (p, list) => p.key !== home && p[list].includes(widget.key);
|
|
2880
|
-
const wasHidden = pages.some((p) => keysHere(p, "hidden"));
|
|
2881
|
-
const wasMinimized = pages.some((p) => keysHere(p, "minimized"));
|
|
2882
|
-
if (!wasHidden && !wasMinimized) continue;
|
|
2883
|
-
pages = pages.map((p) => {
|
|
2884
|
-
let next = p;
|
|
2885
|
-
if (keysHere(p, "hidden")) {
|
|
2886
|
-
next = { ...next, hidden: without(next.hidden, widget.key) };
|
|
2887
|
-
}
|
|
2888
|
-
if (keysHere(p, "minimized")) {
|
|
2889
|
-
next = { ...next, minimized: without(next.minimized, widget.key) };
|
|
3435
|
+
const resolveOptions = baseOptions;
|
|
3436
|
+
function currentOptions() {
|
|
3437
|
+
return { ...resolveOptions, optionValues: Object.fromEntries(chosen) };
|
|
3438
|
+
}
|
|
3439
|
+
const unapplied = /* @__PURE__ */ new Map();
|
|
3440
|
+
const visible = widgetsFor(definitions, context.roles);
|
|
3441
|
+
let snapshot = loadingWidgets(visible);
|
|
3442
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
3443
|
+
const started = /* @__PURE__ */ new Set();
|
|
3444
|
+
const generations = /* @__PURE__ */ new Map();
|
|
3445
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
3446
|
+
let disposed = false;
|
|
3447
|
+
function publish(next) {
|
|
3448
|
+
snapshot = next;
|
|
3449
|
+
for (const listener of [...listeners]) {
|
|
3450
|
+
try {
|
|
3451
|
+
listener();
|
|
3452
|
+
} catch {
|
|
2890
3453
|
}
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
function replace(key, widget) {
|
|
3457
|
+
publish(
|
|
3458
|
+
snapshot.map(
|
|
3459
|
+
(current) => current.definition.key === key ? widget : current
|
|
3460
|
+
)
|
|
3461
|
+
);
|
|
3462
|
+
}
|
|
3463
|
+
function abortAll(reason) {
|
|
3464
|
+
for (const controller of inFlight.values()) controller.abort(reason);
|
|
3465
|
+
inFlight.clear();
|
|
3466
|
+
}
|
|
3467
|
+
resolveOptions.signal?.addEventListener(
|
|
3468
|
+
"abort",
|
|
3469
|
+
() => abortAll(resolveOptions.signal?.reason),
|
|
3470
|
+
{ once: true }
|
|
3471
|
+
);
|
|
3472
|
+
async function run(definition, readCache) {
|
|
3473
|
+
const key = definition.key;
|
|
3474
|
+
const generation = (generations.get(key) ?? 0) + 1;
|
|
3475
|
+
generations.set(key, generation);
|
|
3476
|
+
inFlight.get(key)?.abort(new Error("Superseded by a newer load."));
|
|
3477
|
+
const controller = new AbortController();
|
|
3478
|
+
inFlight.set(key, controller);
|
|
3479
|
+
const isCurrent = () => !disposed && generations.get(key) === generation;
|
|
3480
|
+
const runOptions = currentOptions();
|
|
3481
|
+
const values = optionValuesFor(definition, runOptions);
|
|
3482
|
+
if (readCache && runOptions.cache !== void 0) {
|
|
3483
|
+
try {
|
|
3484
|
+
const entry = await runOptions.cache.get(
|
|
3485
|
+
cacheKeyFor(definition, runOptions, values)
|
|
3486
|
+
);
|
|
3487
|
+
if (entry !== void 0 && isCurrent()) {
|
|
3488
|
+
const cached = resolvePayload(definition, entry.payload, runOptions);
|
|
3489
|
+
if (cached.status === "ok") {
|
|
3490
|
+
const stale = { ...cached, updatedAt: entry.storedAt, stale: true };
|
|
3491
|
+
unapplied.set(key, stale);
|
|
3492
|
+
replace(key, finalizeWidget(stale, values));
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
} catch {
|
|
2897
3496
|
}
|
|
2898
|
-
|
|
3497
|
+
}
|
|
3498
|
+
if (!isCurrent()) return;
|
|
3499
|
+
const providerKey = definition.sourceKey ?? key;
|
|
3500
|
+
const provider = Object.prototype.hasOwnProperty.call(
|
|
3501
|
+
providers,
|
|
3502
|
+
providerKey
|
|
3503
|
+
) ? providers[providerKey] : void 0;
|
|
3504
|
+
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
3505
|
+
...runOptions,
|
|
3506
|
+
signal: controller.signal
|
|
2899
3507
|
});
|
|
3508
|
+
if (!isCurrent()) return;
|
|
3509
|
+
inFlight.delete(key);
|
|
3510
|
+
if (result.status === "ok") unapplied.set(key, result);
|
|
3511
|
+
else unapplied.delete(key);
|
|
3512
|
+
replace(key, finalizeWidget(result, values));
|
|
2900
3513
|
}
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
3514
|
+
function start(definition, readCache) {
|
|
3515
|
+
started.add(definition.key);
|
|
3516
|
+
void run(definition, readCache);
|
|
2904
3517
|
}
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
(a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
|
|
2908
|
-
).map(({ definition }) => definition);
|
|
2909
|
-
for (const widget of unplaced) {
|
|
2910
|
-
const home = homePageKey(work(), widget);
|
|
2911
|
-
pages = pages.map(
|
|
2912
|
-
(p) => p.key === home ? { ...p, order: [...p.order, widget.key] } : p
|
|
2913
|
-
);
|
|
3518
|
+
function definitionFor(key) {
|
|
3519
|
+
return visible.find((definition) => definition.key === key);
|
|
2914
3520
|
}
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
const
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
}
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
for (let j = i + 1; j < pages.length && !placed; j += 1) {
|
|
2935
|
-
const candidate = pages[j];
|
|
2936
|
-
const trial = pages.map(
|
|
2937
|
-
(p) => p.key === candidate.key ? {
|
|
2938
|
-
...p,
|
|
2939
|
-
order: [...moving, ...p.order],
|
|
2940
|
-
minimized: [...p.minimized, ...movedMinimized]
|
|
2941
|
-
} : p
|
|
2942
|
-
);
|
|
2943
|
-
const items = pageWidgets(
|
|
2944
|
-
defs,
|
|
2945
|
-
{ ...locked, pages: trial },
|
|
2946
|
-
candidate.key
|
|
3521
|
+
function load(key) {
|
|
3522
|
+
if (disposed || started.has(key)) return;
|
|
3523
|
+
const definition = definitionFor(key);
|
|
3524
|
+
if (definition !== void 0) start(definition, true);
|
|
3525
|
+
}
|
|
3526
|
+
function loadAll() {
|
|
3527
|
+
for (const definition of visible) load(definition.key);
|
|
3528
|
+
}
|
|
3529
|
+
function refresh(key, refreshOptions) {
|
|
3530
|
+
if (disposed) return;
|
|
3531
|
+
const keys = key === void 0 ? [...started] : [key];
|
|
3532
|
+
for (const target of keys) {
|
|
3533
|
+
const definition = definitionFor(target);
|
|
3534
|
+
if (definition === void 0) continue;
|
|
3535
|
+
if (refreshOptions?.skipInFlight === true && inFlight.has(target)) {
|
|
3536
|
+
continue;
|
|
3537
|
+
}
|
|
3538
|
+
const current = snapshot.find(
|
|
3539
|
+
(widget) => widget.definition.key === target
|
|
2947
3540
|
);
|
|
2948
|
-
if (
|
|
2949
|
-
|
|
2950
|
-
placed = true;
|
|
3541
|
+
if (current?.status === "error") {
|
|
3542
|
+
replace(target, { definition, status: "loading" });
|
|
2951
3543
|
}
|
|
3544
|
+
start(definition, false);
|
|
2952
3545
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
3546
|
+
}
|
|
3547
|
+
function setOptions(key, values) {
|
|
3548
|
+
if (disposed) return;
|
|
3549
|
+
const definition = definitionFor(key);
|
|
3550
|
+
if (definition === void 0) return;
|
|
3551
|
+
const before = optionValuesFor(definition, currentOptions());
|
|
3552
|
+
chosen.set(key, { ...values });
|
|
3553
|
+
const runOptions = currentOptions();
|
|
3554
|
+
const after = optionValuesFor(definition, runOptions);
|
|
3555
|
+
if (sameOptionValues(before, after) || !started.has(key)) return;
|
|
3556
|
+
const kept = unapplied.get(key);
|
|
3557
|
+
if (kept !== void 0 && optionsCacheSuffix(definition, before, runOptions) === optionsCacheSuffix(definition, after, runOptions)) {
|
|
3558
|
+
replace(key, finalizeWidget(kept, after));
|
|
3559
|
+
return;
|
|
3560
|
+
}
|
|
3561
|
+
const current = snapshot.find(
|
|
3562
|
+
(widget) => widget.definition.key === key
|
|
3563
|
+
);
|
|
3564
|
+
if (current?.status === "ok") replace(key, { ...current, stale: true });
|
|
3565
|
+
else if (current?.status === "error") {
|
|
3566
|
+
replace(key, { definition, status: "loading" });
|
|
2964
3567
|
}
|
|
3568
|
+
start(definition, true);
|
|
2965
3569
|
}
|
|
2966
|
-
|
|
2967
|
-
|
|
3570
|
+
function dispose() {
|
|
3571
|
+
disposed = true;
|
|
3572
|
+
abortAll(new Error("The widget loader was disposed."));
|
|
3573
|
+
listeners.clear();
|
|
3574
|
+
}
|
|
3575
|
+
function subscribe(listener) {
|
|
3576
|
+
listeners.add(listener);
|
|
3577
|
+
return () => {
|
|
3578
|
+
listeners.delete(listener);
|
|
3579
|
+
};
|
|
3580
|
+
}
|
|
3581
|
+
return {
|
|
3582
|
+
getSnapshot: () => snapshot,
|
|
3583
|
+
subscribe,
|
|
3584
|
+
load,
|
|
3585
|
+
loadAll,
|
|
3586
|
+
refresh,
|
|
3587
|
+
setOptions,
|
|
3588
|
+
dispose
|
|
3589
|
+
};
|
|
2968
3590
|
}
|
|
2969
3591
|
|
|
2970
3592
|
// src/core/scale.ts
|
|
@@ -3796,6 +4418,6 @@ function indent(block) {
|
|
|
3796
4418
|
return block.split("\n").map((line) => ` ${line}`).join("\n");
|
|
3797
4419
|
}
|
|
3798
4420
|
|
|
3799
|
-
export { DATE_RANGE_PRESETS, DEFAULT_COMPARISON_LABEL, DEFAULT_CURRENCY, DEFAULT_DETAIL_PAGE_SIZE, DEFAULT_EMPTY_TEXT, DEFAULT_MAX_LAYOUT_BYTES, DEFAULT_PAGE_KEY, DEFAULT_PAGE_ROWS, DETAIL_MODES, EMPTY_LAYOUT, LAYOUT_ENVELOPE_VERSION, MAX_DETAIL_PAGE_SIZE, MAX_GRAPH_SERIES, MAX_PAGE_ROWS, MAX_PAGE_TITLE_LENGTH, MAX_PARSED_PAGES, MAX_PARSED_SETTINGS, MAX_REPORTED_PROBLEMS, MAX_TEXT_OPTION_LENGTH, MAX_WIDGET_OPTIONS, MAX_WIDGET_TITLE_LENGTH, MAX_WIDGET_WIDTH, MIN_WIDGET_WIDTH, OPTION_KEY_PATTERN, THEME_TOKEN_VARIABLES, VALUE_FORMATS, WIDGET_FILLS, WIDGET_KINDS, WIDGET_OPTION_TYPES, WIDGET_SIZES, WIDTH_COLUMNS, addPage, applyClientOptions, applySettings, assignPages, baseColumnSpan, cacheKeyFor, clearWidgetSettings, coerceOptionValue, createLayoutPersistence, createTheme, createWidgetLoader, defaultDetailQuery, defaultOptionValue, defineWidget, deriveDetailData, describeUnknownError, effectiveDefinitions, emptyTextFor, emptyWidget, enforceLocks, err, failedWidget, fillColumnSpans, fillWidthSpans, fillsHeight, fillsWidth, finalizeWidget, fitCount, formatCount, formatPercentChange, formatValue, fromLegacyDefinition, hasPages, hiddenWidgets, hideWidget, isClientApplied, isColumnKeyFor, isFlexible, isHidden, isIsoInterval, isMinimized, isOptionsLocked, isSafeHref, isSafeImageUrl, isTitleInUse, isWidgetEmptyState, isWidgetKind, isWidgetVisibleTo, isWidgetWidth, itemWidth, kpiDelta, labelStride, loadingWidgets, memoryAdapter, minimizeWidget, movePage, moveWidget, moveWidgetBy, moveWidgetDown, moveWidgetToPage, moveWidgetUp, nextPageKey, niceDomain, niceStep, normalizeLayout, normalizeSettings, normalizeTitle, ok, optionValuesFor, optionValuesFromLayout, optionsCacheSuffix, pageLayout, pageList, pageOf, pageRoom, pageWidgets, parseDetailQuery, parseLayout, parseSettings, parseSortValue, percentChange, placeRows, pruneLayout, queryRows, readOnly, removePage, renamePage, resolveDateRange, resolveDetailOptions, resolveOptionValues, resolvePageKey, resolvePayload, resolveTableControls, resolveWidget, resolveWidgetLock, resolveWidgetUnapplied, resolveWidgets, restoreWidget, sameOptionValues, serializeDetailQuery, serializeLayout, serializeSettings, setWidgetSettings, shareOf, showWidget, sortDefinitions, sortFromOptions, tableDetailData, toggleHidden, toggleMinimized, upgradeLegacyWidgetData, usesWidthColumns, utf8Length, validateDetailData, validateWidgetData, validateWidgetDefinition, visibleWidgets, widgetOptionValues, widgetsFor, widthForSize, widthRange, widthShareLabel, withEncoding, withFallback, withLogging, withPageLayout, withPrefix, withReadCache, withRetry };
|
|
4421
|
+
export { BASE_VIEW_KEY, DATE_RANGE_PRESETS, DEFAULT_COMPARISON_LABEL, DEFAULT_CURRENCY, DEFAULT_DETAIL_PAGE_SIZE, DEFAULT_EMPTY_TEXT, DEFAULT_MAX_LAYOUT_BYTES, DEFAULT_PAGE_KEY, DEFAULT_PAGE_ROWS, DETAIL_MODES, EMPTY_LAYOUT, LAYOUT_ENVELOPE_VERSION, MAX_DETAIL_PAGE_SIZE, MAX_GRAPH_SERIES, MAX_PAGE_ROWS, MAX_PAGE_TITLE_LENGTH, MAX_PARSED_CLONES, MAX_PARSED_PAGES, MAX_PARSED_SETTINGS, MAX_REPORTED_PROBLEMS, MAX_TEXT_OPTION_LENGTH, MAX_WIDGET_OPTIONS, MAX_WIDGET_TITLE_LENGTH, MAX_WIDGET_VIEWS, MAX_WIDGET_WIDTH, MIN_WIDGET_WIDTH, OPTION_KEY_PATTERN, THEME_TOKEN_VARIABLES, VALUE_FORMATS, VIEW_CONVERSIONS, WIDGET_FILLS, WIDGET_KINDS, WIDGET_OPTION_TYPES, WIDGET_SIZES, WIDTH_COLUMNS, addPage, applyClientOptions, applySettings, applyView, assignPages, baseColumnSpan, cacheKeyFor, canDuplicate, cleanClones, clearWidgetSettings, cloneNumber, coerceOptionValue, convertView, createLayoutPersistence, createTheme, createWidgetLoader, defaultDetailQuery, defaultOptionValue, defineWidget, deleteClone, deriveDetailData, describeUnknownError, duplicateWidget, effectiveDefinitions, effectiveViewKey, emptyTextFor, emptyWidget, enforceLocks, err, failedWidget, fillColumnSpans, fillWidthSpans, fillsHeight, fillsWidth, finalizeWidget, findView, fitCount, formatCount, formatPercentChange, formatValue, fromLegacyDefinition, hasPages, hasViews, hiddenWidgets, hideWidget, isClientApplied, isColumnKeyFor, isFlexible, isHidden, isIsoInterval, isMinimized, isOptionsLocked, isSafeHref, isSafeImageUrl, isTitleInUse, isWidgetEmptyState, isWidgetKind, isWidgetVisibleTo, isWidgetWidth, itemWidth, kindLabel, kpiDelta, labelStride, loadingWidgets, memoryAdapter, minimizeWidget, movePage, moveWidget, moveWidgetBy, moveWidgetDown, moveWidgetToPage, moveWidgetUp, nextCloneKey, nextPageKey, niceDomain, niceStep, normalizeLayout, normalizeSettings, normalizeTitle, ok, optionValuesFor, optionValuesFromLayout, optionsCacheSuffix, pageLayout, pageList, pageOf, pageRoom, pageWidgets, parseClones, parseDetailQuery, parseLayout, parseSettings, parseSortValue, percentChange, placeRows, pruneLayout, queryRows, readOnly, removePage, renamePage, resolveDateRange, resolveDetailOptions, resolveOptionValues, resolvePageKey, resolvePayload, resolveTableControls, resolveWidget, resolveWidgetLock, resolveWidgetUnapplied, resolveWidgets, restoreWidget, sameOptionValues, serializeClones, serializeDetailQuery, serializeLayout, serializeSettings, setWidgetSettings, shareOf, showWidget, sortDefinitions, sortFromOptions, suggestedViewTitle, tableDetailData, toggleHidden, toggleMinimized, upgradeLegacyWidgetData, usesWidthColumns, utf8Length, validateDetailData, validateWidgetData, validateWidgetDefinition, viewChoices, viewKind, viewNote, visibleWidgets, widgetOptionValues, widgetsFor, widthForSize, widthRange, widthShareLabel, withClones, withEncoding, withFallback, withLogging, withPageLayout, withPrefix, withReadCache, withRetry };
|
|
3800
4422
|
//# sourceMappingURL=core.js.map
|
|
3801
4423
|
//# sourceMappingURL=core.js.map
|