@richardmcquiston01/dashboard-widgets-toolkit 0.10.0 → 0.11.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 +1222 -17
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +13 -179
- package/dist/core.d.ts +13 -179
- package/dist/core.js +1191 -18
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1222 -17
- 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 +1191 -18
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +587 -32
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +75 -5
- package/dist/react.d.ts +75 -5
- package/dist/react.js +588 -34
- package/dist/react.js.map +1 -1
- package/dist/storage-VaaysVnZ.d.cts +1333 -0
- package/dist/storage-VaaysVnZ.d.ts +1333 -0
- package/package.json +1 -1
- package/dist/scale-N69bo7wo.d.cts +0 -758
- package/dist/scale-N69bo7wo.d.ts +0 -758
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createContext, useContext, useId, useState, useRef, useEffect,
|
|
1
|
+
import { createContext, useContext, useId, useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
|
|
4
4
|
// src/react/card.tsx
|
|
@@ -470,6 +470,265 @@ function placeAndFillRows(items, columns, spanOf) {
|
|
|
470
470
|
return { spans, rows };
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
// src/core/options.ts
|
|
474
|
+
var DATE_RANGE_PRESETS = [
|
|
475
|
+
"today",
|
|
476
|
+
"yesterday",
|
|
477
|
+
"last7",
|
|
478
|
+
"last30",
|
|
479
|
+
"last90",
|
|
480
|
+
"thisMonth",
|
|
481
|
+
"lastMonth",
|
|
482
|
+
"thisYear",
|
|
483
|
+
"lastYear"
|
|
484
|
+
];
|
|
485
|
+
var COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
486
|
+
var INTERVAL_PATTERN = /^(\d{4}-\d{2}-\d{2})\/(\d{4}-\d{2}-\d{2})$/;
|
|
487
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
488
|
+
function isClientApplied(option) {
|
|
489
|
+
return (option.type === "sort" || option.type === "columns") && option.apply === "client";
|
|
490
|
+
}
|
|
491
|
+
function civilToday(context) {
|
|
492
|
+
const now = context.now ?? Date.now();
|
|
493
|
+
let timeZone = context.timeZone ?? "UTC";
|
|
494
|
+
try {
|
|
495
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
496
|
+
} catch {
|
|
497
|
+
timeZone = "UTC";
|
|
498
|
+
}
|
|
499
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
500
|
+
timeZone,
|
|
501
|
+
year: "numeric",
|
|
502
|
+
month: "2-digit",
|
|
503
|
+
day: "2-digit"
|
|
504
|
+
}).formatToParts(new Date(now));
|
|
505
|
+
const pick = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
|
|
506
|
+
return { year: pick("year"), month: pick("month"), day: pick("day") };
|
|
507
|
+
}
|
|
508
|
+
function toIso(epochMs) {
|
|
509
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
510
|
+
}
|
|
511
|
+
var DAY_MS = 864e5;
|
|
512
|
+
function parseIsoDay(text) {
|
|
513
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
514
|
+
if (match === null) return void 0;
|
|
515
|
+
const epoch = Date.UTC(
|
|
516
|
+
Number(match[1]),
|
|
517
|
+
Number(match[2]) - 1,
|
|
518
|
+
Number(match[3])
|
|
519
|
+
);
|
|
520
|
+
return toIso(epoch) === text ? epoch : void 0;
|
|
521
|
+
}
|
|
522
|
+
function isIsoInterval(value) {
|
|
523
|
+
const match = INTERVAL_PATTERN.exec(value);
|
|
524
|
+
if (match === null) return false;
|
|
525
|
+
const start = parseIsoDay(match[1]);
|
|
526
|
+
const end = parseIsoDay(match[2]);
|
|
527
|
+
return start !== void 0 && end !== void 0 && start <= end;
|
|
528
|
+
}
|
|
529
|
+
function isPreset(value) {
|
|
530
|
+
return DATE_RANGE_PRESETS.includes(value);
|
|
531
|
+
}
|
|
532
|
+
function resolveDateRange(value, context = {}) {
|
|
533
|
+
if (isIsoInterval(value)) return value;
|
|
534
|
+
if (!isPreset(value)) return void 0;
|
|
535
|
+
const today = civilToday(context);
|
|
536
|
+
const todayMs = Date.UTC(today.year, today.month - 1, today.day);
|
|
537
|
+
const range = (startMs, endMs) => `${toIso(startMs)}/${toIso(endMs)}`;
|
|
538
|
+
switch (value) {
|
|
539
|
+
case "today":
|
|
540
|
+
return range(todayMs, todayMs);
|
|
541
|
+
case "yesterday":
|
|
542
|
+
return range(todayMs - DAY_MS, todayMs - DAY_MS);
|
|
543
|
+
case "last7":
|
|
544
|
+
return range(todayMs - 6 * DAY_MS, todayMs);
|
|
545
|
+
case "last30":
|
|
546
|
+
return range(todayMs - 29 * DAY_MS, todayMs);
|
|
547
|
+
case "last90":
|
|
548
|
+
return range(todayMs - 89 * DAY_MS, todayMs);
|
|
549
|
+
case "thisMonth":
|
|
550
|
+
return range(Date.UTC(today.year, today.month - 1, 1), todayMs);
|
|
551
|
+
case "lastMonth":
|
|
552
|
+
return range(
|
|
553
|
+
Date.UTC(today.year, today.month - 2, 1),
|
|
554
|
+
Date.UTC(today.year, today.month - 1, 1) - DAY_MS
|
|
555
|
+
);
|
|
556
|
+
case "thisYear":
|
|
557
|
+
return range(Date.UTC(today.year, 0, 1), todayMs);
|
|
558
|
+
case "lastYear":
|
|
559
|
+
return range(
|
|
560
|
+
Date.UTC(today.year - 1, 0, 1),
|
|
561
|
+
Date.UTC(today.year, 0, 1) - DAY_MS
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function onStepGrid(option, value) {
|
|
566
|
+
if (option.step === void 0) return true;
|
|
567
|
+
const steps = (value - option.min) / option.step;
|
|
568
|
+
return Math.abs(steps - Math.round(steps)) < 1e-9;
|
|
569
|
+
}
|
|
570
|
+
function parseSortValue(value) {
|
|
571
|
+
const separator = value.lastIndexOf(":");
|
|
572
|
+
if (separator <= 0) return void 0;
|
|
573
|
+
const column = value.slice(0, separator);
|
|
574
|
+
const direction = value.slice(separator + 1);
|
|
575
|
+
if (direction !== "asc" && direction !== "desc") return void 0;
|
|
576
|
+
return { column, direction };
|
|
577
|
+
}
|
|
578
|
+
function sortFromOptions(definition, values) {
|
|
579
|
+
if (values === void 0) return void 0;
|
|
580
|
+
const sortOption = definition.options?.find(
|
|
581
|
+
(option) => option.type === "sort"
|
|
582
|
+
);
|
|
583
|
+
if (sortOption === void 0) return void 0;
|
|
584
|
+
const chosen = values[sortOption.key];
|
|
585
|
+
return typeof chosen === "string" ? parseSortValue(chosen) : void 0;
|
|
586
|
+
}
|
|
587
|
+
function coerceOptionValue(option, value, context = {}) {
|
|
588
|
+
switch (option.type) {
|
|
589
|
+
case "choice":
|
|
590
|
+
return typeof value === "string" && option.choices.some((choice) => choice.value === value) ? value : void 0;
|
|
591
|
+
case "number":
|
|
592
|
+
return typeof value === "number" && Number.isFinite(value) && value >= option.min && value <= option.max && onStepGrid(option, value) ? value : void 0;
|
|
593
|
+
case "boolean":
|
|
594
|
+
return typeof value === "boolean" ? value : void 0;
|
|
595
|
+
case "text": {
|
|
596
|
+
if (typeof value !== "string") return void 0;
|
|
597
|
+
const trimmed = value.trim();
|
|
598
|
+
return trimmed.length <= option.maxLength && !CONTROL_CHARACTERS.test(trimmed) ? trimmed : void 0;
|
|
599
|
+
}
|
|
600
|
+
case "dateRange": {
|
|
601
|
+
if (typeof value !== "string") return void 0;
|
|
602
|
+
if (isPreset(value) && option.presets !== void 0 && !option.presets.some((preset) => preset.value === value)) {
|
|
603
|
+
return void 0;
|
|
604
|
+
}
|
|
605
|
+
return resolveDateRange(value, context);
|
|
606
|
+
}
|
|
607
|
+
case "color":
|
|
608
|
+
return typeof value === "string" && COLOR_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
609
|
+
case "sort": {
|
|
610
|
+
if (typeof value !== "string") return void 0;
|
|
611
|
+
const sort = parseSortValue(value);
|
|
612
|
+
return sort !== void 0 && option.columns.some((column) => column.key === sort.column) ? value : void 0;
|
|
613
|
+
}
|
|
614
|
+
case "columns": {
|
|
615
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
616
|
+
const keys = value;
|
|
617
|
+
const allowed = new Set(option.columns.map((c) => c.key));
|
|
618
|
+
const seen = /* @__PURE__ */ new Set();
|
|
619
|
+
for (const key of keys) {
|
|
620
|
+
if (typeof key !== "string" || !allowed.has(key) || seen.has(key)) {
|
|
621
|
+
return void 0;
|
|
622
|
+
}
|
|
623
|
+
seen.add(key);
|
|
624
|
+
}
|
|
625
|
+
return [...seen];
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function defaultOptionValue(option, context = {}) {
|
|
630
|
+
switch (option.type) {
|
|
631
|
+
case "dateRange":
|
|
632
|
+
return resolveDateRange(option.default, context);
|
|
633
|
+
case "sort":
|
|
634
|
+
return option.default;
|
|
635
|
+
case "columns":
|
|
636
|
+
return [...option.default];
|
|
637
|
+
default:
|
|
638
|
+
return option.default;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
var NO_VALUES = Object.freeze({});
|
|
642
|
+
function resolveOptionValues(definition, chosen, context = {}) {
|
|
643
|
+
const options = definition.options;
|
|
644
|
+
if (options === void 0 || options.length === 0) return NO_VALUES;
|
|
645
|
+
const values = {};
|
|
646
|
+
for (const option of options) {
|
|
647
|
+
const picked = chosen?.[option.key];
|
|
648
|
+
const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked, context)) ?? defaultOptionValue(option, context);
|
|
649
|
+
if (value !== void 0) values[option.key] = value;
|
|
650
|
+
}
|
|
651
|
+
return values;
|
|
652
|
+
}
|
|
653
|
+
function sameValue(a, b) {
|
|
654
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
655
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
656
|
+
}
|
|
657
|
+
return a === b;
|
|
658
|
+
}
|
|
659
|
+
function sameOptionValues(a, b) {
|
|
660
|
+
const keys = Object.keys({ ...a, ...b });
|
|
661
|
+
return keys.every((key) => sameValue(a[key], b[key]));
|
|
662
|
+
}
|
|
663
|
+
function optionsCacheSuffix(definition, values, context = {}) {
|
|
664
|
+
const parts = [];
|
|
665
|
+
for (const option of definition.options ?? []) {
|
|
666
|
+
if (isClientApplied(option)) continue;
|
|
667
|
+
const value = values[option.key];
|
|
668
|
+
if (sameValue(value, defaultOptionValue(option, context))) continue;
|
|
669
|
+
parts.push(
|
|
670
|
+
`${option.key}=${encodeURIComponent(JSON.stringify(value ?? null))}`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
parts.sort();
|
|
674
|
+
return parts.length === 0 ? "" : `?${parts.join("&")}`;
|
|
675
|
+
}
|
|
676
|
+
function tableColumnIndex(key) {
|
|
677
|
+
const match = /^c(\d+)$/.exec(key);
|
|
678
|
+
return match === null ? void 0 : Number(match[1]);
|
|
679
|
+
}
|
|
680
|
+
function sortBarItems(items, sort) {
|
|
681
|
+
const sign = sort.direction === "desc" ? -1 : 1;
|
|
682
|
+
return items.map((item, index) => ({ item, index })).sort((a, b) => {
|
|
683
|
+
const order = sort.column === "value" ? a.item.value - b.item.value : a.item.label.localeCompare(b.item.label);
|
|
684
|
+
return order * sign || a.index - b.index;
|
|
685
|
+
}).map(({ item }) => item);
|
|
686
|
+
}
|
|
687
|
+
function applyClientOptions(definition, data, values) {
|
|
688
|
+
const options = definition.options ?? [];
|
|
689
|
+
const sortOption = options.find(
|
|
690
|
+
(o) => o.type === "sort" && o.apply === "client"
|
|
691
|
+
);
|
|
692
|
+
const columnsOption = options.find(
|
|
693
|
+
(o) => o.type === "columns" && o.apply === "client"
|
|
694
|
+
);
|
|
695
|
+
const sortValue = sortOption === void 0 ? void 0 : values[sortOption.key];
|
|
696
|
+
const columnsValue = columnsOption === void 0 ? void 0 : values[columnsOption.key];
|
|
697
|
+
let next = data;
|
|
698
|
+
if (typeof sortValue === "string") {
|
|
699
|
+
const sort = parseSortValue(sortValue);
|
|
700
|
+
if (sort !== void 0 && next.kind === "BAR_LIST") {
|
|
701
|
+
if (sort.column === "label" || sort.column === "value") {
|
|
702
|
+
next = { ...next, items: sortBarItems(next.items, sort) };
|
|
703
|
+
}
|
|
704
|
+
} else if (sort !== void 0 && next.kind === "TABLE") {
|
|
705
|
+
const index = tableColumnIndex(sort.column);
|
|
706
|
+
if (index !== void 0 && index < next.columns.length) {
|
|
707
|
+
const sorted = queryRows(tableDetailData(next), {
|
|
708
|
+
page: 1,
|
|
709
|
+
pageSize: Math.max(1, next.rows.length),
|
|
710
|
+
sort
|
|
711
|
+
});
|
|
712
|
+
if (sorted.ok) next = { ...next, rows: sorted.value.rows };
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (Array.isArray(columnsValue) && next.kind === "TABLE") {
|
|
717
|
+
const table = next;
|
|
718
|
+
const indexes = columnsValue.map(tableColumnIndex).filter(
|
|
719
|
+
(index) => index !== void 0 && index < table.columns.length
|
|
720
|
+
);
|
|
721
|
+
if (indexes.length > 0) {
|
|
722
|
+
next = {
|
|
723
|
+
...table,
|
|
724
|
+
columns: indexes.map((i) => table.columns[i]),
|
|
725
|
+
rows: table.rows.map((row) => indexes.map((i) => row[i]))
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return next;
|
|
730
|
+
}
|
|
731
|
+
|
|
473
732
|
// src/core/scale.ts
|
|
474
733
|
function niceStep(value) {
|
|
475
734
|
if (!(value > 0) || !Number.isFinite(value)) return 1;
|
|
@@ -1236,7 +1495,8 @@ function GaugeWidget({
|
|
|
1236
1495
|
}
|
|
1237
1496
|
function TableWidget({
|
|
1238
1497
|
data,
|
|
1239
|
-
controls
|
|
1498
|
+
controls,
|
|
1499
|
+
initialSort
|
|
1240
1500
|
}) {
|
|
1241
1501
|
if (controls === void 0) {
|
|
1242
1502
|
return /* @__PURE__ */ jsxs("div", { className: "dwt-table-wrap", children: [
|
|
@@ -1244,7 +1504,14 @@ function TableWidget({
|
|
|
1244
1504
|
data.footer !== void 0 && /* @__PURE__ */ jsx("p", { className: "dwt-note", children: data.footer })
|
|
1245
1505
|
] });
|
|
1246
1506
|
}
|
|
1247
|
-
return /* @__PURE__ */ jsx(
|
|
1507
|
+
return /* @__PURE__ */ jsx(
|
|
1508
|
+
InteractiveTable,
|
|
1509
|
+
{
|
|
1510
|
+
data,
|
|
1511
|
+
controls,
|
|
1512
|
+
initialSort
|
|
1513
|
+
}
|
|
1514
|
+
);
|
|
1248
1515
|
}
|
|
1249
1516
|
function TableView({
|
|
1250
1517
|
data,
|
|
@@ -1297,13 +1564,14 @@ function nextTableSort(current, column) {
|
|
|
1297
1564
|
}
|
|
1298
1565
|
function InteractiveTable({
|
|
1299
1566
|
data,
|
|
1300
|
-
controls
|
|
1567
|
+
controls,
|
|
1568
|
+
initialSort
|
|
1301
1569
|
}) {
|
|
1302
1570
|
const slot = useSlotClassName();
|
|
1303
1571
|
const { labels, locale } = useWidgetSettings();
|
|
1304
1572
|
const searchId = useId();
|
|
1305
1573
|
const [search, setSearch] = useState("");
|
|
1306
|
-
const [sort, setSort] = useState(
|
|
1574
|
+
const [sort, setSort] = useState(initialSort);
|
|
1307
1575
|
const localeTag = typeof locale === "string" ? locale : locale?.[0];
|
|
1308
1576
|
const query = {
|
|
1309
1577
|
page: 1,
|
|
@@ -1405,7 +1673,8 @@ function AlertListWidget({
|
|
|
1405
1673
|
}
|
|
1406
1674
|
function WidgetContent({
|
|
1407
1675
|
data,
|
|
1408
|
-
tableControls
|
|
1676
|
+
tableControls,
|
|
1677
|
+
initialSort
|
|
1409
1678
|
}) {
|
|
1410
1679
|
switch (data.kind) {
|
|
1411
1680
|
case "TEXT":
|
|
@@ -1415,7 +1684,14 @@ function WidgetContent({
|
|
|
1415
1684
|
case "GAUGE":
|
|
1416
1685
|
return /* @__PURE__ */ jsx(GaugeWidget, { data });
|
|
1417
1686
|
case "TABLE":
|
|
1418
|
-
return /* @__PURE__ */ jsx(
|
|
1687
|
+
return /* @__PURE__ */ jsx(
|
|
1688
|
+
TableWidget,
|
|
1689
|
+
{
|
|
1690
|
+
data,
|
|
1691
|
+
controls: tableControls,
|
|
1692
|
+
initialSort
|
|
1693
|
+
}
|
|
1694
|
+
);
|
|
1419
1695
|
case "BAR_LIST":
|
|
1420
1696
|
return /* @__PURE__ */ jsx(BarListWidget, { data });
|
|
1421
1697
|
case "ALERT_LIST":
|
|
@@ -1612,7 +1888,8 @@ function ResolvedWidgetCard({
|
|
|
1612
1888
|
WidgetContent,
|
|
1613
1889
|
{
|
|
1614
1890
|
data: widget.data,
|
|
1615
|
-
tableControls: resolveTableControls(definition)
|
|
1891
|
+
tableControls: resolveTableControls(definition),
|
|
1892
|
+
initialSort: sortFromOptions(definition, widget.options)
|
|
1616
1893
|
}
|
|
1617
1894
|
) : null
|
|
1618
1895
|
}
|
|
@@ -2374,7 +2651,8 @@ function WidgetDetailDialog({
|
|
|
2374
2651
|
}
|
|
2375
2652
|
);
|
|
2376
2653
|
}
|
|
2377
|
-
|
|
2654
|
+
var NO_OPTION_VALUES = Object.freeze({});
|
|
2655
|
+
function useDetailData(definition, load, enabled, fallback, optionValues = NO_OPTION_VALUES) {
|
|
2378
2656
|
const [state, setState] = useState({ status: "loading" });
|
|
2379
2657
|
const [attempt, setAttempt] = useState(0);
|
|
2380
2658
|
useEffect(() => {
|
|
@@ -2384,6 +2662,7 @@ function useDetailData(definition, load, enabled, fallback) {
|
|
|
2384
2662
|
Promise.resolve().then(
|
|
2385
2663
|
() => load === void 0 ? void 0 : load(definition, {
|
|
2386
2664
|
signal: controller.signal,
|
|
2665
|
+
options: optionValues,
|
|
2387
2666
|
query: defaultDetailQuery()
|
|
2388
2667
|
})
|
|
2389
2668
|
).then((raw) => {
|
|
@@ -2411,7 +2690,7 @@ function useDetailData(definition, load, enabled, fallback) {
|
|
|
2411
2690
|
});
|
|
2412
2691
|
});
|
|
2413
2692
|
return () => controller.abort();
|
|
2414
|
-
}, [enabled, load, fallback, definition, attempt]);
|
|
2693
|
+
}, [enabled, load, fallback, definition, optionValues, attempt]);
|
|
2415
2694
|
return { state, reload: () => setAttempt((count) => count + 1) };
|
|
2416
2695
|
}
|
|
2417
2696
|
var NEW_PAGE_VALUE = "__new-page__";
|
|
@@ -2527,7 +2806,14 @@ function DetailHost({
|
|
|
2527
2806
|
const { definition } = widget;
|
|
2528
2807
|
const options = resolveDetailOptions(definition);
|
|
2529
2808
|
const pageSize = options?.pageSize ?? 25;
|
|
2530
|
-
const
|
|
2809
|
+
const optionSort = sortFromOptions(
|
|
2810
|
+
definition,
|
|
2811
|
+
widget.options
|
|
2812
|
+
);
|
|
2813
|
+
const [query, setQuery] = useState({
|
|
2814
|
+
...defaultDetailQuery(pageSize),
|
|
2815
|
+
...optionSort === void 0 ? {} : { sort: optionSort }
|
|
2816
|
+
});
|
|
2531
2817
|
const derived = useMemo(
|
|
2532
2818
|
() => widget.status === "ok" ? deriveDetailData(widget.data, locale) : void 0,
|
|
2533
2819
|
[widget, locale]
|
|
@@ -2536,7 +2822,8 @@ function DetailHost({
|
|
|
2536
2822
|
definition,
|
|
2537
2823
|
loadDetail,
|
|
2538
2824
|
true,
|
|
2539
|
-
derived
|
|
2825
|
+
derived,
|
|
2826
|
+
widget.options
|
|
2540
2827
|
);
|
|
2541
2828
|
const title = options?.title ?? definition.title;
|
|
2542
2829
|
return /* @__PURE__ */ jsx(WidgetDetailDialog, { title, open: true, onClose, children: /* @__PURE__ */ jsx(
|
|
@@ -3390,6 +3677,196 @@ function Dashboard({
|
|
|
3390
3677
|
}
|
|
3391
3678
|
);
|
|
3392
3679
|
}
|
|
3680
|
+
function useStoredLayout(options) {
|
|
3681
|
+
const {
|
|
3682
|
+
persistence,
|
|
3683
|
+
scope,
|
|
3684
|
+
definitions,
|
|
3685
|
+
defaultLayout,
|
|
3686
|
+
saveDelayMs = 500,
|
|
3687
|
+
initialLayout,
|
|
3688
|
+
normalizeOptions
|
|
3689
|
+
} = options;
|
|
3690
|
+
const scopeKey = persistence.keyFor(scope);
|
|
3691
|
+
const [layout, setLayoutState] = useState(
|
|
3692
|
+
initialLayout ?? defaultLayout
|
|
3693
|
+
);
|
|
3694
|
+
const [backup, setBackupState] = useState(null);
|
|
3695
|
+
const [status, setStatus] = useState("loading");
|
|
3696
|
+
const [error, setError] = useState(null);
|
|
3697
|
+
const [conflict, setConflict] = useState(null);
|
|
3698
|
+
const [remoteLayout, setRemoteLayout] = useState(
|
|
3699
|
+
null
|
|
3700
|
+
);
|
|
3701
|
+
const latest = useRef(layout);
|
|
3702
|
+
const scopeRef = useRef(scope);
|
|
3703
|
+
scopeRef.current = scope;
|
|
3704
|
+
const normalizeRef = useRef(normalizeOptions);
|
|
3705
|
+
normalizeRef.current = normalizeOptions;
|
|
3706
|
+
const timer = useRef(void 0);
|
|
3707
|
+
const dirty = useRef(false);
|
|
3708
|
+
const apply = useCallback((next) => {
|
|
3709
|
+
latest.current = next;
|
|
3710
|
+
setLayoutState(next);
|
|
3711
|
+
}, []);
|
|
3712
|
+
const finishSave = useCallback(
|
|
3713
|
+
(result, mine) => {
|
|
3714
|
+
if (!result.ok) {
|
|
3715
|
+
setError(result.error);
|
|
3716
|
+
setStatus("error");
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
const saved = result.value;
|
|
3720
|
+
if (saved.outcome === "saved") {
|
|
3721
|
+
dirty.current = latest.current !== mine;
|
|
3722
|
+
setError(null);
|
|
3723
|
+
setStatus(dirty.current ? "saving" : "ready");
|
|
3724
|
+
return;
|
|
3725
|
+
}
|
|
3726
|
+
if (saved.outcome === "kept-theirs") {
|
|
3727
|
+
dirty.current = false;
|
|
3728
|
+
apply(saved.layout);
|
|
3729
|
+
setError(null);
|
|
3730
|
+
setStatus("ready");
|
|
3731
|
+
return;
|
|
3732
|
+
}
|
|
3733
|
+
setConflict({ mine, theirs: saved.layout });
|
|
3734
|
+
setStatus("conflict");
|
|
3735
|
+
},
|
|
3736
|
+
[apply]
|
|
3737
|
+
);
|
|
3738
|
+
const flush = useCallback(() => {
|
|
3739
|
+
if (timer.current !== void 0) {
|
|
3740
|
+
clearTimeout(timer.current);
|
|
3741
|
+
timer.current = void 0;
|
|
3742
|
+
}
|
|
3743
|
+
if (!dirty.current) return;
|
|
3744
|
+
const mine = latest.current;
|
|
3745
|
+
setStatus("saving");
|
|
3746
|
+
void persistence.save(scopeRef.current, mine).then((result) => finishSave(result, mine));
|
|
3747
|
+
}, [persistence, finishSave]);
|
|
3748
|
+
useEffect(() => {
|
|
3749
|
+
let current = true;
|
|
3750
|
+
setStatus("loading");
|
|
3751
|
+
setConflict(null);
|
|
3752
|
+
setRemoteLayout(null);
|
|
3753
|
+
void persistence.load(scopeRef.current, definitions, normalizeRef.current).then((result) => {
|
|
3754
|
+
if (!current) return;
|
|
3755
|
+
if (!result.ok) {
|
|
3756
|
+
setError(result.error);
|
|
3757
|
+
setStatus("error");
|
|
3758
|
+
return;
|
|
3759
|
+
}
|
|
3760
|
+
setError(null);
|
|
3761
|
+
if (!dirty.current && result.value.layout !== null) {
|
|
3762
|
+
apply(result.value.layout);
|
|
3763
|
+
}
|
|
3764
|
+
setStatus(dirty.current ? "saving" : "ready");
|
|
3765
|
+
});
|
|
3766
|
+
void persistence.loadBackup(scopeRef.current).then((result) => {
|
|
3767
|
+
if (current && result.ok) setBackupState(result.value);
|
|
3768
|
+
});
|
|
3769
|
+
return () => {
|
|
3770
|
+
current = false;
|
|
3771
|
+
};
|
|
3772
|
+
}, [persistence, scopeKey, definitions, apply]);
|
|
3773
|
+
useEffect(() => {
|
|
3774
|
+
return persistence.watch(
|
|
3775
|
+
scopeRef.current,
|
|
3776
|
+
definitions,
|
|
3777
|
+
(loaded) => {
|
|
3778
|
+
if (loaded.layout === null) return;
|
|
3779
|
+
if (dirty.current) {
|
|
3780
|
+
setRemoteLayout(loaded.layout);
|
|
3781
|
+
return;
|
|
3782
|
+
}
|
|
3783
|
+
apply(loaded.layout);
|
|
3784
|
+
},
|
|
3785
|
+
normalizeRef.current
|
|
3786
|
+
);
|
|
3787
|
+
}, [persistence, scopeKey, definitions, apply]);
|
|
3788
|
+
useEffect(() => {
|
|
3789
|
+
const onHide = () => {
|
|
3790
|
+
if (typeof document === "undefined" || document.visibilityState !== "hidden") {
|
|
3791
|
+
return;
|
|
3792
|
+
}
|
|
3793
|
+
flush();
|
|
3794
|
+
};
|
|
3795
|
+
if (typeof window !== "undefined") {
|
|
3796
|
+
window.addEventListener("pagehide", flush);
|
|
3797
|
+
document.addEventListener("visibilitychange", onHide);
|
|
3798
|
+
}
|
|
3799
|
+
return () => {
|
|
3800
|
+
if (typeof window !== "undefined") {
|
|
3801
|
+
window.removeEventListener("pagehide", flush);
|
|
3802
|
+
document.removeEventListener("visibilitychange", onHide);
|
|
3803
|
+
}
|
|
3804
|
+
flush();
|
|
3805
|
+
};
|
|
3806
|
+
}, [flush]);
|
|
3807
|
+
const setLayout = useCallback(
|
|
3808
|
+
(next) => {
|
|
3809
|
+
apply(next);
|
|
3810
|
+
dirty.current = true;
|
|
3811
|
+
if (timer.current !== void 0) clearTimeout(timer.current);
|
|
3812
|
+
timer.current = setTimeout(flush, Math.max(0, saveDelayMs));
|
|
3813
|
+
},
|
|
3814
|
+
[apply, flush, saveDelayMs]
|
|
3815
|
+
);
|
|
3816
|
+
const setBackup = useCallback(
|
|
3817
|
+
(next) => {
|
|
3818
|
+
setBackupState(next);
|
|
3819
|
+
if (next === null) return;
|
|
3820
|
+
void persistence.saveBackup(scopeRef.current, next).then((result) => {
|
|
3821
|
+
if (!result.ok) setError(result.error);
|
|
3822
|
+
});
|
|
3823
|
+
},
|
|
3824
|
+
[persistence]
|
|
3825
|
+
);
|
|
3826
|
+
const resolveConflict = useCallback(
|
|
3827
|
+
(choice) => {
|
|
3828
|
+
const pending = conflict;
|
|
3829
|
+
if (pending === null) return;
|
|
3830
|
+
setConflict(null);
|
|
3831
|
+
if (choice === "theirs") {
|
|
3832
|
+
dirty.current = false;
|
|
3833
|
+
apply(pending.theirs);
|
|
3834
|
+
setStatus("ready");
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
setStatus("saving");
|
|
3838
|
+
void persistence.save(scopeRef.current, pending.mine, { force: true }).then((result) => finishSave(result, pending.mine));
|
|
3839
|
+
},
|
|
3840
|
+
[conflict, persistence, apply, finishSave]
|
|
3841
|
+
);
|
|
3842
|
+
const resolveRemote = useCallback(
|
|
3843
|
+
(choice) => {
|
|
3844
|
+
const remote = remoteLayout;
|
|
3845
|
+
setRemoteLayout(null);
|
|
3846
|
+
if (choice === "use" && remote !== null) {
|
|
3847
|
+
if (timer.current !== void 0) {
|
|
3848
|
+
clearTimeout(timer.current);
|
|
3849
|
+
timer.current = void 0;
|
|
3850
|
+
}
|
|
3851
|
+
dirty.current = false;
|
|
3852
|
+
apply(remote);
|
|
3853
|
+
}
|
|
3854
|
+
},
|
|
3855
|
+
[remoteLayout, apply]
|
|
3856
|
+
);
|
|
3857
|
+
return {
|
|
3858
|
+
layout,
|
|
3859
|
+
setLayout,
|
|
3860
|
+
backup,
|
|
3861
|
+
setBackup,
|
|
3862
|
+
status,
|
|
3863
|
+
error,
|
|
3864
|
+
conflict,
|
|
3865
|
+
resolveConflict,
|
|
3866
|
+
remoteLayout,
|
|
3867
|
+
resolveRemote
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3393
3870
|
|
|
3394
3871
|
// src/core/payload.ts
|
|
3395
3872
|
var WIDGET_KINDS = [
|
|
@@ -3733,12 +4210,33 @@ function failedWidget(definition, error) {
|
|
|
3733
4210
|
function loadingWidgets(definitions) {
|
|
3734
4211
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
3735
4212
|
}
|
|
3736
|
-
function cacheKeyFor(definition, options) {
|
|
3737
|
-
|
|
4213
|
+
function cacheKeyFor(definition, options, values) {
|
|
4214
|
+
const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
4215
|
+
return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
|
|
4216
|
+
}
|
|
4217
|
+
function optionValuesFor(definition, options) {
|
|
4218
|
+
return resolveOptionValues(
|
|
4219
|
+
definition,
|
|
4220
|
+
options.optionValues?.[definition.key],
|
|
4221
|
+
options
|
|
4222
|
+
);
|
|
4223
|
+
}
|
|
4224
|
+
function finalizeWidget(widget, values) {
|
|
4225
|
+
if (Object.keys(values).length === 0) return widget;
|
|
4226
|
+
if (widget.status === "ok") {
|
|
4227
|
+
const data = applyClientOptions(
|
|
4228
|
+
widget.definition,
|
|
4229
|
+
widget.data,
|
|
4230
|
+
values
|
|
4231
|
+
);
|
|
4232
|
+
return { ...widget, data, options: values };
|
|
4233
|
+
}
|
|
4234
|
+
return { ...widget, options: values };
|
|
3738
4235
|
}
|
|
3739
4236
|
var LoadInterruption = class extends Error {
|
|
3740
4237
|
};
|
|
3741
|
-
async function
|
|
4238
|
+
async function resolveWidgetUnapplied(definition, provider, context, options = {}) {
|
|
4239
|
+
const values = optionValuesFor(definition, options);
|
|
3742
4240
|
const quotedKey = JSON.stringify(definition.key);
|
|
3743
4241
|
if (provider === void 0) {
|
|
3744
4242
|
return failedWidget(
|
|
@@ -3773,7 +4271,10 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
3773
4271
|
payload = await Promise.race([
|
|
3774
4272
|
Promise.resolve().then(() => {
|
|
3775
4273
|
if (controller.signal.aborted) throw new LoadInterruption();
|
|
3776
|
-
return provider(context, definition, {
|
|
4274
|
+
return provider(context, definition, {
|
|
4275
|
+
signal: controller.signal,
|
|
4276
|
+
options: values
|
|
4277
|
+
});
|
|
3777
4278
|
}),
|
|
3778
4279
|
interrupted
|
|
3779
4280
|
]);
|
|
@@ -3797,7 +4298,7 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
3797
4298
|
const storedAt = Date.now();
|
|
3798
4299
|
if (options.cache !== void 0) {
|
|
3799
4300
|
try {
|
|
3800
|
-
await options.cache.set(cacheKeyFor(definition, options), {
|
|
4301
|
+
await options.cache.set(cacheKeyFor(definition, options, values), {
|
|
3801
4302
|
payload: resolved.data,
|
|
3802
4303
|
storedAt
|
|
3803
4304
|
});
|
|
@@ -3814,7 +4315,17 @@ function widgetsFor(definitions, roles) {
|
|
|
3814
4315
|
|
|
3815
4316
|
// src/core/loader.ts
|
|
3816
4317
|
function createWidgetLoader(options) {
|
|
3817
|
-
const { definitions, providers, context, ...
|
|
4318
|
+
const { definitions, providers, context, ...baseOptions } = options;
|
|
4319
|
+
const chosen = new Map(
|
|
4320
|
+
Object.entries(baseOptions.optionValues ?? {}).flatMap(
|
|
4321
|
+
([key, values]) => values === void 0 ? [] : [[key, values]]
|
|
4322
|
+
)
|
|
4323
|
+
);
|
|
4324
|
+
const resolveOptions = baseOptions;
|
|
4325
|
+
function currentOptions() {
|
|
4326
|
+
return { ...resolveOptions, optionValues: Object.fromEntries(chosen) };
|
|
4327
|
+
}
|
|
4328
|
+
const unapplied = /* @__PURE__ */ new Map();
|
|
3818
4329
|
const visible = widgetsFor(definitions, context.roles);
|
|
3819
4330
|
let snapshot = loadingWidgets(visible);
|
|
3820
4331
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -3855,19 +4366,19 @@ function createWidgetLoader(options) {
|
|
|
3855
4366
|
const controller = new AbortController();
|
|
3856
4367
|
inFlight.set(key, controller);
|
|
3857
4368
|
const isCurrent = () => !disposed && generations.get(key) === generation;
|
|
3858
|
-
|
|
4369
|
+
const runOptions = currentOptions();
|
|
4370
|
+
const values = optionValuesFor(definition, runOptions);
|
|
4371
|
+
if (readCache && runOptions.cache !== void 0) {
|
|
3859
4372
|
try {
|
|
3860
|
-
const entry = await
|
|
3861
|
-
cacheKeyFor(definition,
|
|
4373
|
+
const entry = await runOptions.cache.get(
|
|
4374
|
+
cacheKeyFor(definition, runOptions, values)
|
|
3862
4375
|
);
|
|
3863
4376
|
if (entry !== void 0 && isCurrent()) {
|
|
3864
|
-
const cached = resolvePayload(
|
|
3865
|
-
definition,
|
|
3866
|
-
entry.payload,
|
|
3867
|
-
resolveOptions
|
|
3868
|
-
);
|
|
4377
|
+
const cached = resolvePayload(definition, entry.payload, runOptions);
|
|
3869
4378
|
if (cached.status === "ok") {
|
|
3870
|
-
|
|
4379
|
+
const stale = { ...cached, updatedAt: entry.storedAt, stale: true };
|
|
4380
|
+
unapplied.set(key, stale);
|
|
4381
|
+
replace(key, finalizeWidget(stale, values));
|
|
3871
4382
|
}
|
|
3872
4383
|
}
|
|
3873
4384
|
} catch {
|
|
@@ -3875,13 +4386,15 @@ function createWidgetLoader(options) {
|
|
|
3875
4386
|
}
|
|
3876
4387
|
if (!isCurrent()) return;
|
|
3877
4388
|
const provider = Object.prototype.hasOwnProperty.call(providers, key) ? providers[key] : void 0;
|
|
3878
|
-
const result = await
|
|
3879
|
-
...
|
|
4389
|
+
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
4390
|
+
...runOptions,
|
|
3880
4391
|
signal: controller.signal
|
|
3881
4392
|
});
|
|
3882
4393
|
if (!isCurrent()) return;
|
|
3883
4394
|
inFlight.delete(key);
|
|
3884
|
-
|
|
4395
|
+
if (result.status === "ok") unapplied.set(key, result);
|
|
4396
|
+
else unapplied.delete(key);
|
|
4397
|
+
replace(key, finalizeWidget(result, values));
|
|
3885
4398
|
}
|
|
3886
4399
|
function start(definition, readCache) {
|
|
3887
4400
|
started.add(definition.key);
|
|
@@ -3916,6 +4429,29 @@ function createWidgetLoader(options) {
|
|
|
3916
4429
|
start(definition, false);
|
|
3917
4430
|
}
|
|
3918
4431
|
}
|
|
4432
|
+
function setOptions(key, values) {
|
|
4433
|
+
if (disposed) return;
|
|
4434
|
+
const definition = definitionFor(key);
|
|
4435
|
+
if (definition === void 0) return;
|
|
4436
|
+
const before = optionValuesFor(definition, currentOptions());
|
|
4437
|
+
chosen.set(key, { ...values });
|
|
4438
|
+
const runOptions = currentOptions();
|
|
4439
|
+
const after = optionValuesFor(definition, runOptions);
|
|
4440
|
+
if (sameOptionValues(before, after) || !started.has(key)) return;
|
|
4441
|
+
const kept = unapplied.get(key);
|
|
4442
|
+
if (kept !== void 0 && optionsCacheSuffix(definition, before, runOptions) === optionsCacheSuffix(definition, after, runOptions)) {
|
|
4443
|
+
replace(key, finalizeWidget(kept, after));
|
|
4444
|
+
return;
|
|
4445
|
+
}
|
|
4446
|
+
const current = snapshot.find(
|
|
4447
|
+
(widget) => widget.definition.key === key
|
|
4448
|
+
);
|
|
4449
|
+
if (current?.status === "ok") replace(key, { ...current, stale: true });
|
|
4450
|
+
else if (current?.status === "error") {
|
|
4451
|
+
replace(key, { definition, status: "loading" });
|
|
4452
|
+
}
|
|
4453
|
+
start(definition, true);
|
|
4454
|
+
}
|
|
3919
4455
|
function dispose() {
|
|
3920
4456
|
disposed = true;
|
|
3921
4457
|
abortAll(new Error("The widget loader was disposed."));
|
|
@@ -3933,6 +4469,7 @@ function createWidgetLoader(options) {
|
|
|
3933
4469
|
load,
|
|
3934
4470
|
loadAll,
|
|
3935
4471
|
refresh,
|
|
4472
|
+
setOptions,
|
|
3936
4473
|
dispose
|
|
3937
4474
|
};
|
|
3938
4475
|
}
|
|
@@ -3947,7 +4484,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3947
4484
|
timeoutMs,
|
|
3948
4485
|
signal,
|
|
3949
4486
|
cache,
|
|
3950
|
-
cacheKey
|
|
4487
|
+
cacheKey,
|
|
4488
|
+
optionValues
|
|
3951
4489
|
} = options;
|
|
3952
4490
|
const placeholders = useMemo(
|
|
3953
4491
|
() => loadingWidgets(widgetsFor(definitions, context.roles)),
|
|
@@ -3957,6 +4495,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3957
4495
|
const [loader, setLoader] = useState(null);
|
|
3958
4496
|
const loaderRef = useRef(null);
|
|
3959
4497
|
const gridRef = useRef(null);
|
|
4498
|
+
const optionValuesRef = useRef(optionValues);
|
|
4499
|
+
optionValuesRef.current = optionValues;
|
|
3960
4500
|
useEffect(() => {
|
|
3961
4501
|
const created = createWidgetLoader({
|
|
3962
4502
|
definitions,
|
|
@@ -3966,7 +4506,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3966
4506
|
...timeoutMs === void 0 ? {} : { timeoutMs },
|
|
3967
4507
|
...signal === void 0 ? {} : { signal },
|
|
3968
4508
|
...cache === void 0 ? {} : { cache },
|
|
3969
|
-
...cacheKey === void 0 ? {} : { cacheKey }
|
|
4509
|
+
...cacheKey === void 0 ? {} : { cacheKey },
|
|
4510
|
+
...optionValuesRef.current === void 0 ? {} : { optionValues: optionValuesRef.current }
|
|
3970
4511
|
});
|
|
3971
4512
|
loaderRef.current = created;
|
|
3972
4513
|
setWidgets(created.getSnapshot());
|
|
@@ -4038,12 +4579,25 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
4038
4579
|
}, refreshMs);
|
|
4039
4580
|
return () => clearInterval(timer);
|
|
4040
4581
|
}, [loader, refreshMs]);
|
|
4582
|
+
const optionSignature = JSON.stringify(optionValues ?? {});
|
|
4583
|
+
useEffect(() => {
|
|
4584
|
+
if (loader === null) return;
|
|
4585
|
+
for (const [key, chosen] of Object.entries(optionValuesRef.current ?? {})) {
|
|
4586
|
+
if (chosen !== void 0) loader.setOptions(key, chosen);
|
|
4587
|
+
}
|
|
4588
|
+
}, [loader, optionSignature]);
|
|
4589
|
+
const setOptions = useCallback(
|
|
4590
|
+
(key, chosen) => {
|
|
4591
|
+
loaderRef.current?.setOptions(key, chosen);
|
|
4592
|
+
},
|
|
4593
|
+
[]
|
|
4594
|
+
);
|
|
4041
4595
|
const refresh = useCallback((key) => {
|
|
4042
4596
|
loaderRef.current?.refresh(key);
|
|
4043
4597
|
}, []);
|
|
4044
|
-
return { widgets, refresh, gridRef };
|
|
4598
|
+
return { widgets, refresh, setOptions, gridRef };
|
|
4045
4599
|
}
|
|
4046
4600
|
|
|
4047
|
-
export { AlertListWidget, BarListWidget, DEFAULT_LABELS, DEFAULT_SERIES_COLORS, Dashboard, GaugeWidget, GraphWidget, KpiWidget, ResolvedWidgetCard, TableWidget, TextWidget, Thumbnail, WidgetCard, WidgetContent, WidgetDetail, WidgetDetailDialog, WidgetGrid, WidgetLink, WidgetSettingsProvider, barPath, buildChartModel, describeChart, joinClassNames, seriesColor, useDetailData, useWidgetSettings, useWidgets, widgetTitle };
|
|
4601
|
+
export { AlertListWidget, BarListWidget, DEFAULT_LABELS, DEFAULT_SERIES_COLORS, Dashboard, GaugeWidget, GraphWidget, KpiWidget, ResolvedWidgetCard, TableWidget, TextWidget, Thumbnail, WidgetCard, WidgetContent, WidgetDetail, WidgetDetailDialog, WidgetGrid, WidgetLink, WidgetSettingsProvider, barPath, buildChartModel, describeChart, joinClassNames, seriesColor, useDetailData, useStoredLayout, useWidgetSettings, useWidgets, widgetTitle };
|
|
4048
4602
|
//# sourceMappingURL=react.js.map
|
|
4049
4603
|
//# sourceMappingURL=react.js.map
|