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