@richardmcquiston01/dashboard-widgets-toolkit 0.15.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 +1931 -1698
- 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 +1921 -1699
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1931 -1698
- 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 +1921 -1699
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +276 -11
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +21 -2
- package/dist/react.d.ts +21 -2
- package/dist/react.js +276 -11
- package/dist/react.js.map +1 -1
- package/dist/{storage-DwJ-vGJg.d.cts → storage-oivwB7al.d.cts} +126 -55
- package/dist/{storage-DwJ-vGJg.d.ts → storage-oivwB7al.d.ts} +126 -55
- 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,20 +294,277 @@ 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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
319
|
+
function hasViews(item) {
|
|
320
|
+
return item.views !== void 0 && item.views.length > 0;
|
|
321
|
+
}
|
|
322
|
+
function findView(item, key) {
|
|
323
|
+
return item.views?.find((view) => view.key === key);
|
|
324
|
+
}
|
|
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);
|
|
252
568
|
return scheme === null || scheme === "http" || scheme === "https" || scheme === "mailto";
|
|
253
569
|
}
|
|
254
570
|
function isSafeImageUrl(url) {
|
|
@@ -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,1267 +905,1643 @@ function deriveDetailData(data, locale) {
|
|
|
589
905
|
return void 0;
|
|
590
906
|
}
|
|
591
907
|
|
|
592
|
-
// src/core/
|
|
593
|
-
var
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
908
|
+
// src/core/options.ts
|
|
909
|
+
var DATE_RANGE_PRESETS = [
|
|
910
|
+
"today",
|
|
911
|
+
"yesterday",
|
|
912
|
+
"last7",
|
|
913
|
+
"last30",
|
|
914
|
+
"last90",
|
|
915
|
+
"thisMonth",
|
|
916
|
+
"lastMonth",
|
|
917
|
+
"thisYear",
|
|
918
|
+
"lastYear"
|
|
919
|
+
];
|
|
920
|
+
var WIDGET_OPTION_TYPES = [
|
|
921
|
+
"choice",
|
|
922
|
+
"number",
|
|
923
|
+
"boolean",
|
|
924
|
+
"text",
|
|
925
|
+
"dateRange",
|
|
926
|
+
"color",
|
|
927
|
+
"sort",
|
|
928
|
+
"columns"
|
|
929
|
+
];
|
|
930
|
+
var MAX_WIDGET_OPTIONS = 20;
|
|
931
|
+
var MAX_TEXT_OPTION_LENGTH = 500;
|
|
932
|
+
var OPTION_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,39}$/;
|
|
933
|
+
var COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
934
|
+
var INTERVAL_PATTERN = /^(\d{4}-\d{2}-\d{2})\/(\d{4}-\d{2}-\d{2})$/;
|
|
935
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
936
|
+
function isColumnKeyFor(kind, key) {
|
|
937
|
+
if (kind === "TABLE") return /^c\d+$/.test(key);
|
|
938
|
+
if (kind === "BAR_LIST") return key === "label" || key === "value";
|
|
939
|
+
return false;
|
|
598
940
|
}
|
|
599
|
-
function
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
941
|
+
function isClientApplied(option) {
|
|
942
|
+
return (option.type === "sort" || option.type === "columns") && option.apply === "client";
|
|
943
|
+
}
|
|
944
|
+
function civilToday(context) {
|
|
945
|
+
const now = context.now ?? Date.now();
|
|
946
|
+
let timeZone = context.timeZone ?? "UTC";
|
|
947
|
+
try {
|
|
948
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
949
|
+
} catch {
|
|
950
|
+
timeZone = "UTC";
|
|
609
951
|
}
|
|
952
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
953
|
+
timeZone,
|
|
954
|
+
year: "numeric",
|
|
955
|
+
month: "2-digit",
|
|
956
|
+
day: "2-digit"
|
|
957
|
+
}).formatToParts(new Date(now));
|
|
958
|
+
const pick = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
|
|
959
|
+
return { year: pick("year"), month: pick("month"), day: pick("day") };
|
|
610
960
|
}
|
|
611
|
-
function
|
|
612
|
-
return
|
|
961
|
+
function toIso(epochMs) {
|
|
962
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
613
963
|
}
|
|
614
|
-
|
|
615
|
-
|
|
964
|
+
var DAY_MS = 864e5;
|
|
965
|
+
function parseIsoDay(text) {
|
|
966
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
967
|
+
if (match === null) return void 0;
|
|
968
|
+
const epoch = Date.UTC(
|
|
969
|
+
Number(match[1]),
|
|
970
|
+
Number(match[2]) - 1,
|
|
971
|
+
Number(match[3])
|
|
972
|
+
);
|
|
973
|
+
return toIso(epoch) === text ? epoch : void 0;
|
|
616
974
|
}
|
|
617
|
-
function
|
|
618
|
-
|
|
975
|
+
function isIsoInterval(value) {
|
|
976
|
+
const match = INTERVAL_PATTERN.exec(value);
|
|
977
|
+
if (match === null) return false;
|
|
978
|
+
const start = parseIsoDay(match[1]);
|
|
979
|
+
const end = parseIsoDay(match[2]);
|
|
980
|
+
return start !== void 0 && end !== void 0 && start <= end;
|
|
619
981
|
}
|
|
620
|
-
function
|
|
621
|
-
return
|
|
982
|
+
function isPreset(value) {
|
|
983
|
+
return DATE_RANGE_PRESETS.includes(value);
|
|
622
984
|
}
|
|
623
|
-
function
|
|
624
|
-
if (
|
|
625
|
-
if (
|
|
626
|
-
|
|
985
|
+
function resolveDateRange(value, context = {}) {
|
|
986
|
+
if (isIsoInterval(value)) return value;
|
|
987
|
+
if (!isPreset(value)) return void 0;
|
|
988
|
+
const today = civilToday(context);
|
|
989
|
+
const todayMs = Date.UTC(today.year, today.month - 1, today.day);
|
|
990
|
+
const range = (startMs, endMs) => `${toIso(startMs)}/${toIso(endMs)}`;
|
|
991
|
+
switch (value) {
|
|
992
|
+
case "today":
|
|
993
|
+
return range(todayMs, todayMs);
|
|
994
|
+
case "yesterday":
|
|
995
|
+
return range(todayMs - DAY_MS, todayMs - DAY_MS);
|
|
996
|
+
case "last7":
|
|
997
|
+
return range(todayMs - 6 * DAY_MS, todayMs);
|
|
998
|
+
case "last30":
|
|
999
|
+
return range(todayMs - 29 * DAY_MS, todayMs);
|
|
1000
|
+
case "last90":
|
|
1001
|
+
return range(todayMs - 89 * DAY_MS, todayMs);
|
|
1002
|
+
case "thisMonth":
|
|
1003
|
+
return range(Date.UTC(today.year, today.month - 1, 1), todayMs);
|
|
1004
|
+
case "lastMonth":
|
|
1005
|
+
return range(
|
|
1006
|
+
Date.UTC(today.year, today.month - 2, 1),
|
|
1007
|
+
Date.UTC(today.year, today.month - 1, 1) - DAY_MS
|
|
1008
|
+
);
|
|
1009
|
+
case "thisYear":
|
|
1010
|
+
return range(Date.UTC(today.year, 0, 1), todayMs);
|
|
1011
|
+
case "lastYear":
|
|
1012
|
+
return range(
|
|
1013
|
+
Date.UTC(today.year - 1, 0, 1),
|
|
1014
|
+
Date.UTC(today.year, 0, 1) - DAY_MS
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
627
1017
|
}
|
|
628
|
-
function
|
|
629
|
-
return
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
(item, trackCount) => baseColumnSpan(item.defaultSize, trackCount, largeSpan)
|
|
633
|
-
);
|
|
1018
|
+
function onStepGrid(option, value) {
|
|
1019
|
+
if (option.step === void 0) return true;
|
|
1020
|
+
const steps = (value - option.min) / option.step;
|
|
1021
|
+
return Math.abs(steps - Math.round(steps)) < 1e-9;
|
|
634
1022
|
}
|
|
635
|
-
function
|
|
636
|
-
|
|
1023
|
+
function parseSortValue(value) {
|
|
1024
|
+
const separator = value.lastIndexOf(":");
|
|
1025
|
+
if (separator <= 0) return void 0;
|
|
1026
|
+
const column = value.slice(0, separator);
|
|
1027
|
+
const direction = value.slice(separator + 1);
|
|
1028
|
+
if (direction !== "asc" && direction !== "desc") return void 0;
|
|
1029
|
+
return { column, direction };
|
|
637
1030
|
}
|
|
638
|
-
function
|
|
639
|
-
|
|
1031
|
+
function sortFromOptions(definition, values) {
|
|
1032
|
+
if (values === void 0) return void 0;
|
|
1033
|
+
const sortOption = definition.options?.find(
|
|
1034
|
+
(option) => option.type === "sort"
|
|
1035
|
+
);
|
|
1036
|
+
if (sortOption === void 0) return void 0;
|
|
1037
|
+
const chosen = values[sortOption.key];
|
|
1038
|
+
return typeof chosen === "string" ? parseSortValue(chosen) : void 0;
|
|
640
1039
|
}
|
|
641
|
-
function
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
1040
|
+
function coerceOptionValue(option, value, context = {}) {
|
|
1041
|
+
switch (option.type) {
|
|
1042
|
+
case "choice":
|
|
1043
|
+
return typeof value === "string" && option.choices.some((choice) => choice.value === value) ? value : void 0;
|
|
1044
|
+
case "number":
|
|
1045
|
+
return typeof value === "number" && Number.isFinite(value) && value >= option.min && value <= option.max && onStepGrid(option, value) ? value : void 0;
|
|
1046
|
+
case "boolean":
|
|
1047
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1048
|
+
case "text": {
|
|
1049
|
+
if (typeof value !== "string") return void 0;
|
|
1050
|
+
const trimmed = value.trim();
|
|
1051
|
+
return trimmed.length <= option.maxLength && !CONTROL_CHARACTERS.test(trimmed) ? trimmed : void 0;
|
|
1052
|
+
}
|
|
1053
|
+
case "dateRange": {
|
|
1054
|
+
if (typeof value !== "string") return void 0;
|
|
1055
|
+
if (isPreset(value) && option.presets !== void 0 && !option.presets.some((preset) => preset.value === value)) {
|
|
1056
|
+
return void 0;
|
|
1057
|
+
}
|
|
1058
|
+
return resolveDateRange(value, context);
|
|
1059
|
+
}
|
|
1060
|
+
case "color":
|
|
1061
|
+
return typeof value === "string" && COLOR_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
1062
|
+
case "sort": {
|
|
1063
|
+
if (typeof value !== "string") return void 0;
|
|
1064
|
+
const sort = parseSortValue(value);
|
|
1065
|
+
return sort !== void 0 && option.columns.some((column) => column.key === sort.column) ? value : void 0;
|
|
1066
|
+
}
|
|
1067
|
+
case "columns": {
|
|
1068
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
1069
|
+
const keys = value;
|
|
1070
|
+
const allowed = new Set(option.columns.map((c) => c.key));
|
|
1071
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1072
|
+
for (const key of keys) {
|
|
1073
|
+
if (typeof key !== "string" || !allowed.has(key) || seen.has(key)) {
|
|
1074
|
+
return void 0;
|
|
1075
|
+
}
|
|
1076
|
+
seen.add(key);
|
|
1077
|
+
}
|
|
1078
|
+
return [...seen];
|
|
1079
|
+
}
|
|
646
1080
|
}
|
|
647
|
-
return count;
|
|
648
|
-
}
|
|
649
|
-
function placeAndFill(items, columns, spanOf) {
|
|
650
|
-
return placeAndFillRows(items, columns, spanOf).spans;
|
|
651
1081
|
}
|
|
652
|
-
function
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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;
|
|
1082
|
+
function defaultOptionValue(option, context = {}) {
|
|
1083
|
+
switch (option.type) {
|
|
1084
|
+
case "dateRange":
|
|
1085
|
+
return resolveDateRange(option.default, context);
|
|
1086
|
+
case "sort":
|
|
1087
|
+
return option.default;
|
|
1088
|
+
case "columns":
|
|
1089
|
+
return [...option.default];
|
|
1090
|
+
default:
|
|
1091
|
+
return option.default;
|
|
678
1092
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1093
|
+
}
|
|
1094
|
+
var NO_VALUES = Object.freeze({});
|
|
1095
|
+
function resolveOptionValues(definition, chosen, context = {}) {
|
|
1096
|
+
const options = definition.options;
|
|
1097
|
+
if (options === void 0 || options.length === 0) return NO_VALUES;
|
|
1098
|
+
const values = {};
|
|
1099
|
+
for (const option of options) {
|
|
1100
|
+
const picked = chosen?.[option.key];
|
|
1101
|
+
const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked, context)) ?? defaultOptionValue(option, context);
|
|
1102
|
+
if (value !== void 0) values[option.key] = value;
|
|
684
1103
|
}
|
|
685
|
-
|
|
686
|
-
return { spans, rows };
|
|
1104
|
+
return values;
|
|
687
1105
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
tableToBarList: { from: "TABLE", to: "BAR_LIST" },
|
|
694
|
-
tableToGraph: { from: "TABLE", to: "GRAPH" },
|
|
695
|
-
barListToTable: { from: "BAR_LIST", to: "TABLE" },
|
|
696
|
-
graphToTable: { from: "GRAPH", to: "TABLE" },
|
|
697
|
-
barListToGraph: { from: "BAR_LIST", to: "GRAPH" }
|
|
698
|
-
};
|
|
699
|
-
var KIND_LABELS = {
|
|
700
|
-
TEXT: "Text",
|
|
701
|
-
KPI: "Number",
|
|
702
|
-
GAUGE: "Gauge",
|
|
703
|
-
TABLE: "Table",
|
|
704
|
-
BAR_LIST: "Bars",
|
|
705
|
-
ALERT_LIST: "List",
|
|
706
|
-
GRAPH: "Chart"
|
|
707
|
-
};
|
|
708
|
-
function kindLabel(kind) {
|
|
709
|
-
return KIND_LABELS[kind];
|
|
710
|
-
}
|
|
711
|
-
function hasViews(item) {
|
|
712
|
-
return item.views !== void 0 && item.views.length > 0;
|
|
713
|
-
}
|
|
714
|
-
function findView(item, key) {
|
|
715
|
-
return item.views?.find((view) => view.key === key);
|
|
1106
|
+
function sameValue(a, b) {
|
|
1107
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1108
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
1109
|
+
}
|
|
1110
|
+
return a === b;
|
|
716
1111
|
}
|
|
717
|
-
function
|
|
718
|
-
|
|
719
|
-
return [
|
|
720
|
-
{
|
|
721
|
-
key: BASE_VIEW_KEY,
|
|
722
|
-
label: item.baseViewLabel ?? kindLabel(item.kind),
|
|
723
|
-
kind: item.kind
|
|
724
|
-
},
|
|
725
|
-
...(item.views ?? []).map(({ key, label, kind }) => ({
|
|
726
|
-
key,
|
|
727
|
-
label,
|
|
728
|
-
kind
|
|
729
|
-
}))
|
|
730
|
-
];
|
|
1112
|
+
function sameOptionValues(a, b) {
|
|
1113
|
+
const keys = Object.keys({ ...a, ...b });
|
|
1114
|
+
return keys.every((key) => sameValue(a[key], b[key]));
|
|
731
1115
|
}
|
|
732
|
-
function
|
|
733
|
-
|
|
1116
|
+
function optionsCacheSuffix(definition, values, context = {}) {
|
|
1117
|
+
const parts = [];
|
|
1118
|
+
for (const option of definition.options ?? []) {
|
|
1119
|
+
if (isClientApplied(option)) continue;
|
|
1120
|
+
const value = values[option.key];
|
|
1121
|
+
if (sameValue(value, defaultOptionValue(option, context))) continue;
|
|
1122
|
+
parts.push(
|
|
1123
|
+
`${option.key}=${encodeURIComponent(JSON.stringify(value ?? null))}`
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
parts.sort();
|
|
1127
|
+
return parts.length === 0 ? "" : `?${parts.join("&")}`;
|
|
734
1128
|
}
|
|
735
|
-
function
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
if (isKnownView(item, item.defaultView)) return item.defaultView;
|
|
739
|
-
return BASE_VIEW_KEY;
|
|
1129
|
+
function tableColumnIndex(key) {
|
|
1130
|
+
const match = /^c(\d+)$/.exec(key);
|
|
1131
|
+
return match === null ? void 0 : Number(match[1]);
|
|
740
1132
|
}
|
|
741
|
-
function
|
|
742
|
-
|
|
1133
|
+
function sortBarItems(items, sort) {
|
|
1134
|
+
const sign = sort.direction === "desc" ? -1 : 1;
|
|
1135
|
+
return items.map((item, index) => ({ item, index })).sort((a, b) => {
|
|
1136
|
+
const order = sort.column === "value" ? a.item.value - b.item.value : a.item.label.localeCompare(b.item.label);
|
|
1137
|
+
return order * sign || a.index - b.index;
|
|
1138
|
+
}).map(({ item }) => item);
|
|
743
1139
|
}
|
|
744
|
-
function
|
|
745
|
-
const
|
|
746
|
-
|
|
1140
|
+
function applyClientOptions(definition, data, values) {
|
|
1141
|
+
const options = definition.options ?? [];
|
|
1142
|
+
const sortOption = options.find(
|
|
1143
|
+
(o) => o.type === "sort" && o.apply === "client"
|
|
1144
|
+
);
|
|
1145
|
+
const columnsOption = options.find(
|
|
1146
|
+
(o) => o.type === "columns" && o.apply === "client"
|
|
1147
|
+
);
|
|
1148
|
+
const sortValue = sortOption === void 0 ? void 0 : values[sortOption.key];
|
|
1149
|
+
const columnsValue = columnsOption === void 0 ? void 0 : values[columnsOption.key];
|
|
1150
|
+
let next = data;
|
|
1151
|
+
if (typeof sortValue === "string") {
|
|
1152
|
+
const sort = parseSortValue(sortValue);
|
|
1153
|
+
if (sort !== void 0 && next.kind === "BAR_LIST") {
|
|
1154
|
+
if (sort.column === "label" || sort.column === "value") {
|
|
1155
|
+
next = { ...next, items: sortBarItems(next.items, sort) };
|
|
1156
|
+
}
|
|
1157
|
+
} else if (sort !== void 0 && next.kind === "TABLE") {
|
|
1158
|
+
const index = tableColumnIndex(sort.column);
|
|
1159
|
+
if (index !== void 0 && index < next.columns.length) {
|
|
1160
|
+
const sorted = queryRows(tableDetailData(next), {
|
|
1161
|
+
page: 1,
|
|
1162
|
+
pageSize: Math.max(1, next.rows.length),
|
|
1163
|
+
sort
|
|
1164
|
+
});
|
|
1165
|
+
if (sorted.ok) next = { ...next, rows: sorted.value.rows };
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
if (Array.isArray(columnsValue) && next.kind === "TABLE") {
|
|
1170
|
+
const table = next;
|
|
1171
|
+
const indexes = columnsValue.map(tableColumnIndex).filter(
|
|
1172
|
+
(index) => index !== void 0 && index < table.columns.length
|
|
1173
|
+
);
|
|
1174
|
+
if (indexes.length > 0) {
|
|
1175
|
+
next = {
|
|
1176
|
+
...table,
|
|
1177
|
+
columns: indexes.map((i) => table.columns[i]),
|
|
1178
|
+
rows: table.rows.map((row) => indexes.map((i) => row[i]))
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return next;
|
|
747
1183
|
}
|
|
748
|
-
|
|
749
|
-
|
|
1184
|
+
|
|
1185
|
+
// src/core/settings.ts
|
|
1186
|
+
var MAX_WIDGET_TITLE_LENGTH = 80;
|
|
1187
|
+
var MAX_PARSED_SETTINGS = 500;
|
|
1188
|
+
var MAX_COLUMN_LIST = 100;
|
|
1189
|
+
var MAX_VALUE_LENGTH = 500;
|
|
1190
|
+
function isRecord(value) {
|
|
1191
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
750
1192
|
}
|
|
751
|
-
function
|
|
752
|
-
if (
|
|
753
|
-
return
|
|
1193
|
+
function parseOptionValue(value) {
|
|
1194
|
+
if (typeof value === "string") {
|
|
1195
|
+
return value.length <= MAX_VALUE_LENGTH ? value : void 0;
|
|
1196
|
+
}
|
|
1197
|
+
if (typeof value === "number")
|
|
1198
|
+
return Number.isFinite(value) ? value : void 0;
|
|
1199
|
+
if (typeof value === "boolean") return value;
|
|
1200
|
+
if (Array.isArray(value) && value.length <= MAX_COLUMN_LIST) {
|
|
1201
|
+
return value.every(
|
|
1202
|
+
(entry) => typeof entry === "string" && entry.length <= MAX_VALUE_LENGTH
|
|
1203
|
+
) ? value : void 0;
|
|
754
1204
|
}
|
|
755
1205
|
return void 0;
|
|
756
1206
|
}
|
|
757
|
-
function
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
1207
|
+
function parseOne(raw) {
|
|
1208
|
+
if (!isRecord(raw)) return void 0;
|
|
1209
|
+
const title = raw["title"];
|
|
1210
|
+
const width = raw["width"];
|
|
1211
|
+
const flexible = raw["flexible"];
|
|
1212
|
+
const options = raw["options"];
|
|
1213
|
+
const view = raw["view"];
|
|
1214
|
+
const cleanTitle = typeof title === "string" ? title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH) : "";
|
|
1215
|
+
const chosen = {};
|
|
1216
|
+
if (isRecord(options)) {
|
|
1217
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1218
|
+
const parsed = parseOptionValue(value);
|
|
1219
|
+
if (parsed !== void 0) chosen[key] = parsed;
|
|
765
1220
|
}
|
|
766
|
-
numbers.push(value);
|
|
767
1221
|
}
|
|
768
|
-
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
label: row[conversion.label]?.text ?? "",
|
|
777
|
-
value: numbers.value[index],
|
|
778
|
-
display: row[conversion.value]?.text ?? String(numbers.value[index])
|
|
779
|
-
}));
|
|
780
|
-
return ok({ kind: "BAR_LIST", items });
|
|
1222
|
+
const settings = {
|
|
1223
|
+
...cleanTitle === "" ? {} : { title: cleanTitle },
|
|
1224
|
+
...isWidgetWidth(width) ? { width } : {},
|
|
1225
|
+
...typeof flexible === "boolean" ? { flexible } : {},
|
|
1226
|
+
...Object.keys(chosen).length === 0 ? {} : { options: chosen },
|
|
1227
|
+
...typeof view === "string" && view !== "" && view.length <= 80 ? { view } : {}
|
|
1228
|
+
};
|
|
1229
|
+
return Object.keys(settings).length === 0 ? void 0 : settings;
|
|
781
1230
|
}
|
|
782
|
-
function
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
context
|
|
793
|
-
);
|
|
794
|
-
for (const index of conversion.values) {
|
|
795
|
-
bad ??= checkColumn(table, index, "series", context);
|
|
1231
|
+
function parseSettings(raw) {
|
|
1232
|
+
if (!isRecord(raw)) return void 0;
|
|
1233
|
+
const parsed = {};
|
|
1234
|
+
for (const [key, value] of Object.entries(raw).slice(
|
|
1235
|
+
0,
|
|
1236
|
+
MAX_PARSED_SETTINGS
|
|
1237
|
+
)) {
|
|
1238
|
+
if (key === "") continue;
|
|
1239
|
+
const one = parseOne(value);
|
|
1240
|
+
if (one !== void 0) parsed[key] = one;
|
|
796
1241
|
}
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
}
|
|
808
|
-
|
|
1242
|
+
return Object.keys(parsed).length === 0 ? void 0 : parsed;
|
|
1243
|
+
}
|
|
1244
|
+
function serializeSettings(settings) {
|
|
1245
|
+
if (settings === void 0) return void 0;
|
|
1246
|
+
const out = {};
|
|
1247
|
+
for (const key of Object.keys(settings)) {
|
|
1248
|
+
const one = settings[key];
|
|
1249
|
+
if (one === void 0) continue;
|
|
1250
|
+
const piece = {
|
|
1251
|
+
...one.title === void 0 ? {} : { title: one.title },
|
|
1252
|
+
...one.width === void 0 ? {} : { width: one.width },
|
|
1253
|
+
...one.flexible === void 0 ? {} : { flexible: one.flexible },
|
|
1254
|
+
...one.options === void 0 || Object.keys(one.options).length === 0 ? {} : { options: { ...one.options } },
|
|
1255
|
+
...one.view === void 0 ? {} : { view: one.view }
|
|
1256
|
+
};
|
|
1257
|
+
if (Object.keys(piece).length > 0) out[key] = piece;
|
|
809
1258
|
}
|
|
810
|
-
return
|
|
811
|
-
kind: "GRAPH",
|
|
812
|
-
series,
|
|
813
|
-
chartType: conversion.chartType,
|
|
814
|
-
valueFormat: conversion.valueFormat ?? "number",
|
|
815
|
-
...conversion.currency === void 0 ? {} : { currency: conversion.currency },
|
|
816
|
-
xLabel: columnName(table, conversion.label)
|
|
817
|
-
});
|
|
1259
|
+
return Object.keys(out).length === 0 ? void 0 : out;
|
|
818
1260
|
}
|
|
819
|
-
function
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
{ label: conversion.labelHeading ?? "Name" },
|
|
824
|
-
{ label: conversion.valueHeading ?? "Value", numeric: true }
|
|
825
|
-
],
|
|
826
|
-
rows: list.items.map((item) => [
|
|
827
|
-
{ text: item.label },
|
|
828
|
-
{ text: item.display ?? String(item.value), value: item.value }
|
|
829
|
-
])
|
|
830
|
-
});
|
|
1261
|
+
function widthRange(item) {
|
|
1262
|
+
const min = isWidgetWidth(item.minWidth) ? item.minWidth : MIN_WIDGET_WIDTH;
|
|
1263
|
+
const max = isWidgetWidth(item.maxWidth) ? item.maxWidth : MAX_WIDGET_WIDTH;
|
|
1264
|
+
return { min, max: Math.max(min, max) };
|
|
831
1265
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
{ text: point.label },
|
|
848
|
-
...graph.series.map((series) => {
|
|
849
|
-
const value = series.points[index]?.value;
|
|
850
|
-
return value === void 0 ? { text: "\u2014" } : { text: formatValue(value, graph.valueFormat, format), value };
|
|
851
|
-
})
|
|
852
|
-
])
|
|
853
|
-
});
|
|
1266
|
+
var WIDTH_SHARES = {
|
|
1267
|
+
2: "1/6",
|
|
1268
|
+
3: "1/4",
|
|
1269
|
+
4: "1/3",
|
|
1270
|
+
5: "5/12",
|
|
1271
|
+
6: "1/2",
|
|
1272
|
+
7: "7/12",
|
|
1273
|
+
8: "2/3",
|
|
1274
|
+
9: "3/4",
|
|
1275
|
+
10: "5/6",
|
|
1276
|
+
11: "11/12",
|
|
1277
|
+
12: "Full"
|
|
1278
|
+
};
|
|
1279
|
+
function widthShareLabel(width) {
|
|
1280
|
+
return WIDTH_SHARES[width] ?? `${width}/12`;
|
|
854
1281
|
}
|
|
855
|
-
function
|
|
856
|
-
return
|
|
857
|
-
kind: "GRAPH",
|
|
858
|
-
series: [
|
|
859
|
-
{
|
|
860
|
-
name: conversion.seriesName ?? "Value",
|
|
861
|
-
points: list.items.map((item) => ({
|
|
862
|
-
label: item.label,
|
|
863
|
-
value: item.value
|
|
864
|
-
}))
|
|
865
|
-
}
|
|
866
|
-
],
|
|
867
|
-
chartType: conversion.chartType,
|
|
868
|
-
valueFormat: conversion.valueFormat ?? "number",
|
|
869
|
-
...conversion.currency === void 0 ? {} : { currency: conversion.currency }
|
|
870
|
-
});
|
|
1282
|
+
function isOptionsLocked(item) {
|
|
1283
|
+
return resolveWidgetLock(item).options;
|
|
871
1284
|
}
|
|
872
|
-
function
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
1285
|
+
function fillWith(fill, flexible) {
|
|
1286
|
+
const heightToo = fill === "height" || fill === "both";
|
|
1287
|
+
if (flexible) return heightToo ? "both" : "width";
|
|
1288
|
+
return heightToo ? "height" : void 0;
|
|
1289
|
+
}
|
|
1290
|
+
function isFlexible(item, settings) {
|
|
1291
|
+
if (settings?.flexible !== void 0) return settings.flexible;
|
|
1292
|
+
return item.fill === "width" || item.fill === "both";
|
|
1293
|
+
}
|
|
1294
|
+
function applySettings(definition, settings) {
|
|
1295
|
+
if (settings === void 0) return definition;
|
|
1296
|
+
const range = widthRange(definition);
|
|
1297
|
+
const width = settings.width === void 0 ? void 0 : Math.min(range.max, Math.max(range.min, settings.width));
|
|
1298
|
+
const title = settings.title === void 0 || settings.title.trim() === "" ? void 0 : settings.title;
|
|
1299
|
+
const flexibleChanged = settings.flexible !== void 0 && settings.flexible !== isFlexible(definition, void 0);
|
|
1300
|
+
if (title === void 0 && width === void 0 && !flexibleChanged) {
|
|
1301
|
+
return definition;
|
|
883
1302
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1303
|
+
const next = { ...definition };
|
|
1304
|
+
if (title !== void 0) next.title = title;
|
|
1305
|
+
if (width !== void 0) next.width = width;
|
|
1306
|
+
if (flexibleChanged) {
|
|
1307
|
+
const fill = fillWith(
|
|
1308
|
+
definition.fill,
|
|
1309
|
+
settings.flexible === true
|
|
887
1310
|
);
|
|
1311
|
+
if (fill === void 0) {
|
|
1312
|
+
delete next.fill;
|
|
1313
|
+
} else {
|
|
1314
|
+
next.fill = fill;
|
|
1315
|
+
}
|
|
888
1316
|
}
|
|
889
|
-
|
|
890
|
-
case "tableToBarList":
|
|
891
|
-
return tableToBarList(data, conversion, context);
|
|
892
|
-
case "tableToGraph":
|
|
893
|
-
return tableToGraph(data, conversion, context);
|
|
894
|
-
case "barListToTable":
|
|
895
|
-
return barListToTable(data, conversion);
|
|
896
|
-
case "graphToTable":
|
|
897
|
-
return graphToTable(data, context);
|
|
898
|
-
case "barListToGraph":
|
|
899
|
-
return barListToGraph(data, conversion);
|
|
900
|
-
}
|
|
1317
|
+
return next;
|
|
901
1318
|
}
|
|
902
|
-
function
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
let indexes;
|
|
906
|
-
if (conversion.type === "tableToBarList") {
|
|
907
|
-
indexes = [conversion.label, conversion.value];
|
|
908
|
-
} else if (conversion.type === "tableToGraph") {
|
|
909
|
-
indexes = [conversion.label, ...conversion.values];
|
|
910
|
-
} else {
|
|
911
|
-
return "";
|
|
912
|
-
}
|
|
913
|
-
const names = indexes.map(
|
|
914
|
-
(index) => data.columns[index]?.label ?? `#${index + 1}`
|
|
1319
|
+
function effectiveDefinitions(definitions, layout, options = {}) {
|
|
1320
|
+
return definitions.map(
|
|
1321
|
+
(definition) => options.overrideLocks !== true && isOptionsLocked(definition) ? definition : applySettings(definition, layout.settings?.[definition.key])
|
|
915
1322
|
);
|
|
916
|
-
return `Shows ${names.join(", ")} only.`;
|
|
917
1323
|
}
|
|
918
|
-
function
|
|
919
|
-
const
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
};
|
|
926
|
-
if (widget.status === "loading") return { ...widget, definition };
|
|
927
|
-
if (widget.status === "empty") return { ...widget, definition };
|
|
928
|
-
if (widget.status === "error") return { ...widget, definition };
|
|
929
|
-
const converted = convertView(
|
|
930
|
-
widget.definition.key,
|
|
931
|
-
view,
|
|
932
|
-
widget.data,
|
|
933
|
-
options
|
|
934
|
-
);
|
|
935
|
-
if (!converted.ok) {
|
|
936
|
-
return {
|
|
937
|
-
definition,
|
|
938
|
-
status: "error",
|
|
939
|
-
error: converted.error,
|
|
940
|
-
...widget.options === void 0 ? {} : { options: widget.options }
|
|
941
|
-
};
|
|
1324
|
+
function widgetOptionValues(definition, settings) {
|
|
1325
|
+
const declared = definition.options ?? [];
|
|
1326
|
+
const values = {};
|
|
1327
|
+
for (const option of declared) {
|
|
1328
|
+
const picked = settings?.options?.[option.key];
|
|
1329
|
+
const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked)) ?? defaultOptionValue(option);
|
|
1330
|
+
if (value !== void 0) values[option.key] = value;
|
|
942
1331
|
}
|
|
943
|
-
return
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// src/core/options.ts
|
|
947
|
-
var DATE_RANGE_PRESETS = [
|
|
948
|
-
"today",
|
|
949
|
-
"yesterday",
|
|
950
|
-
"last7",
|
|
951
|
-
"last30",
|
|
952
|
-
"last90",
|
|
953
|
-
"thisMonth",
|
|
954
|
-
"lastMonth",
|
|
955
|
-
"thisYear",
|
|
956
|
-
"lastYear"
|
|
957
|
-
];
|
|
958
|
-
var WIDGET_OPTION_TYPES = [
|
|
959
|
-
"choice",
|
|
960
|
-
"number",
|
|
961
|
-
"boolean",
|
|
962
|
-
"text",
|
|
963
|
-
"dateRange",
|
|
964
|
-
"color",
|
|
965
|
-
"sort",
|
|
966
|
-
"columns"
|
|
967
|
-
];
|
|
968
|
-
var MAX_WIDGET_OPTIONS = 20;
|
|
969
|
-
var MAX_TEXT_OPTION_LENGTH = 500;
|
|
970
|
-
var OPTION_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,39}$/;
|
|
971
|
-
var COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
972
|
-
var INTERVAL_PATTERN = /^(\d{4}-\d{2}-\d{2})\/(\d{4}-\d{2}-\d{2})$/;
|
|
973
|
-
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
974
|
-
function isColumnKeyFor(kind, key) {
|
|
975
|
-
if (kind === "TABLE") return /^c\d+$/.test(key);
|
|
976
|
-
if (kind === "BAR_LIST") return key === "label" || key === "value";
|
|
977
|
-
return false;
|
|
978
|
-
}
|
|
979
|
-
function isClientApplied(option) {
|
|
980
|
-
return (option.type === "sort" || option.type === "columns") && option.apply === "client";
|
|
1332
|
+
return values;
|
|
981
1333
|
}
|
|
982
|
-
function
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1334
|
+
function optionValuesFromLayout(definitions, layout, options = {}) {
|
|
1335
|
+
const out = {};
|
|
1336
|
+
for (const definition of definitions) {
|
|
1337
|
+
if (definition.options === void 0 || definition.options.length === 0) {
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
const locked = options.overrideLocks !== true && isOptionsLocked(definition);
|
|
1341
|
+
out[definition.key] = locked ? {} : layout.settings?.[definition.key]?.options ?? {};
|
|
989
1342
|
}
|
|
990
|
-
|
|
991
|
-
timeZone,
|
|
992
|
-
year: "numeric",
|
|
993
|
-
month: "2-digit",
|
|
994
|
-
day: "2-digit"
|
|
995
|
-
}).formatToParts(new Date(now));
|
|
996
|
-
const pick = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
|
|
997
|
-
return { year: pick("year"), month: pick("month"), day: pick("day") };
|
|
998
|
-
}
|
|
999
|
-
function toIso(epochMs) {
|
|
1000
|
-
return new Date(epochMs).toISOString().slice(0, 10);
|
|
1343
|
+
return out;
|
|
1001
1344
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
if (match === null) return void 0;
|
|
1006
|
-
const epoch = Date.UTC(
|
|
1007
|
-
Number(match[1]),
|
|
1008
|
-
Number(match[2]) - 1,
|
|
1009
|
-
Number(match[3])
|
|
1010
|
-
);
|
|
1011
|
-
return toIso(epoch) === text ? epoch : void 0;
|
|
1345
|
+
function sameSettings(a, b) {
|
|
1346
|
+
if (a === b) return true;
|
|
1347
|
+
return a?.title === b?.title && a?.width === b?.width && a?.flexible === b?.flexible && a?.view === b?.view && sameOptionValues(a?.options ?? {}, b?.options ?? {});
|
|
1012
1348
|
}
|
|
1013
|
-
function
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
case "last90":
|
|
1039
|
-
return range(todayMs - 89 * DAY_MS, todayMs);
|
|
1040
|
-
case "thisMonth":
|
|
1041
|
-
return range(Date.UTC(today.year, today.month - 1, 1), todayMs);
|
|
1042
|
-
case "lastMonth":
|
|
1043
|
-
return range(
|
|
1044
|
-
Date.UTC(today.year, today.month - 2, 1),
|
|
1045
|
-
Date.UTC(today.year, today.month - 1, 1) - DAY_MS
|
|
1046
|
-
);
|
|
1047
|
-
case "thisYear":
|
|
1048
|
-
return range(Date.UTC(today.year, 0, 1), todayMs);
|
|
1049
|
-
case "lastYear":
|
|
1050
|
-
return range(
|
|
1051
|
-
Date.UTC(today.year - 1, 0, 1),
|
|
1052
|
-
Date.UTC(today.year, 0, 1) - DAY_MS
|
|
1053
|
-
);
|
|
1349
|
+
function setWidgetSettings(layout, key, patch) {
|
|
1350
|
+
const before = layout.settings?.[key];
|
|
1351
|
+
const draft = { ...before };
|
|
1352
|
+
for (const field of [
|
|
1353
|
+
"title",
|
|
1354
|
+
"width",
|
|
1355
|
+
"flexible",
|
|
1356
|
+
"options",
|
|
1357
|
+
"view"
|
|
1358
|
+
]) {
|
|
1359
|
+
if (!(field in patch)) continue;
|
|
1360
|
+
const value = patch[field];
|
|
1361
|
+
if (value === void 0) {
|
|
1362
|
+
delete draft[field];
|
|
1363
|
+
} else {
|
|
1364
|
+
draft[field] = value;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
const merged = parseOne(draft);
|
|
1368
|
+
if (sameSettings(before, merged)) return layout;
|
|
1369
|
+
const rest = { ...layout.settings };
|
|
1370
|
+
if (merged === void 0) {
|
|
1371
|
+
delete rest[key];
|
|
1372
|
+
} else {
|
|
1373
|
+
rest[key] = merged;
|
|
1054
1374
|
}
|
|
1375
|
+
const { settings: _previous, ...others } = layout;
|
|
1376
|
+
return Object.keys(rest).length === 0 ? others : { ...others, settings: rest };
|
|
1055
1377
|
}
|
|
1056
|
-
function
|
|
1057
|
-
if (
|
|
1058
|
-
|
|
1059
|
-
|
|
1378
|
+
function clearWidgetSettings(layout, key) {
|
|
1379
|
+
if (layout.settings?.[key] === void 0) return layout;
|
|
1380
|
+
return setWidgetSettings(layout, key, {
|
|
1381
|
+
title: void 0,
|
|
1382
|
+
width: void 0,
|
|
1383
|
+
flexible: void 0,
|
|
1384
|
+
options: void 0,
|
|
1385
|
+
view: void 0
|
|
1386
|
+
});
|
|
1060
1387
|
}
|
|
1061
|
-
function
|
|
1062
|
-
|
|
1063
|
-
if (separator <= 0) return void 0;
|
|
1064
|
-
const column = value.slice(0, separator);
|
|
1065
|
-
const direction = value.slice(separator + 1);
|
|
1066
|
-
if (direction !== "asc" && direction !== "desc") return void 0;
|
|
1067
|
-
return { column, direction };
|
|
1388
|
+
function normalizeTitle(title) {
|
|
1389
|
+
return title.normalize("NFKC").trim().toLowerCase();
|
|
1068
1390
|
}
|
|
1069
|
-
function
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1391
|
+
function isTitleInUse(definitions, layout, key, title) {
|
|
1392
|
+
const wanted = normalizeTitle(title);
|
|
1393
|
+
if (wanted === "") return false;
|
|
1394
|
+
return effectiveDefinitions(definitions, layout, {
|
|
1395
|
+
overrideLocks: true
|
|
1396
|
+
}).some(
|
|
1397
|
+
(other) => other.key !== key && normalizeTitle(other.title ?? other.key) === wanted
|
|
1073
1398
|
);
|
|
1074
|
-
if (sortOption === void 0) return void 0;
|
|
1075
|
-
const chosen = values[sortOption.key];
|
|
1076
|
-
return typeof chosen === "string" ? parseSortValue(chosen) : void 0;
|
|
1077
1399
|
}
|
|
1078
|
-
function
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1400
|
+
function uniqueTitle(taken, title) {
|
|
1401
|
+
if (!taken.has(normalizeTitle(title))) return title;
|
|
1402
|
+
for (let n = 2; ; n += 1) {
|
|
1403
|
+
const suffix = ` (#${n})`;
|
|
1404
|
+
const candidate = `${title.slice(0, MAX_WIDGET_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
1405
|
+
if (!taken.has(normalizeTitle(candidate))) return candidate;
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
function normalizeSettings(definitions, layout, options = {}) {
|
|
1409
|
+
const settings = layout.settings;
|
|
1410
|
+
if (settings === void 0) return layout;
|
|
1411
|
+
const cleaned = {};
|
|
1412
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1413
|
+
for (const definition of definitions) {
|
|
1414
|
+
const renamed = settings[definition.key]?.title !== void 0 && (options.overrideLocks === true || !isOptionsLocked(definition));
|
|
1415
|
+
if (!renamed) taken.add(normalizeTitle(definition.title ?? definition.key));
|
|
1416
|
+
}
|
|
1417
|
+
for (const definition of definitions) {
|
|
1418
|
+
const one = settings[definition.key];
|
|
1419
|
+
if (one === void 0) continue;
|
|
1420
|
+
if (options.overrideLocks !== true && isOptionsLocked(definition)) continue;
|
|
1421
|
+
const range = widthRange(definition);
|
|
1422
|
+
const chosen = {};
|
|
1423
|
+
for (const option of definition.options ?? []) {
|
|
1424
|
+
const picked = one.options?.[option.key];
|
|
1425
|
+
if (picked === void 0) continue;
|
|
1426
|
+
const value = coerceOptionValue(option, picked);
|
|
1427
|
+
if (value !== void 0) {
|
|
1428
|
+
chosen[option.key] = option.type === "dateRange" ? picked : value;
|
|
1095
1429
|
}
|
|
1096
|
-
return resolveDateRange(value, context);
|
|
1097
|
-
}
|
|
1098
|
-
case "color":
|
|
1099
|
-
return typeof value === "string" && COLOR_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
1100
|
-
case "sort": {
|
|
1101
|
-
if (typeof value !== "string") return void 0;
|
|
1102
|
-
const sort = parseSortValue(value);
|
|
1103
|
-
return sort !== void 0 && option.columns.some((column) => column.key === sort.column) ? value : void 0;
|
|
1104
1430
|
}
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1114
|
-
seen.add(key);
|
|
1431
|
+
let title = one.title;
|
|
1432
|
+
if (title !== void 0) {
|
|
1433
|
+
title = title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH);
|
|
1434
|
+
if (title === "") {
|
|
1435
|
+
title = void 0;
|
|
1436
|
+
} else {
|
|
1437
|
+
title = uniqueTitle(taken, title);
|
|
1438
|
+
taken.add(normalizeTitle(title));
|
|
1115
1439
|
}
|
|
1116
|
-
|
|
1440
|
+
}
|
|
1441
|
+
const next = {
|
|
1442
|
+
...title === void 0 ? {} : { title },
|
|
1443
|
+
...one.width === void 0 ? {} : { width: Math.min(range.max, Math.max(range.min, one.width)) },
|
|
1444
|
+
...one.flexible === void 0 ? {} : { flexible: one.flexible },
|
|
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 } : {}
|
|
1447
|
+
};
|
|
1448
|
+
if (Object.keys(next).length > 0) {
|
|
1449
|
+
cleaned[definition.key] = sameSettings(one, next) ? one : next;
|
|
1117
1450
|
}
|
|
1118
1451
|
}
|
|
1452
|
+
const keys = Object.keys(cleaned);
|
|
1453
|
+
const unchanged = keys.length === Object.keys(settings).length && keys.every((key) => cleaned[key] === settings[key]);
|
|
1454
|
+
if (unchanged) return layout;
|
|
1455
|
+
const { settings: _previous, ...others } = layout;
|
|
1456
|
+
return keys.length === 0 ? others : { ...others, settings: cleaned };
|
|
1119
1457
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
return option.default;
|
|
1126
|
-
case "columns":
|
|
1127
|
-
return [...option.default];
|
|
1128
|
-
default:
|
|
1129
|
-
return option.default;
|
|
1130
|
-
}
|
|
1458
|
+
|
|
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);
|
|
1131
1463
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
const
|
|
1135
|
-
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
const
|
|
1139
|
-
const
|
|
1140
|
-
if (
|
|
1464
|
+
function parseClones(raw) {
|
|
1465
|
+
if (!Array.isArray(raw)) return void 0;
|
|
1466
|
+
const seen = /* @__PURE__ */ new Set();
|
|
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 });
|
|
1141
1477
|
}
|
|
1142
|
-
return
|
|
1478
|
+
return clones.length === 0 ? void 0 : clones;
|
|
1143
1479
|
}
|
|
1144
|
-
function
|
|
1145
|
-
if (
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
return a === b;
|
|
1480
|
+
function serializeClones(clones) {
|
|
1481
|
+
if (clones === void 0 || clones.length === 0) return void 0;
|
|
1482
|
+
return clones.map(({ key, from }) => ({ key, from }));
|
|
1149
1483
|
}
|
|
1150
|
-
function
|
|
1151
|
-
const
|
|
1152
|
-
return
|
|
1484
|
+
function cloneNumber(key) {
|
|
1485
|
+
const match = /#(\d+)$/.exec(key);
|
|
1486
|
+
return match === null ? void 0 : Number(match[1]);
|
|
1153
1487
|
}
|
|
1154
|
-
function
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
);
|
|
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;
|
|
1501
|
+
}
|
|
1502
|
+
seen.add(clone.key);
|
|
1503
|
+
kept.push(clone);
|
|
1163
1504
|
}
|
|
1164
|
-
|
|
1165
|
-
return
|
|
1166
|
-
}
|
|
1167
|
-
function tableColumnIndex(key) {
|
|
1168
|
-
const match = /^c(\d+)$/.exec(key);
|
|
1169
|
-
return match === null ? void 0 : Number(match[1]);
|
|
1505
|
+
if (kept.length === 0) return void 0;
|
|
1506
|
+
return kept.length === clones.length && kept.every((c, i) => c === clones[i]) ? clones : kept;
|
|
1170
1507
|
}
|
|
1171
|
-
function
|
|
1172
|
-
const
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
return order * sign || a.index - b.index;
|
|
1176
|
-
}).map(({ item }) => item);
|
|
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}`;
|
|
1177
1512
|
}
|
|
1178
|
-
function
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1513
|
+
function withClones(definitions, layout, options = {}) {
|
|
1514
|
+
const valid = cleanClones(
|
|
1515
|
+
definitions,
|
|
1516
|
+
layout.clones,
|
|
1517
|
+
options
|
|
1182
1518
|
);
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
);
|
|
1186
|
-
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
page: 1,
|
|
1200
|
-
pageSize: Math.max(1, next.rows.length),
|
|
1201
|
-
sort
|
|
1202
|
-
});
|
|
1203
|
-
if (sorted.ok) next = { ...next, rows: sorted.value.rows };
|
|
1204
|
-
}
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
if (Array.isArray(columnsValue) && next.kind === "TABLE") {
|
|
1208
|
-
const table = next;
|
|
1209
|
-
const indexes = columnsValue.map(tableColumnIndex).filter(
|
|
1210
|
-
(index) => index !== void 0 && index < table.columns.length
|
|
1211
|
-
);
|
|
1212
|
-
if (indexes.length > 0) {
|
|
1213
|
-
next = {
|
|
1214
|
-
...table,
|
|
1215
|
-
columns: indexes.map((i) => table.columns[i]),
|
|
1216
|
-
rows: table.rows.map((row) => indexes.map((i) => row[i]))
|
|
1217
|
-
};
|
|
1218
|
-
}
|
|
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
|
+
});
|
|
1219
1535
|
}
|
|
1220
|
-
return
|
|
1536
|
+
return out;
|
|
1221
1537
|
}
|
|
1222
1538
|
|
|
1223
|
-
// src/core/
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
var MAX_COLUMN_LIST = 100;
|
|
1227
|
-
var MAX_VALUE_LENGTH = 500;
|
|
1228
|
-
function isRecord(value) {
|
|
1229
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1539
|
+
// src/core/compat.ts
|
|
1540
|
+
function isStringArray(value) {
|
|
1541
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
1230
1542
|
}
|
|
1231
|
-
function
|
|
1232
|
-
if (typeof value
|
|
1233
|
-
return value
|
|
1543
|
+
function upgradeLegacyWidgetData(value) {
|
|
1544
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1545
|
+
return value;
|
|
1234
1546
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
(
|
|
1241
|
-
|
|
1547
|
+
const record = value;
|
|
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
|
+
};
|
|
1242
1555
|
}
|
|
1243
|
-
|
|
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;
|
|
1244
1566
|
}
|
|
1245
|
-
function
|
|
1246
|
-
if (!
|
|
1247
|
-
|
|
1248
|
-
const width = raw["width"];
|
|
1249
|
-
const flexible = raw["flexible"];
|
|
1250
|
-
const options = raw["options"];
|
|
1251
|
-
const view = raw["view"];
|
|
1252
|
-
const cleanTitle = typeof title === "string" ? title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH) : "";
|
|
1253
|
-
const chosen = {};
|
|
1254
|
-
if (isRecord(options)) {
|
|
1255
|
-
for (const [key, value] of Object.entries(options)) {
|
|
1256
|
-
const parsed = parseOptionValue(value);
|
|
1257
|
-
if (parsed !== void 0) chosen[key] = parsed;
|
|
1258
|
-
}
|
|
1567
|
+
function fromLegacyDefinition(legacy) {
|
|
1568
|
+
if (!isWidgetKind(legacy.viewType)) {
|
|
1569
|
+
return null;
|
|
1259
1570
|
}
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
...
|
|
1264
|
-
|
|
1265
|
-
|
|
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
|
|
1266
1577
|
};
|
|
1267
|
-
return Object.keys(settings).length === 0 ? void 0 : settings;
|
|
1268
1578
|
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
)
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
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);
|
|
1279
1594
|
}
|
|
1280
|
-
return
|
|
1595
|
+
return [...seen];
|
|
1281
1596
|
}
|
|
1282
|
-
function
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
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) {
|
|
1625
|
+
return {
|
|
1626
|
+
order: EMPTY_LAYOUT.order,
|
|
1627
|
+
hidden: EMPTY_LAYOUT.hidden,
|
|
1628
|
+
minimized: EMPTY_LAYOUT.minimized,
|
|
1629
|
+
pages,
|
|
1630
|
+
...extras
|
|
1294
1631
|
};
|
|
1295
|
-
if (Object.keys(piece).length > 0) out[key] = piece;
|
|
1296
1632
|
}
|
|
1297
|
-
|
|
1633
|
+
if (order.length === 0 && hidden.length === 0 && minimized.length === 0) {
|
|
1634
|
+
return Object.keys(extras).length === 0 ? EMPTY_LAYOUT : { ...EMPTY_LAYOUT, ...extras };
|
|
1635
|
+
}
|
|
1636
|
+
return { order, hidden, minimized, ...extras };
|
|
1298
1637
|
}
|
|
1299
|
-
function
|
|
1300
|
-
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1638
|
+
function parsePages(value) {
|
|
1639
|
+
if (!Array.isArray(value)) return [];
|
|
1640
|
+
const pages = [];
|
|
1641
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1642
|
+
for (const entry of value.slice(0, MAX_PARSED_PAGES)) {
|
|
1643
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
const record = entry;
|
|
1647
|
+
const key = record["key"];
|
|
1648
|
+
if (typeof key !== "string" || key === "" || keys.has(key)) continue;
|
|
1649
|
+
keys.add(key);
|
|
1650
|
+
const rawTitle = record["title"];
|
|
1651
|
+
const title = typeof rawTitle === "string" && rawTitle.trim() !== "" ? rawTitle.trim().slice(0, MAX_PAGE_TITLE_LENGTH) : `Page ${pages.length + 1}`;
|
|
1652
|
+
const maxRows = record["maxRows"];
|
|
1653
|
+
const held = /* @__PURE__ */ new Set();
|
|
1654
|
+
for (const earlier of pages) {
|
|
1655
|
+
for (const k of [...earlier.order, ...earlier.hidden]) held.add(k);
|
|
1656
|
+
}
|
|
1657
|
+
const keep = (list) => uniqueStrings(list).filter((k) => !held.has(k));
|
|
1658
|
+
pages.push({
|
|
1659
|
+
key,
|
|
1660
|
+
title,
|
|
1661
|
+
order: keep(record["order"]),
|
|
1662
|
+
hidden: keep(record["hidden"]),
|
|
1663
|
+
minimized: keep(record["minimized"]),
|
|
1664
|
+
...typeof maxRows === "number" && Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_PAGE_ROWS ? { maxRows } : {}
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
return pages;
|
|
1303
1668
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1669
|
+
function serializeLayout(layout) {
|
|
1670
|
+
return JSON.stringify({
|
|
1671
|
+
order: [...layout.order],
|
|
1672
|
+
hidden: [...layout.hidden],
|
|
1673
|
+
minimized: [...layout.minimized],
|
|
1674
|
+
...layout.pages === void 0 || layout.pages.length === 0 ? {} : {
|
|
1675
|
+
pages: layout.pages.map((page) => ({
|
|
1676
|
+
key: page.key,
|
|
1677
|
+
title: page.title,
|
|
1678
|
+
order: [...page.order],
|
|
1679
|
+
hidden: [...page.hidden],
|
|
1680
|
+
minimized: [...page.minimized],
|
|
1681
|
+
...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
|
|
1682
|
+
}))
|
|
1683
|
+
},
|
|
1684
|
+
...serializeSettings(layout.settings) === void 0 ? {} : { settings: serializeSettings(layout.settings) },
|
|
1685
|
+
...serializeClones(layout.clones) === void 0 ? {} : { clones: serializeClones(layout.clones) }
|
|
1686
|
+
});
|
|
1319
1687
|
}
|
|
1320
|
-
function
|
|
1321
|
-
return
|
|
1688
|
+
function bySortOrder(items) {
|
|
1689
|
+
return items.map((item, index) => ({ item, index })).sort(
|
|
1690
|
+
(a, b) => (a.item.sortOrder ?? 0) - (b.item.sortOrder ?? 0) || a.index - b.index
|
|
1691
|
+
).map(({ item }) => item);
|
|
1322
1692
|
}
|
|
1323
|
-
function
|
|
1324
|
-
|
|
1325
|
-
if (flexible) return heightToo ? "both" : "width";
|
|
1326
|
-
return heightToo ? "height" : void 0;
|
|
1327
|
-
}
|
|
1328
|
-
function isFlexible(item, settings) {
|
|
1329
|
-
if (settings?.flexible !== void 0) return settings.flexible;
|
|
1330
|
-
return item.fill === "width" || item.fill === "both";
|
|
1693
|
+
function isPinned(item) {
|
|
1694
|
+
return resolveWidgetLock(item).move;
|
|
1331
1695
|
}
|
|
1332
|
-
function
|
|
1333
|
-
|
|
1334
|
-
const
|
|
1335
|
-
const
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
}
|
|
1341
|
-
const next = { ...definition };
|
|
1342
|
-
if (title !== void 0) next.title = title;
|
|
1343
|
-
if (width !== void 0) next.width = width;
|
|
1344
|
-
if (flexibleChanged) {
|
|
1345
|
-
const fill = fillWith(
|
|
1346
|
-
definition.fill,
|
|
1347
|
-
settings.flexible === true
|
|
1348
|
-
);
|
|
1349
|
-
if (fill === void 0) {
|
|
1350
|
-
delete next.fill;
|
|
1351
|
-
} else {
|
|
1352
|
-
next.fill = fill;
|
|
1696
|
+
function viewerOrder(visible, layout) {
|
|
1697
|
+
const byKey = new Map(visible.map((d) => [d.key, d]));
|
|
1698
|
+
const ordered = [];
|
|
1699
|
+
for (const key of layout.order) {
|
|
1700
|
+
const definition = byKey.get(key);
|
|
1701
|
+
if (definition !== void 0) {
|
|
1702
|
+
ordered.push(definition);
|
|
1703
|
+
byKey.delete(key);
|
|
1353
1704
|
}
|
|
1354
1705
|
}
|
|
1355
|
-
|
|
1706
|
+
for (const definition of bySortOrder(visible)) {
|
|
1707
|
+
if (byKey.has(definition.key)) ordered.push(definition);
|
|
1708
|
+
}
|
|
1709
|
+
return ordered;
|
|
1356
1710
|
}
|
|
1357
|
-
function
|
|
1358
|
-
|
|
1359
|
-
(
|
|
1711
|
+
function placeAroundPinned(visible, free) {
|
|
1712
|
+
const slots = bySortOrder(visible).map(
|
|
1713
|
+
(item) => isPinned(item) ? item : void 0
|
|
1360
1714
|
);
|
|
1715
|
+
let next = 0;
|
|
1716
|
+
return slots.map((pinned) => pinned ?? free[next++]);
|
|
1361
1717
|
}
|
|
1362
|
-
function
|
|
1363
|
-
const
|
|
1364
|
-
const
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
}
|
|
1372
|
-
function optionValuesFromLayout(definitions, layout, options = {}) {
|
|
1373
|
-
const out = {};
|
|
1374
|
-
for (const definition of definitions) {
|
|
1375
|
-
if (definition.options === void 0 || definition.options.length === 0) {
|
|
1376
|
-
continue;
|
|
1377
|
-
}
|
|
1378
|
-
const locked = options.overrideLocks !== true && isOptionsLocked(definition);
|
|
1379
|
-
out[definition.key] = locked ? {} : layout.settings?.[definition.key]?.options ?? {};
|
|
1380
|
-
}
|
|
1381
|
-
return out;
|
|
1718
|
+
function visibleWidgets(definitions, layout) {
|
|
1719
|
+
const hidden = new Set(layout.hidden);
|
|
1720
|
+
const visible = definitions.filter((d) => !hidden.has(d.key));
|
|
1721
|
+
const ordered = viewerOrder(visible, layout);
|
|
1722
|
+
if (!visible.some(isPinned)) return ordered;
|
|
1723
|
+
return placeAroundPinned(
|
|
1724
|
+
visible,
|
|
1725
|
+
ordered.filter((item) => !isPinned(item))
|
|
1726
|
+
);
|
|
1382
1727
|
}
|
|
1383
|
-
function
|
|
1384
|
-
|
|
1385
|
-
return
|
|
1728
|
+
function hiddenWidgets(definitions, layout) {
|
|
1729
|
+
const hidden = new Set(layout.hidden);
|
|
1730
|
+
return bySortOrder(definitions.filter((d) => hidden.has(d.key)));
|
|
1386
1731
|
}
|
|
1387
|
-
function
|
|
1388
|
-
const
|
|
1389
|
-
const
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
"flexible",
|
|
1394
|
-
"options",
|
|
1395
|
-
"view"
|
|
1396
|
-
]) {
|
|
1397
|
-
if (!(field in patch)) continue;
|
|
1398
|
-
const value = patch[field];
|
|
1399
|
-
if (value === void 0) {
|
|
1400
|
-
delete draft[field];
|
|
1401
|
-
} else {
|
|
1402
|
-
draft[field] = value;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
const merged = parseOne(draft);
|
|
1406
|
-
if (sameSettings(before, merged)) return layout;
|
|
1407
|
-
const rest = { ...layout.settings };
|
|
1408
|
-
if (merged === void 0) {
|
|
1409
|
-
delete rest[key];
|
|
1410
|
-
} else {
|
|
1411
|
-
rest[key] = merged;
|
|
1732
|
+
function moveWidget(definitions, layout, key, toIndex) {
|
|
1733
|
+
const current = visibleWidgets(definitions, layout);
|
|
1734
|
+
const fromIndex = current.findIndex((d) => d.key === key);
|
|
1735
|
+
const mover = current[fromIndex];
|
|
1736
|
+
if (mover === void 0 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex || isPinned(mover) || isPinned(current[toIndex])) {
|
|
1737
|
+
return layout;
|
|
1412
1738
|
}
|
|
1413
|
-
const
|
|
1414
|
-
|
|
1739
|
+
const freeSlots = freeSlotIndexes(current);
|
|
1740
|
+
const free = current.filter((item) => !isPinned(item));
|
|
1741
|
+
const fromFree = freeSlots.indexOf(fromIndex);
|
|
1742
|
+
const toFree = freeSlots.indexOf(toIndex);
|
|
1743
|
+
free.splice(fromFree, 1);
|
|
1744
|
+
free.splice(toFree, 0, mover);
|
|
1745
|
+
const next = placeAroundPinned(current, free);
|
|
1746
|
+
return { ...layout, order: next.map((d) => d.key) };
|
|
1415
1747
|
}
|
|
1416
|
-
function
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
width: void 0,
|
|
1421
|
-
flexible: void 0,
|
|
1422
|
-
options: void 0,
|
|
1423
|
-
view: void 0
|
|
1748
|
+
function freeSlotIndexes(ordered) {
|
|
1749
|
+
const slots = [];
|
|
1750
|
+
ordered.forEach((item, index) => {
|
|
1751
|
+
if (!isPinned(item)) slots.push(index);
|
|
1424
1752
|
});
|
|
1753
|
+
return slots;
|
|
1425
1754
|
}
|
|
1426
|
-
function
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
const
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
(
|
|
1755
|
+
function moveWidgetBy(definitions, layout, key, offset) {
|
|
1756
|
+
const current = visibleWidgets(definitions, layout);
|
|
1757
|
+
const fromIndex = current.findIndex((d) => d.key === key);
|
|
1758
|
+
if (fromIndex === -1) return layout;
|
|
1759
|
+
const freeSlots = freeSlotIndexes(current);
|
|
1760
|
+
const fromFree = freeSlots.indexOf(fromIndex);
|
|
1761
|
+
if (fromFree === -1) return layout;
|
|
1762
|
+
const toFree = Math.max(
|
|
1763
|
+
0,
|
|
1764
|
+
Math.min(freeSlots.length - 1, fromFree + Math.trunc(offset))
|
|
1436
1765
|
);
|
|
1766
|
+
return moveWidget(definitions, layout, key, freeSlots[toFree]);
|
|
1437
1767
|
}
|
|
1438
|
-
function
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1768
|
+
function moveWidgetUp(definitions, layout, key) {
|
|
1769
|
+
return moveWidgetBy(definitions, layout, key, -1);
|
|
1770
|
+
}
|
|
1771
|
+
function moveWidgetDown(definitions, layout, key) {
|
|
1772
|
+
return moveWidgetBy(definitions, layout, key, 1);
|
|
1773
|
+
}
|
|
1774
|
+
function isHidden(layout, key) {
|
|
1775
|
+
return layout.hidden.includes(key);
|
|
1776
|
+
}
|
|
1777
|
+
function isMinimized(layout, key) {
|
|
1778
|
+
return layout.minimized.includes(key);
|
|
1779
|
+
}
|
|
1780
|
+
function hideWidget(layout, key) {
|
|
1781
|
+
if (isHidden(layout, key)) return layout;
|
|
1782
|
+
return { ...layout, hidden: [...layout.hidden, key] };
|
|
1783
|
+
}
|
|
1784
|
+
function showWidget(layout, key) {
|
|
1785
|
+
if (!isHidden(layout, key)) return layout;
|
|
1786
|
+
return {
|
|
1787
|
+
...layout,
|
|
1788
|
+
hidden: layout.hidden.filter((k) => k !== key),
|
|
1789
|
+
minimized: layout.minimized.filter((k) => k !== key)
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
function toggleHidden(layout, key) {
|
|
1793
|
+
return isHidden(layout, key) ? showWidget(layout, key) : hideWidget(layout, key);
|
|
1794
|
+
}
|
|
1795
|
+
function minimizeWidget(layout, key) {
|
|
1796
|
+
if (isMinimized(layout, key)) return layout;
|
|
1797
|
+
return { ...layout, minimized: [...layout.minimized, key] };
|
|
1798
|
+
}
|
|
1799
|
+
function restoreWidget(layout, key) {
|
|
1800
|
+
if (!isMinimized(layout, key)) return layout;
|
|
1801
|
+
return { ...layout, minimized: layout.minimized.filter((k) => k !== key) };
|
|
1802
|
+
}
|
|
1803
|
+
function toggleMinimized(layout, key) {
|
|
1804
|
+
return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
|
|
1805
|
+
}
|
|
1806
|
+
function pruneLayout(definitions, layout) {
|
|
1807
|
+
const known = new Set(definitions.map((d) => d.key));
|
|
1808
|
+
const keep = (keys) => keys.every((k) => known.has(k)) ? keys : keys.filter((k) => known.has(k));
|
|
1809
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
1810
|
+
order: keep(lists.order),
|
|
1811
|
+
hidden: keep(lists.hidden),
|
|
1812
|
+
minimized: keep(lists.minimized)
|
|
1813
|
+
}));
|
|
1814
|
+
}
|
|
1815
|
+
function mapLayoutLists(layout, change) {
|
|
1816
|
+
const top = change(layout);
|
|
1817
|
+
const pages = layout.pages?.map((page) => {
|
|
1818
|
+
const next = change(page);
|
|
1819
|
+
return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
|
|
1820
|
+
});
|
|
1821
|
+
const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
|
|
1822
|
+
if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
|
|
1823
|
+
return layout;
|
|
1444
1824
|
}
|
|
1825
|
+
return {
|
|
1826
|
+
...layout,
|
|
1827
|
+
...top,
|
|
1828
|
+
...pages === void 0 ? {} : { pages }
|
|
1829
|
+
};
|
|
1445
1830
|
}
|
|
1446
|
-
function
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
const
|
|
1450
|
-
const
|
|
1831
|
+
function enforceLocks(definitions, layout, options = {}) {
|
|
1832
|
+
if (options.overrideLocks === true) return layout;
|
|
1833
|
+
const noMove = /* @__PURE__ */ new Set();
|
|
1834
|
+
const noHide = /* @__PURE__ */ new Set();
|
|
1835
|
+
const noMinimize = /* @__PURE__ */ new Set();
|
|
1451
1836
|
for (const definition of definitions) {
|
|
1452
|
-
const
|
|
1453
|
-
if (
|
|
1837
|
+
const lock = resolveWidgetLock(definition);
|
|
1838
|
+
if (lock.move) noMove.add(definition.key);
|
|
1839
|
+
if (lock.hide) noHide.add(definition.key);
|
|
1840
|
+
if (lock.minimize) noMinimize.add(definition.key);
|
|
1454
1841
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
for (const option of definition.options ?? []) {
|
|
1462
|
-
const picked = one.options?.[option.key];
|
|
1463
|
-
if (picked === void 0) continue;
|
|
1464
|
-
const value = coerceOptionValue(option, picked);
|
|
1465
|
-
if (value !== void 0) {
|
|
1466
|
-
chosen[option.key] = option.type === "dateRange" ? picked : value;
|
|
1467
|
-
}
|
|
1468
|
-
}
|
|
1469
|
-
let title = one.title;
|
|
1470
|
-
if (title !== void 0) {
|
|
1471
|
-
title = title.trim().slice(0, MAX_WIDGET_TITLE_LENGTH);
|
|
1472
|
-
if (title === "") {
|
|
1473
|
-
title = void 0;
|
|
1474
|
-
} else {
|
|
1475
|
-
title = uniqueTitle(taken, title);
|
|
1476
|
-
taken.add(normalizeTitle(title));
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
const next = {
|
|
1480
|
-
...title === void 0 ? {} : { title },
|
|
1481
|
-
...one.width === void 0 ? {} : { width: Math.min(range.max, Math.max(range.min, one.width)) },
|
|
1482
|
-
...one.flexible === void 0 ? {} : { flexible: one.flexible },
|
|
1483
|
-
...Object.keys(chosen).length === 0 ? {} : { options: chosen },
|
|
1484
|
-
...one.view !== void 0 && (one.view === BASE_VIEW_KEY || findView(definition, one.view) !== void 0) ? { view: one.view } : {}
|
|
1485
|
-
};
|
|
1486
|
-
if (Object.keys(next).length > 0) {
|
|
1487
|
-
cleaned[definition.key] = sameSettings(one, next) ? one : next;
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
const keys = Object.keys(cleaned);
|
|
1491
|
-
const unchanged = keys.length === Object.keys(settings).length && keys.every((key) => cleaned[key] === settings[key]);
|
|
1492
|
-
if (unchanged) return layout;
|
|
1493
|
-
const { settings: _previous, ...others } = layout;
|
|
1494
|
-
return keys.length === 0 ? others : { ...others, settings: cleaned };
|
|
1842
|
+
const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
|
|
1843
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
1844
|
+
order: without2(lists.order, noMove),
|
|
1845
|
+
hidden: without2(lists.hidden, noHide),
|
|
1846
|
+
minimized: without2(lists.minimized, noMinimize)
|
|
1847
|
+
}));
|
|
1495
1848
|
}
|
|
1496
1849
|
|
|
1497
|
-
// src/core/
|
|
1498
|
-
var
|
|
1499
|
-
var
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
hidden: Object.freeze([]),
|
|
1504
|
-
minimized: Object.freeze([])
|
|
1505
|
-
});
|
|
1506
|
-
function uniqueStrings(value) {
|
|
1507
|
-
if (!Array.isArray(value)) return [];
|
|
1508
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1509
|
-
for (const entry of value) {
|
|
1510
|
-
if (typeof entry === "string" && entry !== "") seen.add(entry);
|
|
1511
|
-
}
|
|
1512
|
-
return [...seen];
|
|
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)));
|
|
1513
1856
|
}
|
|
1514
|
-
function
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
return EMPTY_LAYOUT;
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1524
|
-
return EMPTY_LAYOUT;
|
|
1857
|
+
function hasPages(layout) {
|
|
1858
|
+
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
1859
|
+
}
|
|
1860
|
+
function pageList(layout) {
|
|
1861
|
+
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
1862
|
+
return layout.pages;
|
|
1525
1863
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
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
|
+
];
|
|
1873
|
+
}
|
|
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
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
function sameKeys(a, b) {
|
|
1885
|
+
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
1886
|
+
}
|
|
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
|
|
1533
1892
|
);
|
|
1534
|
-
|
|
1535
|
-
|
|
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
|
+
};
|
|
1901
|
+
}
|
|
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;
|
|
1906
|
+
}
|
|
1907
|
+
if (!hasPages(layout)) {
|
|
1536
1908
|
return {
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
...withSettings
|
|
1909
|
+
...layout,
|
|
1910
|
+
order: next.order,
|
|
1911
|
+
hidden: next.hidden,
|
|
1912
|
+
minimized: next.minimized
|
|
1542
1913
|
};
|
|
1543
1914
|
}
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
continue;
|
|
1556
|
-
}
|
|
1557
|
-
const record = entry;
|
|
1558
|
-
const key = record["key"];
|
|
1559
|
-
if (typeof key !== "string" || key === "" || keys.has(key)) continue;
|
|
1560
|
-
keys.add(key);
|
|
1561
|
-
const rawTitle = record["title"];
|
|
1562
|
-
const title = typeof rawTitle === "string" && rawTitle.trim() !== "" ? rawTitle.trim().slice(0, MAX_PAGE_TITLE_LENGTH) : `Page ${pages.length + 1}`;
|
|
1563
|
-
const maxRows = record["maxRows"];
|
|
1564
|
-
const held = /* @__PURE__ */ new Set();
|
|
1565
|
-
for (const earlier of pages) {
|
|
1566
|
-
for (const k of [...earlier.order, ...earlier.hidden]) held.add(k);
|
|
1567
|
-
}
|
|
1568
|
-
const keep = (list) => uniqueStrings(list).filter((k) => !held.has(k));
|
|
1569
|
-
pages.push({
|
|
1570
|
-
key,
|
|
1571
|
-
title,
|
|
1572
|
-
order: keep(record["order"]),
|
|
1573
|
-
hidden: keep(record["hidden"]),
|
|
1574
|
-
minimized: keep(record["minimized"]),
|
|
1575
|
-
...typeof maxRows === "number" && Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_PAGE_ROWS ? { maxRows } : {}
|
|
1576
|
-
});
|
|
1577
|
-
}
|
|
1578
|
-
return pages;
|
|
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
|
+
};
|
|
1579
1926
|
}
|
|
1580
|
-
function
|
|
1581
|
-
return
|
|
1582
|
-
order: [...layout.order],
|
|
1583
|
-
hidden: [...layout.hidden],
|
|
1584
|
-
minimized: [...layout.minimized],
|
|
1585
|
-
...layout.pages === void 0 || layout.pages.length === 0 ? {} : {
|
|
1586
|
-
pages: layout.pages.map((page) => ({
|
|
1587
|
-
key: page.key,
|
|
1588
|
-
title: page.title,
|
|
1589
|
-
order: [...page.order],
|
|
1590
|
-
hidden: [...page.hidden],
|
|
1591
|
-
minimized: [...page.minimized],
|
|
1592
|
-
...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
|
|
1593
|
-
}))
|
|
1594
|
-
},
|
|
1595
|
-
...serializeSettings(layout.settings) === void 0 ? {} : { settings: serializeSettings(layout.settings) }
|
|
1596
|
-
});
|
|
1927
|
+
function normalizedTitle(title) {
|
|
1928
|
+
return title.trim().normalize("NFKC").toLowerCase();
|
|
1597
1929
|
}
|
|
1598
|
-
function
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
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;
|
|
1602
1937
|
}
|
|
1603
|
-
function
|
|
1604
|
-
return
|
|
1938
|
+
function homePageKey(layout, item) {
|
|
1939
|
+
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
1605
1940
|
}
|
|
1606
|
-
function
|
|
1607
|
-
const
|
|
1608
|
-
const
|
|
1609
|
-
for (const
|
|
1610
|
-
|
|
1611
|
-
if (definition
|
|
1612
|
-
|
|
1613
|
-
|
|
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;
|
|
1614
1950
|
}
|
|
1951
|
+
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
1615
1952
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1953
|
+
return assigned;
|
|
1954
|
+
}
|
|
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;
|
|
1618
1958
|
}
|
|
1619
|
-
return
|
|
1959
|
+
return void 0;
|
|
1620
1960
|
}
|
|
1621
|
-
function
|
|
1622
|
-
const
|
|
1623
|
-
|
|
1624
|
-
);
|
|
1625
|
-
let next = 0;
|
|
1626
|
-
return slots.map((pinned) => pinned ?? free[next++]);
|
|
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);
|
|
1627
1965
|
}
|
|
1628
|
-
function
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1631
|
-
const ordered = viewerOrder(visible, layout);
|
|
1632
|
-
if (!visible.some(isPinned)) return ordered;
|
|
1633
|
-
return placeAroundPinned(
|
|
1634
|
-
visible,
|
|
1635
|
-
ordered.filter((item) => !isPinned(item))
|
|
1966
|
+
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
1967
|
+
const page = pageList(layout).find(
|
|
1968
|
+
(p) => p.key === pageKey
|
|
1636
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) };
|
|
1637
1976
|
}
|
|
1638
|
-
function
|
|
1639
|
-
|
|
1640
|
-
return bySortOrder(definitions.filter((d) => hidden.has(d.key)));
|
|
1641
|
-
}
|
|
1642
|
-
function moveWidget(definitions, layout, key, toIndex) {
|
|
1643
|
-
const current = visibleWidgets(definitions, layout);
|
|
1644
|
-
const fromIndex = current.findIndex((d) => d.key === key);
|
|
1645
|
-
const mover = current[fromIndex];
|
|
1646
|
-
if (mover === void 0 || !Number.isInteger(toIndex) || toIndex < 0 || toIndex >= current.length || toIndex === fromIndex || isPinned(mover) || isPinned(current[toIndex])) {
|
|
1647
|
-
return layout;
|
|
1648
|
-
}
|
|
1649
|
-
const freeSlots = freeSlotIndexes(current);
|
|
1650
|
-
const free = current.filter((item) => !isPinned(item));
|
|
1651
|
-
const fromFree = freeSlots.indexOf(fromIndex);
|
|
1652
|
-
const toFree = freeSlots.indexOf(toIndex);
|
|
1653
|
-
free.splice(fromFree, 1);
|
|
1654
|
-
free.splice(toFree, 0, mover);
|
|
1655
|
-
const next = placeAroundPinned(current, free);
|
|
1656
|
-
return { ...layout, order: next.map((d) => d.key) };
|
|
1977
|
+
function describePage(page) {
|
|
1978
|
+
return `Page "${page.title}"`;
|
|
1657
1979
|
}
|
|
1658
|
-
function
|
|
1659
|
-
|
|
1660
|
-
ordered.forEach((item, index) => {
|
|
1661
|
-
if (!isPinned(item)) slots.push(index);
|
|
1662
|
-
});
|
|
1663
|
-
return slots;
|
|
1980
|
+
function titleOf(item) {
|
|
1981
|
+
return item.title ?? item.key;
|
|
1664
1982
|
}
|
|
1665
|
-
function
|
|
1666
|
-
const
|
|
1667
|
-
|
|
1668
|
-
if (
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
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}.`
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
const wanted = normalizedTitle(trimmed);
|
|
1992
|
+
const clash = pageList(layout).find(
|
|
1993
|
+
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
1675
1994
|
);
|
|
1676
|
-
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
return
|
|
1680
|
-
}
|
|
1681
|
-
function moveWidgetDown(definitions, layout, key) {
|
|
1682
|
-
return moveWidgetBy(definitions, layout, key, 1);
|
|
1683
|
-
}
|
|
1684
|
-
function isHidden(layout, key) {
|
|
1685
|
-
return layout.hidden.includes(key);
|
|
1995
|
+
if (clash !== void 0) {
|
|
1996
|
+
return err(`Another page is already called "${clash.title}".`);
|
|
1997
|
+
}
|
|
1998
|
+
return ok(trimmed);
|
|
1686
1999
|
}
|
|
1687
|
-
function
|
|
1688
|
-
|
|
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}`;
|
|
1689
2005
|
}
|
|
1690
|
-
function
|
|
1691
|
-
|
|
1692
|
-
|
|
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}`;
|
|
1693
2013
|
}
|
|
1694
|
-
function
|
|
1695
|
-
if (
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
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.`
|
|
2018
|
+
);
|
|
2019
|
+
}
|
|
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: []
|
|
1700
2036
|
};
|
|
2037
|
+
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
1701
2038
|
}
|
|
1702
|
-
function
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
if (
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
return {
|
|
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
|
+
)
|
|
2053
|
+
});
|
|
1712
2054
|
}
|
|
1713
|
-
function
|
|
1714
|
-
|
|
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 };
|
|
1715
2068
|
}
|
|
1716
|
-
function
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
order: keep(lists.order),
|
|
1721
|
-
hidden: keep(lists.hidden),
|
|
1722
|
-
minimized: keep(lists.minimized)
|
|
1723
|
-
}));
|
|
2069
|
+
function overflowing(definitions, layout, options) {
|
|
2070
|
+
return pageList(layout).find(
|
|
2071
|
+
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
2072
|
+
);
|
|
1724
2073
|
}
|
|
1725
|
-
function
|
|
1726
|
-
const
|
|
1727
|
-
const
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
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
|
+
);
|
|
1734
2083
|
}
|
|
1735
|
-
|
|
2084
|
+
const target = pages[index > 0 ? index - 1 : 1];
|
|
2085
|
+
const merged = {
|
|
1736
2086
|
...layout,
|
|
1737
|
-
|
|
1738
|
-
|
|
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
|
+
)
|
|
1739
2095
|
};
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
if (lock.hide) noHide.add(definition.key);
|
|
1750
|
-
if (lock.minimize) noMinimize.add(definition.key);
|
|
1751
|
-
}
|
|
1752
|
-
const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
|
|
1753
|
-
return mapLayoutLists(layout, (lists) => ({
|
|
1754
|
-
order: without2(lists.order, noMove),
|
|
1755
|
-
hidden: without2(lists.hidden, noHide),
|
|
1756
|
-
minimized: without2(lists.minimized, noMinimize)
|
|
1757
|
-
}));
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
// src/core/validate.ts
|
|
1761
|
-
var MAX_REPORTED_PROBLEMS = 10;
|
|
1762
|
-
function describeType2(value) {
|
|
1763
|
-
if (value === null) return "null";
|
|
1764
|
-
if (Array.isArray(value)) return "array";
|
|
1765
|
-
if (typeof value === "number" && !Number.isFinite(value)) {
|
|
1766
|
-
return String(value);
|
|
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
|
+
);
|
|
1767
2105
|
}
|
|
1768
|
-
return
|
|
1769
|
-
}
|
|
1770
|
-
function isRecord2(value) {
|
|
1771
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2106
|
+
return ok(merged);
|
|
1772
2107
|
}
|
|
1773
|
-
function
|
|
1774
|
-
return
|
|
2108
|
+
function without(keys, key) {
|
|
2109
|
+
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
1775
2110
|
}
|
|
1776
|
-
function
|
|
1777
|
-
const
|
|
1778
|
-
if (
|
|
1779
|
-
|
|
1780
|
-
|
|
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.`
|
|
1781
2116
|
);
|
|
1782
2117
|
}
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
if (
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
1792
|
-
problems.push(
|
|
1793
|
-
`${join(path, field)} must be a finite number, got ${describeType2(value)}.`
|
|
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)}.`
|
|
1794
2126
|
);
|
|
1795
2127
|
}
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
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
|
+
};
|
|
2149
|
+
}
|
|
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
|
+
};
|
|
2157
|
+
}
|
|
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
|
+
);
|
|
2170
|
+
}
|
|
1800
2171
|
}
|
|
2172
|
+
return ok(moved);
|
|
1801
2173
|
}
|
|
1802
|
-
function
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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;
|
|
1808
2189
|
}
|
|
2190
|
+
taken.add(normalizedTitle(candidate));
|
|
2191
|
+
return candidate;
|
|
1809
2192
|
}
|
|
1810
|
-
function
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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);
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
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
|
+
);
|
|
2211
|
+
}
|
|
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
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
const placedKeys = /* @__PURE__ */ new Set();
|
|
2273
|
+
for (const p of pages) {
|
|
2274
|
+
for (const k of [...p.order, ...p.hidden]) placedKeys.add(k);
|
|
2275
|
+
}
|
|
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
|
|
1815
2284
|
);
|
|
1816
2285
|
}
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
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)
|
|
1824
2296
|
);
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
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
|
|
1828
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;
|
|
2322
|
+
}
|
|
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
|
+
}
|
|
1829
2336
|
}
|
|
2337
|
+
const result = work();
|
|
2338
|
+
return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) && locked.settings === layout.settings ? layout : result;
|
|
1830
2339
|
}
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
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
|
+
}
|
|
1838
2364
|
}
|
|
1839
|
-
|
|
2365
|
+
let n = highest + 1;
|
|
2366
|
+
while (taken.has(`${fromKey}#${n}`)) n += 1;
|
|
2367
|
+
return `${fromKey}#${n}`;
|
|
1840
2368
|
}
|
|
1841
|
-
function
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
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.`
|
|
1846
2381
|
);
|
|
1847
2382
|
}
|
|
1848
|
-
|
|
1849
|
-
|
|
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 });
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
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
|
+
}
|
|
2425
|
+
}
|
|
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
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
|
|
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);
|
|
2459
|
+
}
|
|
2460
|
+
return typeof value;
|
|
2461
|
+
}
|
|
2462
|
+
function isRecord3(value) {
|
|
2463
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2464
|
+
}
|
|
2465
|
+
function join(path, field) {
|
|
2466
|
+
return path === "" ? field : `${path}.${field}`;
|
|
2467
|
+
}
|
|
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
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
function optionalString(record, field, path, problems) {
|
|
2477
|
+
if (record[field] !== void 0) {
|
|
2478
|
+
requireString(record, field, path, problems);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
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)}.`
|
|
2486
|
+
);
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
function optionalNumber(record, field, path, problems) {
|
|
2490
|
+
if (record[field] !== void 0) {
|
|
2491
|
+
requireNumber(record, field, path, problems);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
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)}.`
|
|
2499
|
+
);
|
|
2500
|
+
}
|
|
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)}.`
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
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)}.`
|
|
2516
|
+
);
|
|
2517
|
+
} else if (!isSafe(value)) {
|
|
2518
|
+
problems.push(
|
|
2519
|
+
`${join(path, field)} must be ${allowed}, got ${JSON.stringify(value.slice(0, 80))}.`
|
|
2520
|
+
);
|
|
2521
|
+
}
|
|
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;
|
|
2530
|
+
}
|
|
2531
|
+
return value;
|
|
2532
|
+
}
|
|
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
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
function eachRecord(items, path, problems, check) {
|
|
1850
2542
|
items.forEach((item, index) => {
|
|
1851
2543
|
const itemPath = `${path}[${index}]`;
|
|
1852
|
-
if (
|
|
2544
|
+
if (isRecord3(item)) {
|
|
1853
2545
|
check(item, itemPath);
|
|
1854
2546
|
} else {
|
|
1855
2547
|
problems.push(
|
|
@@ -1998,7 +2690,7 @@ function checkPayloadFields(kind, data, problems) {
|
|
|
1998
2690
|
}
|
|
1999
2691
|
function checkDetailOption(detail, problems) {
|
|
2000
2692
|
if (detail === void 0 || typeof detail === "boolean") return;
|
|
2001
|
-
if (!
|
|
2693
|
+
if (!isRecord3(detail)) {
|
|
2002
2694
|
problems.push(
|
|
2003
2695
|
`detail must be true, false or an options object, got ${describeType2(detail)}.`
|
|
2004
2696
|
);
|
|
@@ -2017,13 +2709,13 @@ function checkDetailOption(detail, problems) {
|
|
|
2017
2709
|
}
|
|
2018
2710
|
function checkTableControlsOption(controls, kind, problems) {
|
|
2019
2711
|
if (controls === void 0 || controls === false) return;
|
|
2020
|
-
if (typeof controls !== "boolean" && !
|
|
2712
|
+
if (typeof controls !== "boolean" && !isRecord3(controls)) {
|
|
2021
2713
|
problems.push(
|
|
2022
2714
|
`tableControls must be true, false or an options object, got ${describeType2(controls)}.`
|
|
2023
2715
|
);
|
|
2024
2716
|
return;
|
|
2025
2717
|
}
|
|
2026
|
-
if (
|
|
2718
|
+
if (isRecord3(controls)) {
|
|
2027
2719
|
optionalBoolean(controls, "search", "tableControls", problems);
|
|
2028
2720
|
optionalBoolean(controls, "sort", "tableControls", problems);
|
|
2029
2721
|
}
|
|
@@ -2065,7 +2757,7 @@ function checkWidthLimits(value, problems) {
|
|
|
2065
2757
|
}
|
|
2066
2758
|
function checkLockedOption(locked, problems) {
|
|
2067
2759
|
if (locked === void 0 || typeof locked === "boolean") return;
|
|
2068
|
-
if (!
|
|
2760
|
+
if (!isRecord3(locked)) {
|
|
2069
2761
|
problems.push(
|
|
2070
2762
|
`locked must be true, false or an object, got ${describeType2(locked)}.`
|
|
2071
2763
|
);
|
|
@@ -2075,6 +2767,7 @@ function checkLockedOption(locked, problems) {
|
|
|
2075
2767
|
optionalBoolean(locked, "hide", "locked", problems);
|
|
2076
2768
|
optionalBoolean(locked, "minimize", "locked", problems);
|
|
2077
2769
|
optionalBoolean(locked, "options", "locked", problems);
|
|
2770
|
+
optionalBoolean(locked, "clone", "locked", problems);
|
|
2078
2771
|
}
|
|
2079
2772
|
function checkOptionColumns(option, path, kind, problems) {
|
|
2080
2773
|
const columns = option["columns"];
|
|
@@ -2087,7 +2780,7 @@ function checkOptionColumns(option, path, kind, problems) {
|
|
|
2087
2780
|
const keys = [];
|
|
2088
2781
|
columns.forEach((entry, index) => {
|
|
2089
2782
|
const entryPath = `${path}.columns[${index}]`;
|
|
2090
|
-
if (!
|
|
2783
|
+
if (!isRecord3(entry)) {
|
|
2091
2784
|
problems.push(
|
|
2092
2785
|
`${entryPath} must be an object, got ${describeType2(entry)}.`
|
|
2093
2786
|
);
|
|
@@ -2112,7 +2805,7 @@ function checkOptionColumns(option, path, kind, problems) {
|
|
|
2112
2805
|
return keys;
|
|
2113
2806
|
}
|
|
2114
2807
|
function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
2115
|
-
if (!
|
|
2808
|
+
if (!isRecord3(option)) {
|
|
2116
2809
|
problems.push(`${path} must be an object, got ${describeType2(option)}.`);
|
|
2117
2810
|
return;
|
|
2118
2811
|
}
|
|
@@ -2144,7 +2837,7 @@ function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
|
2144
2837
|
} else {
|
|
2145
2838
|
choices.forEach((choice, index) => {
|
|
2146
2839
|
const choicePath = `${path}.choices[${index}]`;
|
|
2147
|
-
if (!
|
|
2840
|
+
if (!isRecord3(choice)) {
|
|
2148
2841
|
problems.push(
|
|
2149
2842
|
`${choicePath} must be an object, got ${describeType2(choice)}.`
|
|
2150
2843
|
);
|
|
@@ -2234,7 +2927,7 @@ function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
|
2234
2927
|
} else {
|
|
2235
2928
|
presets.forEach((preset, index) => {
|
|
2236
2929
|
const presetPath = `${path}.presets[${index}]`;
|
|
2237
|
-
if (!
|
|
2930
|
+
if (!isRecord3(preset)) {
|
|
2238
2931
|
problems.push(
|
|
2239
2932
|
`${presetPath} must be an object, got ${describeType2(preset)}.`
|
|
2240
2933
|
);
|
|
@@ -2363,7 +3056,7 @@ function checkColumnIndex(value, path, problems) {
|
|
|
2363
3056
|
}
|
|
2364
3057
|
}
|
|
2365
3058
|
function checkConversion(conversion, path, baseKind, viewKind2, problems) {
|
|
2366
|
-
if (!
|
|
3059
|
+
if (!isRecord3(conversion)) {
|
|
2367
3060
|
problems.push(
|
|
2368
3061
|
`${path} must be an object with a type, got ${describeType2(conversion)}.`
|
|
2369
3062
|
);
|
|
@@ -2441,7 +3134,7 @@ function checkViews(value, problems) {
|
|
|
2441
3134
|
} else {
|
|
2442
3135
|
views.forEach((view, index) => {
|
|
2443
3136
|
const path = `views[${index}]`;
|
|
2444
|
-
if (!
|
|
3137
|
+
if (!isRecord3(view)) {
|
|
2445
3138
|
problems.push(
|
|
2446
3139
|
`${path} must be an object, got ${describeType2(view)}.`
|
|
2447
3140
|
);
|
|
@@ -2492,7 +3185,7 @@ function formatProblems(prefix, problems) {
|
|
|
2492
3185
|
}
|
|
2493
3186
|
function validateWidgetData(value, options = {}) {
|
|
2494
3187
|
const subject = options.widgetKey === void 0 ? "Widget data" : `Widget ${JSON.stringify(options.widgetKey)}`;
|
|
2495
|
-
if (!
|
|
3188
|
+
if (!isRecord3(value)) {
|
|
2496
3189
|
return err(`${subject}: must be an object, got ${describeType2(value)}.`);
|
|
2497
3190
|
}
|
|
2498
3191
|
if (value["empty"] === true) {
|
|
@@ -2522,7 +3215,7 @@ function validateWidgetData(value, options = {}) {
|
|
|
2522
3215
|
return ok(value);
|
|
2523
3216
|
}
|
|
2524
3217
|
function validateWidgetDefinition(value) {
|
|
2525
|
-
if (!
|
|
3218
|
+
if (!isRecord3(value)) {
|
|
2526
3219
|
return err(
|
|
2527
3220
|
`Widget definition: must be an object, got ${describeType2(value)}.`
|
|
2528
3221
|
);
|
|
@@ -2606,7 +3299,7 @@ function loadingWidgets(definitions) {
|
|
|
2606
3299
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
2607
3300
|
}
|
|
2608
3301
|
function cacheKeyFor(definition, options, values) {
|
|
2609
|
-
const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
3302
|
+
const base = options.cacheKey === void 0 ? definition.sourceKey ?? definition.key : options.cacheKey(definition);
|
|
2610
3303
|
return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
|
|
2611
3304
|
}
|
|
2612
3305
|
function optionValuesFor(definition, options) {
|
|
@@ -2720,7 +3413,10 @@ async function resolveWidgets(definitions, providers, context, options = {}) {
|
|
|
2720
3413
|
visible.map(
|
|
2721
3414
|
(definition) => resolveWidget(
|
|
2722
3415
|
definition,
|
|
2723
|
-
Object.prototype.hasOwnProperty.call(
|
|
3416
|
+
Object.prototype.hasOwnProperty.call(
|
|
3417
|
+
providers,
|
|
3418
|
+
definition.sourceKey ?? definition.key
|
|
3419
|
+
) ? providers[definition.sourceKey ?? definition.key] : void 0,
|
|
2724
3420
|
context,
|
|
2725
3421
|
options
|
|
2726
3422
|
)
|
|
@@ -2800,7 +3496,11 @@ function createWidgetLoader(options) {
|
|
|
2800
3496
|
}
|
|
2801
3497
|
}
|
|
2802
3498
|
if (!isCurrent()) return;
|
|
2803
|
-
const
|
|
3499
|
+
const providerKey = definition.sourceKey ?? key;
|
|
3500
|
+
const provider = Object.prototype.hasOwnProperty.call(
|
|
3501
|
+
providers,
|
|
3502
|
+
providerKey
|
|
3503
|
+
) ? providers[providerKey] : void 0;
|
|
2804
3504
|
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
2805
3505
|
...runOptions,
|
|
2806
3506
|
signal: controller.signal
|
|
@@ -2889,484 +3589,6 @@ function createWidgetLoader(options) {
|
|
|
2889
3589
|
};
|
|
2890
3590
|
}
|
|
2891
3591
|
|
|
2892
|
-
// src/core/pages.ts
|
|
2893
|
-
var DEFAULT_PAGE_KEY = "default";
|
|
2894
|
-
var DEFAULT_PAGE_ROWS = 4;
|
|
2895
|
-
function rowsAllowed(page, options) {
|
|
2896
|
-
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
2897
|
-
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
2898
|
-
}
|
|
2899
|
-
function hasPages(layout) {
|
|
2900
|
-
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
2901
|
-
}
|
|
2902
|
-
function pageList(layout) {
|
|
2903
|
-
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
2904
|
-
return layout.pages;
|
|
2905
|
-
}
|
|
2906
|
-
return [
|
|
2907
|
-
{
|
|
2908
|
-
key: DEFAULT_PAGE_KEY,
|
|
2909
|
-
title: "Page 1",
|
|
2910
|
-
order: layout.order,
|
|
2911
|
-
hidden: layout.hidden,
|
|
2912
|
-
minimized: layout.minimized
|
|
2913
|
-
}
|
|
2914
|
-
];
|
|
2915
|
-
}
|
|
2916
|
-
function withExplicitPages(layout) {
|
|
2917
|
-
if (hasPages(layout)) return layout;
|
|
2918
|
-
return {
|
|
2919
|
-
...layout,
|
|
2920
|
-
order: [],
|
|
2921
|
-
hidden: [],
|
|
2922
|
-
minimized: [],
|
|
2923
|
-
pages: pageList(layout)
|
|
2924
|
-
};
|
|
2925
|
-
}
|
|
2926
|
-
function sameKeys(a, b) {
|
|
2927
|
-
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
2928
|
-
}
|
|
2929
|
-
function pageLayout(layout, pageKey) {
|
|
2930
|
-
if (!hasPages(layout))
|
|
2931
|
-
return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
|
|
2932
|
-
const page = layout.pages?.find(
|
|
2933
|
-
(p) => p.key === pageKey
|
|
2934
|
-
);
|
|
2935
|
-
if (page === void 0) return void 0;
|
|
2936
|
-
const { pages, ...rest } = layout;
|
|
2937
|
-
return {
|
|
2938
|
-
...rest,
|
|
2939
|
-
order: page.order,
|
|
2940
|
-
hidden: page.hidden,
|
|
2941
|
-
minimized: page.minimized
|
|
2942
|
-
};
|
|
2943
|
-
}
|
|
2944
|
-
function withPageLayout(layout, pageKey, next) {
|
|
2945
|
-
const current = pageLayout(layout, pageKey);
|
|
2946
|
-
if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
|
|
2947
|
-
return layout;
|
|
2948
|
-
}
|
|
2949
|
-
if (!hasPages(layout)) {
|
|
2950
|
-
return {
|
|
2951
|
-
...layout,
|
|
2952
|
-
order: next.order,
|
|
2953
|
-
hidden: next.hidden,
|
|
2954
|
-
minimized: next.minimized
|
|
2955
|
-
};
|
|
2956
|
-
}
|
|
2957
|
-
return {
|
|
2958
|
-
...layout,
|
|
2959
|
-
pages: (layout.pages ?? []).map(
|
|
2960
|
-
(page) => page.key === pageKey ? {
|
|
2961
|
-
...page,
|
|
2962
|
-
order: next.order,
|
|
2963
|
-
hidden: next.hidden,
|
|
2964
|
-
minimized: next.minimized
|
|
2965
|
-
} : page
|
|
2966
|
-
)
|
|
2967
|
-
};
|
|
2968
|
-
}
|
|
2969
|
-
function normalizedTitle(title) {
|
|
2970
|
-
return title.trim().normalize("NFKC").toLowerCase();
|
|
2971
|
-
}
|
|
2972
|
-
function resolvePageKey(layout, reference) {
|
|
2973
|
-
if (reference === void 0 || reference === "") return void 0;
|
|
2974
|
-
const pages = pageList(layout);
|
|
2975
|
-
const byKey = pages.find((p) => p.key === reference);
|
|
2976
|
-
if (byKey !== void 0) return byKey.key;
|
|
2977
|
-
const wanted = normalizedTitle(reference);
|
|
2978
|
-
return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
|
|
2979
|
-
}
|
|
2980
|
-
function homePageKey(layout, item) {
|
|
2981
|
-
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
2982
|
-
}
|
|
2983
|
-
function assignPages(definitions, layout) {
|
|
2984
|
-
const pages = pageList(layout);
|
|
2985
|
-
const assigned = new Map(pages.map((p) => [p.key, []]));
|
|
2986
|
-
for (const definition of definitions) {
|
|
2987
|
-
let pageKey;
|
|
2988
|
-
if (!resolveWidgetLock(definition).move) {
|
|
2989
|
-
pageKey = pages.find(
|
|
2990
|
-
(p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
|
|
2991
|
-
)?.key;
|
|
2992
|
-
}
|
|
2993
|
-
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
2994
|
-
}
|
|
2995
|
-
return assigned;
|
|
2996
|
-
}
|
|
2997
|
-
function pageOf(definitions, layout, key) {
|
|
2998
|
-
for (const [pageKey, widgets] of assignPages(definitions, layout)) {
|
|
2999
|
-
if (widgets.some((w) => w.key === key)) return pageKey;
|
|
3000
|
-
}
|
|
3001
|
-
return void 0;
|
|
3002
|
-
}
|
|
3003
|
-
function pageWidgets(definitions, layout, pageKey) {
|
|
3004
|
-
const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
|
|
3005
|
-
const view = pageLayout(layout, pageKey);
|
|
3006
|
-
return view === void 0 ? [] : visibleWidgets(onPage, view);
|
|
3007
|
-
}
|
|
3008
|
-
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
3009
|
-
const page = pageList(layout).find(
|
|
3010
|
-
(p) => p.key === pageKey
|
|
3011
|
-
);
|
|
3012
|
-
if (page === void 0) return void 0;
|
|
3013
|
-
const maxRows = rowsAllowed(page, options);
|
|
3014
|
-
const rowsUsed = placeRows(
|
|
3015
|
-
pageWidgets(definitions, layout, pageKey)
|
|
3016
|
-
).length;
|
|
3017
|
-
return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
|
|
3018
|
-
}
|
|
3019
|
-
function describePage(page) {
|
|
3020
|
-
return `Page "${page.title}"`;
|
|
3021
|
-
}
|
|
3022
|
-
function titleOf(item) {
|
|
3023
|
-
return item.title ?? item.key;
|
|
3024
|
-
}
|
|
3025
|
-
function checkTitle(layout, title, exceptKey) {
|
|
3026
|
-
const trimmed = title.trim();
|
|
3027
|
-
if (trimmed === "") return err("A page title must not be empty.");
|
|
3028
|
-
if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
|
|
3029
|
-
return err(
|
|
3030
|
-
`Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
|
|
3031
|
-
);
|
|
3032
|
-
}
|
|
3033
|
-
const wanted = normalizedTitle(trimmed);
|
|
3034
|
-
const clash = pageList(layout).find(
|
|
3035
|
-
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
3036
|
-
);
|
|
3037
|
-
if (clash !== void 0) {
|
|
3038
|
-
return err(`Another page is already called "${clash.title}".`);
|
|
3039
|
-
}
|
|
3040
|
-
return ok(trimmed);
|
|
3041
|
-
}
|
|
3042
|
-
function nextPageKey(layout) {
|
|
3043
|
-
const keys = new Set(pageList(layout).map((p) => p.key));
|
|
3044
|
-
let n = 1;
|
|
3045
|
-
while (keys.has(`page-${n}`)) n += 1;
|
|
3046
|
-
return `page-${n}`;
|
|
3047
|
-
}
|
|
3048
|
-
function nextPageTitle(layout) {
|
|
3049
|
-
const used = new Set(
|
|
3050
|
-
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
3051
|
-
);
|
|
3052
|
-
let n = pageList(layout).length + 1;
|
|
3053
|
-
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
3054
|
-
return `Page ${n}`;
|
|
3055
|
-
}
|
|
3056
|
-
function addPage(layout, options = {}) {
|
|
3057
|
-
if (pageList(layout).length >= MAX_PARSED_PAGES) {
|
|
3058
|
-
return err(
|
|
3059
|
-
`The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
|
|
3060
|
-
);
|
|
3061
|
-
}
|
|
3062
|
-
const key = options.key ?? nextPageKey(layout);
|
|
3063
|
-
if (pageList(layout).some((p) => p.key === key)) {
|
|
3064
|
-
return err(`A page with the key "${key}" already exists.`);
|
|
3065
|
-
}
|
|
3066
|
-
const title = checkTitle(
|
|
3067
|
-
layout,
|
|
3068
|
-
options.title ?? nextPageTitle(layout)
|
|
3069
|
-
);
|
|
3070
|
-
if (!title.ok) return title;
|
|
3071
|
-
const base = withExplicitPages(layout);
|
|
3072
|
-
const page = {
|
|
3073
|
-
key,
|
|
3074
|
-
title: title.value,
|
|
3075
|
-
order: [],
|
|
3076
|
-
hidden: [],
|
|
3077
|
-
minimized: []
|
|
3078
|
-
};
|
|
3079
|
-
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
3080
|
-
}
|
|
3081
|
-
function renamePage(layout, pageKey, title) {
|
|
3082
|
-
const page = pageList(layout).find(
|
|
3083
|
-
(p) => p.key === pageKey
|
|
3084
|
-
);
|
|
3085
|
-
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
3086
|
-
const checked = checkTitle(layout, title, pageKey);
|
|
3087
|
-
if (!checked.ok) return checked;
|
|
3088
|
-
if (checked.value === page.title) return ok(layout);
|
|
3089
|
-
const base = withExplicitPages(layout);
|
|
3090
|
-
return ok({
|
|
3091
|
-
...base,
|
|
3092
|
-
pages: (base.pages ?? []).map(
|
|
3093
|
-
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
3094
|
-
)
|
|
3095
|
-
});
|
|
3096
|
-
}
|
|
3097
|
-
function movePage(layout, pageKey, offset) {
|
|
3098
|
-
const pages = layout.pages ?? [];
|
|
3099
|
-
const from = pages.findIndex((p) => p.key === pageKey);
|
|
3100
|
-
if (from === -1) return layout;
|
|
3101
|
-
const to = Math.max(
|
|
3102
|
-
0,
|
|
3103
|
-
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
3104
|
-
);
|
|
3105
|
-
if (to === from) return layout;
|
|
3106
|
-
const next = [...pages];
|
|
3107
|
-
const [moved] = next.splice(from, 1);
|
|
3108
|
-
next.splice(to, 0, moved);
|
|
3109
|
-
return { ...layout, pages: next };
|
|
3110
|
-
}
|
|
3111
|
-
function overflowing(definitions, layout, options) {
|
|
3112
|
-
return pageList(layout).find(
|
|
3113
|
-
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
3114
|
-
);
|
|
3115
|
-
}
|
|
3116
|
-
function removePage(definitions, layout, pageKey, options = {}) {
|
|
3117
|
-
const pages = pageList(layout);
|
|
3118
|
-
const index = pages.findIndex((p) => p.key === pageKey);
|
|
3119
|
-
const page = pages[index];
|
|
3120
|
-
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
3121
|
-
if (pages.length === 1) {
|
|
3122
|
-
return err(
|
|
3123
|
-
`${describePage(page)} is the only page, so it can't be removed.`
|
|
3124
|
-
);
|
|
3125
|
-
}
|
|
3126
|
-
const target = pages[index > 0 ? index - 1 : 1];
|
|
3127
|
-
const merged = {
|
|
3128
|
-
...layout,
|
|
3129
|
-
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
3130
|
-
(p) => p.key === target.key ? {
|
|
3131
|
-
...p,
|
|
3132
|
-
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
3133
|
-
hidden: [...p.hidden, ...page.hidden],
|
|
3134
|
-
minimized: [...p.minimized, ...page.minimized]
|
|
3135
|
-
} : p
|
|
3136
|
-
)
|
|
3137
|
-
};
|
|
3138
|
-
const full = overflowing(
|
|
3139
|
-
definitions,
|
|
3140
|
-
merged,
|
|
3141
|
-
options
|
|
3142
|
-
);
|
|
3143
|
-
if (full !== void 0) {
|
|
3144
|
-
return err(
|
|
3145
|
-
`${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.`
|
|
3146
|
-
);
|
|
3147
|
-
}
|
|
3148
|
-
return ok(merged);
|
|
3149
|
-
}
|
|
3150
|
-
function without(keys, key) {
|
|
3151
|
-
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
3152
|
-
}
|
|
3153
|
-
function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
|
|
3154
|
-
const widget = definitions.find((d) => d.key === widgetKey);
|
|
3155
|
-
if (widget === void 0) {
|
|
3156
|
-
return err(
|
|
3157
|
-
`Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
|
|
3158
|
-
);
|
|
3159
|
-
}
|
|
3160
|
-
const pages = pageList(layout);
|
|
3161
|
-
const target = pages.find((p) => p.key === targetKey);
|
|
3162
|
-
if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
|
|
3163
|
-
const sourceKey = pageOf(definitions, layout, widgetKey);
|
|
3164
|
-
if (sourceKey === targetKey) return ok(layout);
|
|
3165
|
-
if (resolveWidgetLock(widget).move) {
|
|
3166
|
-
return err(
|
|
3167
|
-
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
3168
|
-
);
|
|
3169
|
-
}
|
|
3170
|
-
const base = withExplicitPages(layout);
|
|
3171
|
-
const source = (base.pages ?? []).find(
|
|
3172
|
-
(p) => p.key === sourceKey
|
|
3173
|
-
);
|
|
3174
|
-
const hidden = source.hidden.includes(widgetKey);
|
|
3175
|
-
const minimized = source.minimized.includes(widgetKey);
|
|
3176
|
-
const targetOrder = pageWidgets(
|
|
3177
|
-
definitions,
|
|
3178
|
-
base,
|
|
3179
|
-
targetKey
|
|
3180
|
-
).map((d) => d.key);
|
|
3181
|
-
const moved = {
|
|
3182
|
-
...base,
|
|
3183
|
-
pages: (base.pages ?? []).map((p) => {
|
|
3184
|
-
if (p.key === sourceKey) {
|
|
3185
|
-
return {
|
|
3186
|
-
...p,
|
|
3187
|
-
order: without(p.order, widgetKey),
|
|
3188
|
-
hidden: without(p.hidden, widgetKey),
|
|
3189
|
-
minimized: without(p.minimized, widgetKey)
|
|
3190
|
-
};
|
|
3191
|
-
}
|
|
3192
|
-
if (p.key === targetKey) {
|
|
3193
|
-
return {
|
|
3194
|
-
...p,
|
|
3195
|
-
order: hidden ? p.order : [...targetOrder, widgetKey],
|
|
3196
|
-
hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
|
|
3197
|
-
minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
|
|
3198
|
-
};
|
|
3199
|
-
}
|
|
3200
|
-
return p;
|
|
3201
|
-
})
|
|
3202
|
-
};
|
|
3203
|
-
if (!hidden) {
|
|
3204
|
-
const allowed = rowsAllowed(target, options);
|
|
3205
|
-
const needed = placeRows(
|
|
3206
|
-
pageWidgets(definitions, moved, targetKey)
|
|
3207
|
-
).length;
|
|
3208
|
-
if (needed > allowed) {
|
|
3209
|
-
return err(
|
|
3210
|
-
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
3211
|
-
);
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
return ok(moved);
|
|
3215
|
-
}
|
|
3216
|
-
function withoutLockField(definition) {
|
|
3217
|
-
if (definition.locked === void 0) return definition;
|
|
3218
|
-
const copy = { ...definition };
|
|
3219
|
-
delete copy.locked;
|
|
3220
|
-
return copy;
|
|
3221
|
-
}
|
|
3222
|
-
function uniqueTitle2(taken, title, index) {
|
|
3223
|
-
let base = title.trim().slice(0, MAX_PAGE_TITLE_LENGTH);
|
|
3224
|
-
if (base === "") base = `Page ${index + 1}`;
|
|
3225
|
-
let candidate = base;
|
|
3226
|
-
let n = 1;
|
|
3227
|
-
while (taken.has(normalizedTitle(candidate))) {
|
|
3228
|
-
const suffix = ` (#${n})`;
|
|
3229
|
-
candidate = `${base.slice(0, MAX_PAGE_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
3230
|
-
n += 1;
|
|
3231
|
-
}
|
|
3232
|
-
taken.add(normalizedTitle(candidate));
|
|
3233
|
-
return candidate;
|
|
3234
|
-
}
|
|
3235
|
-
function samePages(a, b) {
|
|
3236
|
-
return a.length === b.length && a.every((p, i) => {
|
|
3237
|
-
const q = b[i];
|
|
3238
|
-
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);
|
|
3239
|
-
});
|
|
3240
|
-
}
|
|
3241
|
-
function normalizeLayout(definitions, layout, options = {}) {
|
|
3242
|
-
const known = pruneLayout(definitions, layout);
|
|
3243
|
-
const enforced = enforceLocks(definitions, known, options);
|
|
3244
|
-
const locked = normalizeSettings(
|
|
3245
|
-
definitions,
|
|
3246
|
-
enforced,
|
|
3247
|
-
options
|
|
3248
|
-
);
|
|
3249
|
-
if (!hasPages(locked)) return locked === layout ? layout : locked;
|
|
3250
|
-
const defs = effectiveDefinitions(
|
|
3251
|
-
options.overrideLocks === true ? definitions.map(withoutLockField) : definitions,
|
|
3252
|
-
locked,
|
|
3253
|
-
options
|
|
3254
|
-
);
|
|
3255
|
-
const taken = /* @__PURE__ */ new Set();
|
|
3256
|
-
const seenKeys = /* @__PURE__ */ new Set();
|
|
3257
|
-
const held = /* @__PURE__ */ new Set();
|
|
3258
|
-
let pages = [];
|
|
3259
|
-
(locked.pages ?? []).forEach((page, index) => {
|
|
3260
|
-
if (seenKeys.has(page.key)) return;
|
|
3261
|
-
seenKeys.add(page.key);
|
|
3262
|
-
const keep = (list) => [
|
|
3263
|
-
...new Set(list.filter((k) => !held.has(k)))
|
|
3264
|
-
];
|
|
3265
|
-
const next = {
|
|
3266
|
-
...page,
|
|
3267
|
-
title: uniqueTitle2(taken, page.title, index),
|
|
3268
|
-
order: keep(page.order),
|
|
3269
|
-
hidden: keep(page.hidden),
|
|
3270
|
-
minimized: keep(page.minimized)
|
|
3271
|
-
};
|
|
3272
|
-
for (const k of [...next.order, ...next.hidden]) held.add(k);
|
|
3273
|
-
pages.push(next);
|
|
3274
|
-
});
|
|
3275
|
-
const work = () => ({ ...locked, pages });
|
|
3276
|
-
const pinned = defs.filter((d) => resolveWidgetLock(d).move);
|
|
3277
|
-
for (const widget of pinned) {
|
|
3278
|
-
const home = homePageKey(work(), widget);
|
|
3279
|
-
const keysHere = (p, list) => p.key !== home && p[list].includes(widget.key);
|
|
3280
|
-
const wasHidden = pages.some((p) => keysHere(p, "hidden"));
|
|
3281
|
-
const wasMinimized = pages.some((p) => keysHere(p, "minimized"));
|
|
3282
|
-
if (!wasHidden && !wasMinimized) continue;
|
|
3283
|
-
pages = pages.map((p) => {
|
|
3284
|
-
let next = p;
|
|
3285
|
-
if (keysHere(p, "hidden")) {
|
|
3286
|
-
next = { ...next, hidden: without(next.hidden, widget.key) };
|
|
3287
|
-
}
|
|
3288
|
-
if (keysHere(p, "minimized")) {
|
|
3289
|
-
next = { ...next, minimized: without(next.minimized, widget.key) };
|
|
3290
|
-
}
|
|
3291
|
-
if (p.key === home) {
|
|
3292
|
-
next = {
|
|
3293
|
-
...next,
|
|
3294
|
-
hidden: wasHidden && !next.hidden.includes(widget.key) ? [...next.hidden, widget.key] : next.hidden,
|
|
3295
|
-
minimized: wasMinimized && !next.minimized.includes(widget.key) ? [...next.minimized, widget.key] : next.minimized
|
|
3296
|
-
};
|
|
3297
|
-
}
|
|
3298
|
-
return next;
|
|
3299
|
-
});
|
|
3300
|
-
}
|
|
3301
|
-
const placedKeys = /* @__PURE__ */ new Set();
|
|
3302
|
-
for (const p of pages) {
|
|
3303
|
-
for (const k of [...p.order, ...p.hidden]) placedKeys.add(k);
|
|
3304
|
-
}
|
|
3305
|
-
const pinnedKeys = new Set(pinned.map((d) => d.key));
|
|
3306
|
-
const unplaced = defs.filter((d) => !placedKeys.has(d.key) && !pinnedKeys.has(d.key)).map((definition, index) => ({ definition, index })).sort(
|
|
3307
|
-
(a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
|
|
3308
|
-
).map(({ definition }) => definition);
|
|
3309
|
-
for (const widget of unplaced) {
|
|
3310
|
-
const home = homePageKey(work(), widget);
|
|
3311
|
-
pages = pages.map(
|
|
3312
|
-
(p) => p.key === home ? { ...p, order: [...p.order, widget.key] } : p
|
|
3313
|
-
);
|
|
3314
|
-
}
|
|
3315
|
-
for (let i = 0; i < pages.length; i += 1) {
|
|
3316
|
-
const page = pages[i];
|
|
3317
|
-
const shown = pageWidgets(defs, work(), page.key);
|
|
3318
|
-
const fits = fitCount(shown, rowsAllowed(page, options));
|
|
3319
|
-
if (fits >= shown.length) continue;
|
|
3320
|
-
const moving = shown.slice(fits).filter((d) => !pinnedKeys.has(d.key)).map((d) => d.key);
|
|
3321
|
-
if (moving.length === 0) continue;
|
|
3322
|
-
const moveSet = new Set(moving);
|
|
3323
|
-
const movedMinimized = page.minimized.filter(
|
|
3324
|
-
(k) => moveSet.has(k)
|
|
3325
|
-
);
|
|
3326
|
-
pages = pages.map(
|
|
3327
|
-
(p) => p.key === page.key ? {
|
|
3328
|
-
...p,
|
|
3329
|
-
order: p.order.filter((k) => !moveSet.has(k)),
|
|
3330
|
-
minimized: p.minimized.filter((k) => !moveSet.has(k))
|
|
3331
|
-
} : p
|
|
3332
|
-
);
|
|
3333
|
-
let placed = false;
|
|
3334
|
-
for (let j = i + 1; j < pages.length && !placed; j += 1) {
|
|
3335
|
-
const candidate = pages[j];
|
|
3336
|
-
const trial = pages.map(
|
|
3337
|
-
(p) => p.key === candidate.key ? {
|
|
3338
|
-
...p,
|
|
3339
|
-
order: [...moving, ...p.order],
|
|
3340
|
-
minimized: [...p.minimized, ...movedMinimized]
|
|
3341
|
-
} : p
|
|
3342
|
-
);
|
|
3343
|
-
const items = pageWidgets(
|
|
3344
|
-
defs,
|
|
3345
|
-
{ ...locked, pages: trial },
|
|
3346
|
-
candidate.key
|
|
3347
|
-
);
|
|
3348
|
-
if (placeRows(items).length <= rowsAllowed(candidate, options)) {
|
|
3349
|
-
pages = trial;
|
|
3350
|
-
placed = true;
|
|
3351
|
-
}
|
|
3352
|
-
}
|
|
3353
|
-
if (!placed) {
|
|
3354
|
-
const key = nextPageKey(work());
|
|
3355
|
-
const title = uniqueTitle2(
|
|
3356
|
-
new Set(pages.map((p) => normalizedTitle(p.title))),
|
|
3357
|
-
`Page ${pages.length + 1}`,
|
|
3358
|
-
pages.length
|
|
3359
|
-
);
|
|
3360
|
-
pages = [
|
|
3361
|
-
...pages,
|
|
3362
|
-
{ key, title, order: moving, hidden: [], minimized: movedMinimized }
|
|
3363
|
-
];
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
const result = work();
|
|
3367
|
-
return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) && locked.settings === layout.settings ? layout : result;
|
|
3368
|
-
}
|
|
3369
|
-
|
|
3370
3592
|
// src/core/scale.ts
|
|
3371
3593
|
function niceStep(value) {
|
|
3372
3594
|
if (!(value > 0) || !Number.isFinite(value)) return 1;
|
|
@@ -4196,6 +4418,6 @@ function indent(block) {
|
|
|
4196
4418
|
return block.split("\n").map((line) => ` ${line}`).join("\n");
|
|
4197
4419
|
}
|
|
4198
4420
|
|
|
4199
|
-
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_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, clearWidgetSettings, coerceOptionValue, convertView, createLayoutPersistence, createTheme, createWidgetLoader, defaultDetailQuery, defaultOptionValue, defineWidget, deriveDetailData, describeUnknownError, 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, 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, viewChoices, viewKind, viewNote, 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 };
|
|
4200
4422
|
//# sourceMappingURL=core.js.map
|
|
4201
4423
|
//# sourceMappingURL=core.js.map
|