@richardmcquiston01/dashboard-widgets-toolkit 0.10.0 → 0.11.1
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 +593 -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 +594 -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,20 @@ 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);
|
|
1575
|
+
const seed = initialSort === void 0 ? "" : `${initialSort.column}:${initialSort.direction}`;
|
|
1576
|
+
const [seededWith, setSeededWith] = useState(seed);
|
|
1577
|
+
if (seededWith !== seed) {
|
|
1578
|
+
setSeededWith(seed);
|
|
1579
|
+
setSort(initialSort);
|
|
1580
|
+
}
|
|
1307
1581
|
const localeTag = typeof locale === "string" ? locale : locale?.[0];
|
|
1308
1582
|
const query = {
|
|
1309
1583
|
page: 1,
|
|
@@ -1405,7 +1679,8 @@ function AlertListWidget({
|
|
|
1405
1679
|
}
|
|
1406
1680
|
function WidgetContent({
|
|
1407
1681
|
data,
|
|
1408
|
-
tableControls
|
|
1682
|
+
tableControls,
|
|
1683
|
+
initialSort
|
|
1409
1684
|
}) {
|
|
1410
1685
|
switch (data.kind) {
|
|
1411
1686
|
case "TEXT":
|
|
@@ -1415,7 +1690,14 @@ function WidgetContent({
|
|
|
1415
1690
|
case "GAUGE":
|
|
1416
1691
|
return /* @__PURE__ */ jsx(GaugeWidget, { data });
|
|
1417
1692
|
case "TABLE":
|
|
1418
|
-
return /* @__PURE__ */ jsx(
|
|
1693
|
+
return /* @__PURE__ */ jsx(
|
|
1694
|
+
TableWidget,
|
|
1695
|
+
{
|
|
1696
|
+
data,
|
|
1697
|
+
controls: tableControls,
|
|
1698
|
+
initialSort
|
|
1699
|
+
}
|
|
1700
|
+
);
|
|
1419
1701
|
case "BAR_LIST":
|
|
1420
1702
|
return /* @__PURE__ */ jsx(BarListWidget, { data });
|
|
1421
1703
|
case "ALERT_LIST":
|
|
@@ -1612,7 +1894,8 @@ function ResolvedWidgetCard({
|
|
|
1612
1894
|
WidgetContent,
|
|
1613
1895
|
{
|
|
1614
1896
|
data: widget.data,
|
|
1615
|
-
tableControls: resolveTableControls(definition)
|
|
1897
|
+
tableControls: resolveTableControls(definition),
|
|
1898
|
+
initialSort: sortFromOptions(definition, widget.options)
|
|
1616
1899
|
}
|
|
1617
1900
|
) : null
|
|
1618
1901
|
}
|
|
@@ -2374,7 +2657,8 @@ function WidgetDetailDialog({
|
|
|
2374
2657
|
}
|
|
2375
2658
|
);
|
|
2376
2659
|
}
|
|
2377
|
-
|
|
2660
|
+
var NO_OPTION_VALUES = Object.freeze({});
|
|
2661
|
+
function useDetailData(definition, load, enabled, fallback, optionValues = NO_OPTION_VALUES) {
|
|
2378
2662
|
const [state, setState] = useState({ status: "loading" });
|
|
2379
2663
|
const [attempt, setAttempt] = useState(0);
|
|
2380
2664
|
useEffect(() => {
|
|
@@ -2384,6 +2668,7 @@ function useDetailData(definition, load, enabled, fallback) {
|
|
|
2384
2668
|
Promise.resolve().then(
|
|
2385
2669
|
() => load === void 0 ? void 0 : load(definition, {
|
|
2386
2670
|
signal: controller.signal,
|
|
2671
|
+
options: optionValues,
|
|
2387
2672
|
query: defaultDetailQuery()
|
|
2388
2673
|
})
|
|
2389
2674
|
).then((raw) => {
|
|
@@ -2411,7 +2696,7 @@ function useDetailData(definition, load, enabled, fallback) {
|
|
|
2411
2696
|
});
|
|
2412
2697
|
});
|
|
2413
2698
|
return () => controller.abort();
|
|
2414
|
-
}, [enabled, load, fallback, definition, attempt]);
|
|
2699
|
+
}, [enabled, load, fallback, definition, optionValues, attempt]);
|
|
2415
2700
|
return { state, reload: () => setAttempt((count) => count + 1) };
|
|
2416
2701
|
}
|
|
2417
2702
|
var NEW_PAGE_VALUE = "__new-page__";
|
|
@@ -2527,7 +2812,14 @@ function DetailHost({
|
|
|
2527
2812
|
const { definition } = widget;
|
|
2528
2813
|
const options = resolveDetailOptions(definition);
|
|
2529
2814
|
const pageSize = options?.pageSize ?? 25;
|
|
2530
|
-
const
|
|
2815
|
+
const optionSort = sortFromOptions(
|
|
2816
|
+
definition,
|
|
2817
|
+
widget.options
|
|
2818
|
+
);
|
|
2819
|
+
const [query, setQuery] = useState({
|
|
2820
|
+
...defaultDetailQuery(pageSize),
|
|
2821
|
+
...optionSort === void 0 ? {} : { sort: optionSort }
|
|
2822
|
+
});
|
|
2531
2823
|
const derived = useMemo(
|
|
2532
2824
|
() => widget.status === "ok" ? deriveDetailData(widget.data, locale) : void 0,
|
|
2533
2825
|
[widget, locale]
|
|
@@ -2536,7 +2828,8 @@ function DetailHost({
|
|
|
2536
2828
|
definition,
|
|
2537
2829
|
loadDetail,
|
|
2538
2830
|
true,
|
|
2539
|
-
derived
|
|
2831
|
+
derived,
|
|
2832
|
+
widget.options
|
|
2540
2833
|
);
|
|
2541
2834
|
const title = options?.title ?? definition.title;
|
|
2542
2835
|
return /* @__PURE__ */ jsx(WidgetDetailDialog, { title, open: true, onClose, children: /* @__PURE__ */ jsx(
|
|
@@ -3390,6 +3683,196 @@ function Dashboard({
|
|
|
3390
3683
|
}
|
|
3391
3684
|
);
|
|
3392
3685
|
}
|
|
3686
|
+
function useStoredLayout(options) {
|
|
3687
|
+
const {
|
|
3688
|
+
persistence,
|
|
3689
|
+
scope,
|
|
3690
|
+
definitions,
|
|
3691
|
+
defaultLayout,
|
|
3692
|
+
saveDelayMs = 500,
|
|
3693
|
+
initialLayout,
|
|
3694
|
+
normalizeOptions
|
|
3695
|
+
} = options;
|
|
3696
|
+
const scopeKey = persistence.keyFor(scope);
|
|
3697
|
+
const [layout, setLayoutState] = useState(
|
|
3698
|
+
initialLayout ?? defaultLayout
|
|
3699
|
+
);
|
|
3700
|
+
const [backup, setBackupState] = useState(null);
|
|
3701
|
+
const [status, setStatus] = useState("loading");
|
|
3702
|
+
const [error, setError] = useState(null);
|
|
3703
|
+
const [conflict, setConflict] = useState(null);
|
|
3704
|
+
const [remoteLayout, setRemoteLayout] = useState(
|
|
3705
|
+
null
|
|
3706
|
+
);
|
|
3707
|
+
const latest = useRef(layout);
|
|
3708
|
+
const scopeRef = useRef(scope);
|
|
3709
|
+
scopeRef.current = scope;
|
|
3710
|
+
const normalizeRef = useRef(normalizeOptions);
|
|
3711
|
+
normalizeRef.current = normalizeOptions;
|
|
3712
|
+
const timer = useRef(void 0);
|
|
3713
|
+
const dirty = useRef(false);
|
|
3714
|
+
const apply = useCallback((next) => {
|
|
3715
|
+
latest.current = next;
|
|
3716
|
+
setLayoutState(next);
|
|
3717
|
+
}, []);
|
|
3718
|
+
const finishSave = useCallback(
|
|
3719
|
+
(result, mine) => {
|
|
3720
|
+
if (!result.ok) {
|
|
3721
|
+
setError(result.error);
|
|
3722
|
+
setStatus("error");
|
|
3723
|
+
return;
|
|
3724
|
+
}
|
|
3725
|
+
const saved = result.value;
|
|
3726
|
+
if (saved.outcome === "saved") {
|
|
3727
|
+
dirty.current = latest.current !== mine;
|
|
3728
|
+
setError(null);
|
|
3729
|
+
setStatus(dirty.current ? "saving" : "ready");
|
|
3730
|
+
return;
|
|
3731
|
+
}
|
|
3732
|
+
if (saved.outcome === "kept-theirs") {
|
|
3733
|
+
dirty.current = false;
|
|
3734
|
+
apply(saved.layout);
|
|
3735
|
+
setError(null);
|
|
3736
|
+
setStatus("ready");
|
|
3737
|
+
return;
|
|
3738
|
+
}
|
|
3739
|
+
setConflict({ mine, theirs: saved.layout });
|
|
3740
|
+
setStatus("conflict");
|
|
3741
|
+
},
|
|
3742
|
+
[apply]
|
|
3743
|
+
);
|
|
3744
|
+
const flush = useCallback(() => {
|
|
3745
|
+
if (timer.current !== void 0) {
|
|
3746
|
+
clearTimeout(timer.current);
|
|
3747
|
+
timer.current = void 0;
|
|
3748
|
+
}
|
|
3749
|
+
if (!dirty.current) return;
|
|
3750
|
+
const mine = latest.current;
|
|
3751
|
+
setStatus("saving");
|
|
3752
|
+
void persistence.save(scopeRef.current, mine).then((result) => finishSave(result, mine));
|
|
3753
|
+
}, [persistence, finishSave]);
|
|
3754
|
+
useEffect(() => {
|
|
3755
|
+
let current = true;
|
|
3756
|
+
setStatus("loading");
|
|
3757
|
+
setConflict(null);
|
|
3758
|
+
setRemoteLayout(null);
|
|
3759
|
+
void persistence.load(scopeRef.current, definitions, normalizeRef.current).then((result) => {
|
|
3760
|
+
if (!current) return;
|
|
3761
|
+
if (!result.ok) {
|
|
3762
|
+
setError(result.error);
|
|
3763
|
+
setStatus("error");
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
setError(null);
|
|
3767
|
+
if (!dirty.current && result.value.layout !== null) {
|
|
3768
|
+
apply(result.value.layout);
|
|
3769
|
+
}
|
|
3770
|
+
setStatus(dirty.current ? "saving" : "ready");
|
|
3771
|
+
});
|
|
3772
|
+
void persistence.loadBackup(scopeRef.current).then((result) => {
|
|
3773
|
+
if (current && result.ok) setBackupState(result.value);
|
|
3774
|
+
});
|
|
3775
|
+
return () => {
|
|
3776
|
+
current = false;
|
|
3777
|
+
};
|
|
3778
|
+
}, [persistence, scopeKey, definitions, apply]);
|
|
3779
|
+
useEffect(() => {
|
|
3780
|
+
return persistence.watch(
|
|
3781
|
+
scopeRef.current,
|
|
3782
|
+
definitions,
|
|
3783
|
+
(loaded) => {
|
|
3784
|
+
if (loaded.layout === null) return;
|
|
3785
|
+
if (dirty.current) {
|
|
3786
|
+
setRemoteLayout(loaded.layout);
|
|
3787
|
+
return;
|
|
3788
|
+
}
|
|
3789
|
+
apply(loaded.layout);
|
|
3790
|
+
},
|
|
3791
|
+
normalizeRef.current
|
|
3792
|
+
);
|
|
3793
|
+
}, [persistence, scopeKey, definitions, apply]);
|
|
3794
|
+
useEffect(() => {
|
|
3795
|
+
const onHide = () => {
|
|
3796
|
+
if (typeof document === "undefined" || document.visibilityState !== "hidden") {
|
|
3797
|
+
return;
|
|
3798
|
+
}
|
|
3799
|
+
flush();
|
|
3800
|
+
};
|
|
3801
|
+
if (typeof window !== "undefined") {
|
|
3802
|
+
window.addEventListener("pagehide", flush);
|
|
3803
|
+
document.addEventListener("visibilitychange", onHide);
|
|
3804
|
+
}
|
|
3805
|
+
return () => {
|
|
3806
|
+
if (typeof window !== "undefined") {
|
|
3807
|
+
window.removeEventListener("pagehide", flush);
|
|
3808
|
+
document.removeEventListener("visibilitychange", onHide);
|
|
3809
|
+
}
|
|
3810
|
+
flush();
|
|
3811
|
+
};
|
|
3812
|
+
}, [flush]);
|
|
3813
|
+
const setLayout = useCallback(
|
|
3814
|
+
(next) => {
|
|
3815
|
+
apply(next);
|
|
3816
|
+
dirty.current = true;
|
|
3817
|
+
if (timer.current !== void 0) clearTimeout(timer.current);
|
|
3818
|
+
timer.current = setTimeout(flush, Math.max(0, saveDelayMs));
|
|
3819
|
+
},
|
|
3820
|
+
[apply, flush, saveDelayMs]
|
|
3821
|
+
);
|
|
3822
|
+
const setBackup = useCallback(
|
|
3823
|
+
(next) => {
|
|
3824
|
+
setBackupState(next);
|
|
3825
|
+
if (next === null) return;
|
|
3826
|
+
void persistence.saveBackup(scopeRef.current, next).then((result) => {
|
|
3827
|
+
if (!result.ok) setError(result.error);
|
|
3828
|
+
});
|
|
3829
|
+
},
|
|
3830
|
+
[persistence]
|
|
3831
|
+
);
|
|
3832
|
+
const resolveConflict = useCallback(
|
|
3833
|
+
(choice) => {
|
|
3834
|
+
const pending = conflict;
|
|
3835
|
+
if (pending === null) return;
|
|
3836
|
+
setConflict(null);
|
|
3837
|
+
if (choice === "theirs") {
|
|
3838
|
+
dirty.current = false;
|
|
3839
|
+
apply(pending.theirs);
|
|
3840
|
+
setStatus("ready");
|
|
3841
|
+
return;
|
|
3842
|
+
}
|
|
3843
|
+
setStatus("saving");
|
|
3844
|
+
void persistence.save(scopeRef.current, pending.mine, { force: true }).then((result) => finishSave(result, pending.mine));
|
|
3845
|
+
},
|
|
3846
|
+
[conflict, persistence, apply, finishSave]
|
|
3847
|
+
);
|
|
3848
|
+
const resolveRemote = useCallback(
|
|
3849
|
+
(choice) => {
|
|
3850
|
+
const remote = remoteLayout;
|
|
3851
|
+
setRemoteLayout(null);
|
|
3852
|
+
if (choice === "use" && remote !== null) {
|
|
3853
|
+
if (timer.current !== void 0) {
|
|
3854
|
+
clearTimeout(timer.current);
|
|
3855
|
+
timer.current = void 0;
|
|
3856
|
+
}
|
|
3857
|
+
dirty.current = false;
|
|
3858
|
+
apply(remote);
|
|
3859
|
+
}
|
|
3860
|
+
},
|
|
3861
|
+
[remoteLayout, apply]
|
|
3862
|
+
);
|
|
3863
|
+
return {
|
|
3864
|
+
layout,
|
|
3865
|
+
setLayout,
|
|
3866
|
+
backup,
|
|
3867
|
+
setBackup,
|
|
3868
|
+
status,
|
|
3869
|
+
error,
|
|
3870
|
+
conflict,
|
|
3871
|
+
resolveConflict,
|
|
3872
|
+
remoteLayout,
|
|
3873
|
+
resolveRemote
|
|
3874
|
+
};
|
|
3875
|
+
}
|
|
3393
3876
|
|
|
3394
3877
|
// src/core/payload.ts
|
|
3395
3878
|
var WIDGET_KINDS = [
|
|
@@ -3733,12 +4216,33 @@ function failedWidget(definition, error) {
|
|
|
3733
4216
|
function loadingWidgets(definitions) {
|
|
3734
4217
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
3735
4218
|
}
|
|
3736
|
-
function cacheKeyFor(definition, options) {
|
|
3737
|
-
|
|
4219
|
+
function cacheKeyFor(definition, options, values) {
|
|
4220
|
+
const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
4221
|
+
return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
|
|
4222
|
+
}
|
|
4223
|
+
function optionValuesFor(definition, options) {
|
|
4224
|
+
return resolveOptionValues(
|
|
4225
|
+
definition,
|
|
4226
|
+
options.optionValues?.[definition.key],
|
|
4227
|
+
options
|
|
4228
|
+
);
|
|
4229
|
+
}
|
|
4230
|
+
function finalizeWidget(widget, values) {
|
|
4231
|
+
if (Object.keys(values).length === 0) return widget;
|
|
4232
|
+
if (widget.status === "ok") {
|
|
4233
|
+
const data = applyClientOptions(
|
|
4234
|
+
widget.definition,
|
|
4235
|
+
widget.data,
|
|
4236
|
+
values
|
|
4237
|
+
);
|
|
4238
|
+
return { ...widget, data, options: values };
|
|
4239
|
+
}
|
|
4240
|
+
return { ...widget, options: values };
|
|
3738
4241
|
}
|
|
3739
4242
|
var LoadInterruption = class extends Error {
|
|
3740
4243
|
};
|
|
3741
|
-
async function
|
|
4244
|
+
async function resolveWidgetUnapplied(definition, provider, context, options = {}) {
|
|
4245
|
+
const values = optionValuesFor(definition, options);
|
|
3742
4246
|
const quotedKey = JSON.stringify(definition.key);
|
|
3743
4247
|
if (provider === void 0) {
|
|
3744
4248
|
return failedWidget(
|
|
@@ -3773,7 +4277,10 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
3773
4277
|
payload = await Promise.race([
|
|
3774
4278
|
Promise.resolve().then(() => {
|
|
3775
4279
|
if (controller.signal.aborted) throw new LoadInterruption();
|
|
3776
|
-
return provider(context, definition, {
|
|
4280
|
+
return provider(context, definition, {
|
|
4281
|
+
signal: controller.signal,
|
|
4282
|
+
options: values
|
|
4283
|
+
});
|
|
3777
4284
|
}),
|
|
3778
4285
|
interrupted
|
|
3779
4286
|
]);
|
|
@@ -3797,7 +4304,7 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
3797
4304
|
const storedAt = Date.now();
|
|
3798
4305
|
if (options.cache !== void 0) {
|
|
3799
4306
|
try {
|
|
3800
|
-
await options.cache.set(cacheKeyFor(definition, options), {
|
|
4307
|
+
await options.cache.set(cacheKeyFor(definition, options, values), {
|
|
3801
4308
|
payload: resolved.data,
|
|
3802
4309
|
storedAt
|
|
3803
4310
|
});
|
|
@@ -3814,7 +4321,17 @@ function widgetsFor(definitions, roles) {
|
|
|
3814
4321
|
|
|
3815
4322
|
// src/core/loader.ts
|
|
3816
4323
|
function createWidgetLoader(options) {
|
|
3817
|
-
const { definitions, providers, context, ...
|
|
4324
|
+
const { definitions, providers, context, ...baseOptions } = options;
|
|
4325
|
+
const chosen = new Map(
|
|
4326
|
+
Object.entries(baseOptions.optionValues ?? {}).flatMap(
|
|
4327
|
+
([key, values]) => values === void 0 ? [] : [[key, values]]
|
|
4328
|
+
)
|
|
4329
|
+
);
|
|
4330
|
+
const resolveOptions = baseOptions;
|
|
4331
|
+
function currentOptions() {
|
|
4332
|
+
return { ...resolveOptions, optionValues: Object.fromEntries(chosen) };
|
|
4333
|
+
}
|
|
4334
|
+
const unapplied = /* @__PURE__ */ new Map();
|
|
3818
4335
|
const visible = widgetsFor(definitions, context.roles);
|
|
3819
4336
|
let snapshot = loadingWidgets(visible);
|
|
3820
4337
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -3855,19 +4372,19 @@ function createWidgetLoader(options) {
|
|
|
3855
4372
|
const controller = new AbortController();
|
|
3856
4373
|
inFlight.set(key, controller);
|
|
3857
4374
|
const isCurrent = () => !disposed && generations.get(key) === generation;
|
|
3858
|
-
|
|
4375
|
+
const runOptions = currentOptions();
|
|
4376
|
+
const values = optionValuesFor(definition, runOptions);
|
|
4377
|
+
if (readCache && runOptions.cache !== void 0) {
|
|
3859
4378
|
try {
|
|
3860
|
-
const entry = await
|
|
3861
|
-
cacheKeyFor(definition,
|
|
4379
|
+
const entry = await runOptions.cache.get(
|
|
4380
|
+
cacheKeyFor(definition, runOptions, values)
|
|
3862
4381
|
);
|
|
3863
4382
|
if (entry !== void 0 && isCurrent()) {
|
|
3864
|
-
const cached = resolvePayload(
|
|
3865
|
-
definition,
|
|
3866
|
-
entry.payload,
|
|
3867
|
-
resolveOptions
|
|
3868
|
-
);
|
|
4383
|
+
const cached = resolvePayload(definition, entry.payload, runOptions);
|
|
3869
4384
|
if (cached.status === "ok") {
|
|
3870
|
-
|
|
4385
|
+
const stale = { ...cached, updatedAt: entry.storedAt, stale: true };
|
|
4386
|
+
unapplied.set(key, stale);
|
|
4387
|
+
replace(key, finalizeWidget(stale, values));
|
|
3871
4388
|
}
|
|
3872
4389
|
}
|
|
3873
4390
|
} catch {
|
|
@@ -3875,13 +4392,15 @@ function createWidgetLoader(options) {
|
|
|
3875
4392
|
}
|
|
3876
4393
|
if (!isCurrent()) return;
|
|
3877
4394
|
const provider = Object.prototype.hasOwnProperty.call(providers, key) ? providers[key] : void 0;
|
|
3878
|
-
const result = await
|
|
3879
|
-
...
|
|
4395
|
+
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
4396
|
+
...runOptions,
|
|
3880
4397
|
signal: controller.signal
|
|
3881
4398
|
});
|
|
3882
4399
|
if (!isCurrent()) return;
|
|
3883
4400
|
inFlight.delete(key);
|
|
3884
|
-
|
|
4401
|
+
if (result.status === "ok") unapplied.set(key, result);
|
|
4402
|
+
else unapplied.delete(key);
|
|
4403
|
+
replace(key, finalizeWidget(result, values));
|
|
3885
4404
|
}
|
|
3886
4405
|
function start(definition, readCache) {
|
|
3887
4406
|
started.add(definition.key);
|
|
@@ -3916,6 +4435,29 @@ function createWidgetLoader(options) {
|
|
|
3916
4435
|
start(definition, false);
|
|
3917
4436
|
}
|
|
3918
4437
|
}
|
|
4438
|
+
function setOptions(key, values) {
|
|
4439
|
+
if (disposed) return;
|
|
4440
|
+
const definition = definitionFor(key);
|
|
4441
|
+
if (definition === void 0) return;
|
|
4442
|
+
const before = optionValuesFor(definition, currentOptions());
|
|
4443
|
+
chosen.set(key, { ...values });
|
|
4444
|
+
const runOptions = currentOptions();
|
|
4445
|
+
const after = optionValuesFor(definition, runOptions);
|
|
4446
|
+
if (sameOptionValues(before, after) || !started.has(key)) return;
|
|
4447
|
+
const kept = unapplied.get(key);
|
|
4448
|
+
if (kept !== void 0 && optionsCacheSuffix(definition, before, runOptions) === optionsCacheSuffix(definition, after, runOptions)) {
|
|
4449
|
+
replace(key, finalizeWidget(kept, after));
|
|
4450
|
+
return;
|
|
4451
|
+
}
|
|
4452
|
+
const current = snapshot.find(
|
|
4453
|
+
(widget) => widget.definition.key === key
|
|
4454
|
+
);
|
|
4455
|
+
if (current?.status === "ok") replace(key, { ...current, stale: true });
|
|
4456
|
+
else if (current?.status === "error") {
|
|
4457
|
+
replace(key, { definition, status: "loading" });
|
|
4458
|
+
}
|
|
4459
|
+
start(definition, true);
|
|
4460
|
+
}
|
|
3919
4461
|
function dispose() {
|
|
3920
4462
|
disposed = true;
|
|
3921
4463
|
abortAll(new Error("The widget loader was disposed."));
|
|
@@ -3933,6 +4475,7 @@ function createWidgetLoader(options) {
|
|
|
3933
4475
|
load,
|
|
3934
4476
|
loadAll,
|
|
3935
4477
|
refresh,
|
|
4478
|
+
setOptions,
|
|
3936
4479
|
dispose
|
|
3937
4480
|
};
|
|
3938
4481
|
}
|
|
@@ -3947,7 +4490,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3947
4490
|
timeoutMs,
|
|
3948
4491
|
signal,
|
|
3949
4492
|
cache,
|
|
3950
|
-
cacheKey
|
|
4493
|
+
cacheKey,
|
|
4494
|
+
optionValues
|
|
3951
4495
|
} = options;
|
|
3952
4496
|
const placeholders = useMemo(
|
|
3953
4497
|
() => loadingWidgets(widgetsFor(definitions, context.roles)),
|
|
@@ -3957,6 +4501,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3957
4501
|
const [loader, setLoader] = useState(null);
|
|
3958
4502
|
const loaderRef = useRef(null);
|
|
3959
4503
|
const gridRef = useRef(null);
|
|
4504
|
+
const optionValuesRef = useRef(optionValues);
|
|
4505
|
+
optionValuesRef.current = optionValues;
|
|
3960
4506
|
useEffect(() => {
|
|
3961
4507
|
const created = createWidgetLoader({
|
|
3962
4508
|
definitions,
|
|
@@ -3966,7 +4512,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3966
4512
|
...timeoutMs === void 0 ? {} : { timeoutMs },
|
|
3967
4513
|
...signal === void 0 ? {} : { signal },
|
|
3968
4514
|
...cache === void 0 ? {} : { cache },
|
|
3969
|
-
...cacheKey === void 0 ? {} : { cacheKey }
|
|
4515
|
+
...cacheKey === void 0 ? {} : { cacheKey },
|
|
4516
|
+
...optionValuesRef.current === void 0 ? {} : { optionValues: optionValuesRef.current }
|
|
3970
4517
|
});
|
|
3971
4518
|
loaderRef.current = created;
|
|
3972
4519
|
setWidgets(created.getSnapshot());
|
|
@@ -4038,12 +4585,25 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
4038
4585
|
}, refreshMs);
|
|
4039
4586
|
return () => clearInterval(timer);
|
|
4040
4587
|
}, [loader, refreshMs]);
|
|
4588
|
+
const optionSignature = JSON.stringify(optionValues ?? {});
|
|
4589
|
+
useEffect(() => {
|
|
4590
|
+
if (loader === null) return;
|
|
4591
|
+
for (const [key, chosen] of Object.entries(optionValuesRef.current ?? {})) {
|
|
4592
|
+
if (chosen !== void 0) loader.setOptions(key, chosen);
|
|
4593
|
+
}
|
|
4594
|
+
}, [loader, optionSignature]);
|
|
4595
|
+
const setOptions = useCallback(
|
|
4596
|
+
(key, chosen) => {
|
|
4597
|
+
loaderRef.current?.setOptions(key, chosen);
|
|
4598
|
+
},
|
|
4599
|
+
[]
|
|
4600
|
+
);
|
|
4041
4601
|
const refresh = useCallback((key) => {
|
|
4042
4602
|
loaderRef.current?.refresh(key);
|
|
4043
4603
|
}, []);
|
|
4044
|
-
return { widgets, refresh, gridRef };
|
|
4604
|
+
return { widgets, refresh, setOptions, gridRef };
|
|
4045
4605
|
}
|
|
4046
4606
|
|
|
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 };
|
|
4607
|
+
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
4608
|
//# sourceMappingURL=react.js.map
|
|
4049
4609
|
//# sourceMappingURL=react.js.map
|