@richardmcquiston01/dashboard-widgets-toolkit 0.9.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 +1827 -31
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +13 -48
- package/dist/core.d.ts +13 -48
- package/dist/core.js +1773 -32
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1827 -31
- 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 +1773 -32
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1601 -196
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +113 -6
- package/dist/react.d.ts +113 -6
- package/dist/react.js +1602 -198
- package/dist/react.js.map +1 -1
- package/dist/storage-VaaysVnZ.d.cts +1333 -0
- package/dist/storage-VaaysVnZ.d.ts +1333 -0
- package/dist/styles.css +138 -0
- package/package.json +1 -1
- package/dist/scale-CrXxawQn.d.cts +0 -723
- package/dist/scale-CrXxawQn.d.ts +0 -723
package/dist/react.cjs
CHANGED
|
@@ -429,21 +429,36 @@ function fillColumnSpans(items, columns, largeSpan = 2) {
|
|
|
429
429
|
function fillWidthSpans(items) {
|
|
430
430
|
return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
|
|
431
431
|
}
|
|
432
|
+
function placeRows(items) {
|
|
433
|
+
return placeAndFillRows(items, WIDTH_COLUMNS, itemWidth).rows;
|
|
434
|
+
}
|
|
432
435
|
function placeAndFill(items, columns, spanOf) {
|
|
436
|
+
return placeAndFillRows(items, columns, spanOf).spans;
|
|
437
|
+
}
|
|
438
|
+
function placeAndFillRows(items, columns, spanOf) {
|
|
433
439
|
const trackCount = Math.max(1, Math.floor(columns));
|
|
434
440
|
const spans = /* @__PURE__ */ new Map();
|
|
441
|
+
const rows = [];
|
|
435
442
|
let row = [];
|
|
436
443
|
let used = 0;
|
|
437
444
|
function closeRow() {
|
|
445
|
+
if (row.length === 0) return;
|
|
438
446
|
const fillers = row.filter(({ item }) => fillsWidth(item.fill));
|
|
439
447
|
const leftover = trackCount - used;
|
|
448
|
+
const finalSpans = /* @__PURE__ */ new Map();
|
|
440
449
|
if (fillers.length > 0) {
|
|
441
450
|
const share = Math.floor(leftover / fillers.length);
|
|
442
451
|
const remainder = leftover % fillers.length;
|
|
443
452
|
fillers.forEach(({ item, span }, index) => {
|
|
444
|
-
|
|
453
|
+
const grown = span + share + (index < remainder ? 1 : 0);
|
|
454
|
+
spans.set(item.key, grown);
|
|
455
|
+
finalSpans.set(item.key, grown);
|
|
445
456
|
});
|
|
446
457
|
}
|
|
458
|
+
rows.push({
|
|
459
|
+
keys: row.map(({ item }) => item.key),
|
|
460
|
+
spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
|
|
461
|
+
});
|
|
447
462
|
row = [];
|
|
448
463
|
used = 0;
|
|
449
464
|
}
|
|
@@ -454,7 +469,266 @@ function placeAndFill(items, columns, spanOf) {
|
|
|
454
469
|
used += span;
|
|
455
470
|
}
|
|
456
471
|
closeRow();
|
|
457
|
-
return spans;
|
|
472
|
+
return { spans, rows };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/core/options.ts
|
|
476
|
+
var DATE_RANGE_PRESETS = [
|
|
477
|
+
"today",
|
|
478
|
+
"yesterday",
|
|
479
|
+
"last7",
|
|
480
|
+
"last30",
|
|
481
|
+
"last90",
|
|
482
|
+
"thisMonth",
|
|
483
|
+
"lastMonth",
|
|
484
|
+
"thisYear",
|
|
485
|
+
"lastYear"
|
|
486
|
+
];
|
|
487
|
+
var COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
488
|
+
var INTERVAL_PATTERN = /^(\d{4}-\d{2}-\d{2})\/(\d{4}-\d{2}-\d{2})$/;
|
|
489
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
490
|
+
function isClientApplied(option) {
|
|
491
|
+
return (option.type === "sort" || option.type === "columns") && option.apply === "client";
|
|
492
|
+
}
|
|
493
|
+
function civilToday(context) {
|
|
494
|
+
const now = context.now ?? Date.now();
|
|
495
|
+
let timeZone = context.timeZone ?? "UTC";
|
|
496
|
+
try {
|
|
497
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
498
|
+
} catch {
|
|
499
|
+
timeZone = "UTC";
|
|
500
|
+
}
|
|
501
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
502
|
+
timeZone,
|
|
503
|
+
year: "numeric",
|
|
504
|
+
month: "2-digit",
|
|
505
|
+
day: "2-digit"
|
|
506
|
+
}).formatToParts(new Date(now));
|
|
507
|
+
const pick = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
|
|
508
|
+
return { year: pick("year"), month: pick("month"), day: pick("day") };
|
|
509
|
+
}
|
|
510
|
+
function toIso(epochMs) {
|
|
511
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
512
|
+
}
|
|
513
|
+
var DAY_MS = 864e5;
|
|
514
|
+
function parseIsoDay(text) {
|
|
515
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
516
|
+
if (match === null) return void 0;
|
|
517
|
+
const epoch = Date.UTC(
|
|
518
|
+
Number(match[1]),
|
|
519
|
+
Number(match[2]) - 1,
|
|
520
|
+
Number(match[3])
|
|
521
|
+
);
|
|
522
|
+
return toIso(epoch) === text ? epoch : void 0;
|
|
523
|
+
}
|
|
524
|
+
function isIsoInterval(value) {
|
|
525
|
+
const match = INTERVAL_PATTERN.exec(value);
|
|
526
|
+
if (match === null) return false;
|
|
527
|
+
const start = parseIsoDay(match[1]);
|
|
528
|
+
const end = parseIsoDay(match[2]);
|
|
529
|
+
return start !== void 0 && end !== void 0 && start <= end;
|
|
530
|
+
}
|
|
531
|
+
function isPreset(value) {
|
|
532
|
+
return DATE_RANGE_PRESETS.includes(value);
|
|
533
|
+
}
|
|
534
|
+
function resolveDateRange(value, context = {}) {
|
|
535
|
+
if (isIsoInterval(value)) return value;
|
|
536
|
+
if (!isPreset(value)) return void 0;
|
|
537
|
+
const today = civilToday(context);
|
|
538
|
+
const todayMs = Date.UTC(today.year, today.month - 1, today.day);
|
|
539
|
+
const range = (startMs, endMs) => `${toIso(startMs)}/${toIso(endMs)}`;
|
|
540
|
+
switch (value) {
|
|
541
|
+
case "today":
|
|
542
|
+
return range(todayMs, todayMs);
|
|
543
|
+
case "yesterday":
|
|
544
|
+
return range(todayMs - DAY_MS, todayMs - DAY_MS);
|
|
545
|
+
case "last7":
|
|
546
|
+
return range(todayMs - 6 * DAY_MS, todayMs);
|
|
547
|
+
case "last30":
|
|
548
|
+
return range(todayMs - 29 * DAY_MS, todayMs);
|
|
549
|
+
case "last90":
|
|
550
|
+
return range(todayMs - 89 * DAY_MS, todayMs);
|
|
551
|
+
case "thisMonth":
|
|
552
|
+
return range(Date.UTC(today.year, today.month - 1, 1), todayMs);
|
|
553
|
+
case "lastMonth":
|
|
554
|
+
return range(
|
|
555
|
+
Date.UTC(today.year, today.month - 2, 1),
|
|
556
|
+
Date.UTC(today.year, today.month - 1, 1) - DAY_MS
|
|
557
|
+
);
|
|
558
|
+
case "thisYear":
|
|
559
|
+
return range(Date.UTC(today.year, 0, 1), todayMs);
|
|
560
|
+
case "lastYear":
|
|
561
|
+
return range(
|
|
562
|
+
Date.UTC(today.year - 1, 0, 1),
|
|
563
|
+
Date.UTC(today.year, 0, 1) - DAY_MS
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
function onStepGrid(option, value) {
|
|
568
|
+
if (option.step === void 0) return true;
|
|
569
|
+
const steps = (value - option.min) / option.step;
|
|
570
|
+
return Math.abs(steps - Math.round(steps)) < 1e-9;
|
|
571
|
+
}
|
|
572
|
+
function parseSortValue(value) {
|
|
573
|
+
const separator = value.lastIndexOf(":");
|
|
574
|
+
if (separator <= 0) return void 0;
|
|
575
|
+
const column = value.slice(0, separator);
|
|
576
|
+
const direction = value.slice(separator + 1);
|
|
577
|
+
if (direction !== "asc" && direction !== "desc") return void 0;
|
|
578
|
+
return { column, direction };
|
|
579
|
+
}
|
|
580
|
+
function sortFromOptions(definition, values) {
|
|
581
|
+
if (values === void 0) return void 0;
|
|
582
|
+
const sortOption = definition.options?.find(
|
|
583
|
+
(option) => option.type === "sort"
|
|
584
|
+
);
|
|
585
|
+
if (sortOption === void 0) return void 0;
|
|
586
|
+
const chosen = values[sortOption.key];
|
|
587
|
+
return typeof chosen === "string" ? parseSortValue(chosen) : void 0;
|
|
588
|
+
}
|
|
589
|
+
function coerceOptionValue(option, value, context = {}) {
|
|
590
|
+
switch (option.type) {
|
|
591
|
+
case "choice":
|
|
592
|
+
return typeof value === "string" && option.choices.some((choice) => choice.value === value) ? value : void 0;
|
|
593
|
+
case "number":
|
|
594
|
+
return typeof value === "number" && Number.isFinite(value) && value >= option.min && value <= option.max && onStepGrid(option, value) ? value : void 0;
|
|
595
|
+
case "boolean":
|
|
596
|
+
return typeof value === "boolean" ? value : void 0;
|
|
597
|
+
case "text": {
|
|
598
|
+
if (typeof value !== "string") return void 0;
|
|
599
|
+
const trimmed = value.trim();
|
|
600
|
+
return trimmed.length <= option.maxLength && !CONTROL_CHARACTERS.test(trimmed) ? trimmed : void 0;
|
|
601
|
+
}
|
|
602
|
+
case "dateRange": {
|
|
603
|
+
if (typeof value !== "string") return void 0;
|
|
604
|
+
if (isPreset(value) && option.presets !== void 0 && !option.presets.some((preset) => preset.value === value)) {
|
|
605
|
+
return void 0;
|
|
606
|
+
}
|
|
607
|
+
return resolveDateRange(value, context);
|
|
608
|
+
}
|
|
609
|
+
case "color":
|
|
610
|
+
return typeof value === "string" && COLOR_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
611
|
+
case "sort": {
|
|
612
|
+
if (typeof value !== "string") return void 0;
|
|
613
|
+
const sort = parseSortValue(value);
|
|
614
|
+
return sort !== void 0 && option.columns.some((column) => column.key === sort.column) ? value : void 0;
|
|
615
|
+
}
|
|
616
|
+
case "columns": {
|
|
617
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
618
|
+
const keys = value;
|
|
619
|
+
const allowed = new Set(option.columns.map((c) => c.key));
|
|
620
|
+
const seen = /* @__PURE__ */ new Set();
|
|
621
|
+
for (const key of keys) {
|
|
622
|
+
if (typeof key !== "string" || !allowed.has(key) || seen.has(key)) {
|
|
623
|
+
return void 0;
|
|
624
|
+
}
|
|
625
|
+
seen.add(key);
|
|
626
|
+
}
|
|
627
|
+
return [...seen];
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function defaultOptionValue(option, context = {}) {
|
|
632
|
+
switch (option.type) {
|
|
633
|
+
case "dateRange":
|
|
634
|
+
return resolveDateRange(option.default, context);
|
|
635
|
+
case "sort":
|
|
636
|
+
return option.default;
|
|
637
|
+
case "columns":
|
|
638
|
+
return [...option.default];
|
|
639
|
+
default:
|
|
640
|
+
return option.default;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
var NO_VALUES = Object.freeze({});
|
|
644
|
+
function resolveOptionValues(definition, chosen, context = {}) {
|
|
645
|
+
const options = definition.options;
|
|
646
|
+
if (options === void 0 || options.length === 0) return NO_VALUES;
|
|
647
|
+
const values = {};
|
|
648
|
+
for (const option of options) {
|
|
649
|
+
const picked = chosen?.[option.key];
|
|
650
|
+
const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked, context)) ?? defaultOptionValue(option, context);
|
|
651
|
+
if (value !== void 0) values[option.key] = value;
|
|
652
|
+
}
|
|
653
|
+
return values;
|
|
654
|
+
}
|
|
655
|
+
function sameValue(a, b) {
|
|
656
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
657
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
658
|
+
}
|
|
659
|
+
return a === b;
|
|
660
|
+
}
|
|
661
|
+
function sameOptionValues(a, b) {
|
|
662
|
+
const keys = Object.keys({ ...a, ...b });
|
|
663
|
+
return keys.every((key) => sameValue(a[key], b[key]));
|
|
664
|
+
}
|
|
665
|
+
function optionsCacheSuffix(definition, values, context = {}) {
|
|
666
|
+
const parts = [];
|
|
667
|
+
for (const option of definition.options ?? []) {
|
|
668
|
+
if (isClientApplied(option)) continue;
|
|
669
|
+
const value = values[option.key];
|
|
670
|
+
if (sameValue(value, defaultOptionValue(option, context))) continue;
|
|
671
|
+
parts.push(
|
|
672
|
+
`${option.key}=${encodeURIComponent(JSON.stringify(value ?? null))}`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
parts.sort();
|
|
676
|
+
return parts.length === 0 ? "" : `?${parts.join("&")}`;
|
|
677
|
+
}
|
|
678
|
+
function tableColumnIndex(key) {
|
|
679
|
+
const match = /^c(\d+)$/.exec(key);
|
|
680
|
+
return match === null ? void 0 : Number(match[1]);
|
|
681
|
+
}
|
|
682
|
+
function sortBarItems(items, sort) {
|
|
683
|
+
const sign = sort.direction === "desc" ? -1 : 1;
|
|
684
|
+
return items.map((item, index) => ({ item, index })).sort((a, b) => {
|
|
685
|
+
const order = sort.column === "value" ? a.item.value - b.item.value : a.item.label.localeCompare(b.item.label);
|
|
686
|
+
return order * sign || a.index - b.index;
|
|
687
|
+
}).map(({ item }) => item);
|
|
688
|
+
}
|
|
689
|
+
function applyClientOptions(definition, data, values) {
|
|
690
|
+
const options = definition.options ?? [];
|
|
691
|
+
const sortOption = options.find(
|
|
692
|
+
(o) => o.type === "sort" && o.apply === "client"
|
|
693
|
+
);
|
|
694
|
+
const columnsOption = options.find(
|
|
695
|
+
(o) => o.type === "columns" && o.apply === "client"
|
|
696
|
+
);
|
|
697
|
+
const sortValue = sortOption === void 0 ? void 0 : values[sortOption.key];
|
|
698
|
+
const columnsValue = columnsOption === void 0 ? void 0 : values[columnsOption.key];
|
|
699
|
+
let next = data;
|
|
700
|
+
if (typeof sortValue === "string") {
|
|
701
|
+
const sort = parseSortValue(sortValue);
|
|
702
|
+
if (sort !== void 0 && next.kind === "BAR_LIST") {
|
|
703
|
+
if (sort.column === "label" || sort.column === "value") {
|
|
704
|
+
next = { ...next, items: sortBarItems(next.items, sort) };
|
|
705
|
+
}
|
|
706
|
+
} else if (sort !== void 0 && next.kind === "TABLE") {
|
|
707
|
+
const index = tableColumnIndex(sort.column);
|
|
708
|
+
if (index !== void 0 && index < next.columns.length) {
|
|
709
|
+
const sorted = queryRows(tableDetailData(next), {
|
|
710
|
+
page: 1,
|
|
711
|
+
pageSize: Math.max(1, next.rows.length),
|
|
712
|
+
sort
|
|
713
|
+
});
|
|
714
|
+
if (sorted.ok) next = { ...next, rows: sorted.value.rows };
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (Array.isArray(columnsValue) && next.kind === "TABLE") {
|
|
719
|
+
const table = next;
|
|
720
|
+
const indexes = columnsValue.map(tableColumnIndex).filter(
|
|
721
|
+
(index) => index !== void 0 && index < table.columns.length
|
|
722
|
+
);
|
|
723
|
+
if (indexes.length > 0) {
|
|
724
|
+
next = {
|
|
725
|
+
...table,
|
|
726
|
+
columns: indexes.map((i) => table.columns[i]),
|
|
727
|
+
rows: table.rows.map((row) => indexes.map((i) => row[i]))
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return next;
|
|
458
732
|
}
|
|
459
733
|
|
|
460
734
|
// src/core/scale.ts
|
|
@@ -537,6 +811,27 @@ var DEFAULT_LABELS = {
|
|
|
537
811
|
dashboardControls: "Dashboard controls",
|
|
538
812
|
locked: (title) => `${title} is locked`,
|
|
539
813
|
lockedForViewers: (title) => `${title} is locked for viewers`,
|
|
814
|
+
pageBar: "Pages",
|
|
815
|
+
pageTabName: (index, count, title) => `Page ${index} of ${count}: ${title}`,
|
|
816
|
+
previousPageButton: "Previous page",
|
|
817
|
+
nextPageButton: "Next page",
|
|
818
|
+
managePages: "Manage pages",
|
|
819
|
+
addPage: "Add page",
|
|
820
|
+
renamePage: (title) => `Rename ${title}`,
|
|
821
|
+
pageTitle: "Page title",
|
|
822
|
+
savePageTitle: "Save page title",
|
|
823
|
+
cancelPageTitle: "Cancel renaming",
|
|
824
|
+
deletePage: (title) => `Delete ${title}`,
|
|
825
|
+
confirmDeletePage: (title) => `Delete the page "${title}" and move its widgets to another page?`,
|
|
826
|
+
movePageLeft: (title) => `Move ${title} page left`,
|
|
827
|
+
movePageRight: (title) => `Move ${title} page right`,
|
|
828
|
+
moveToPage: (title) => `Move ${title} to another page`,
|
|
829
|
+
moveToPagePlaceholder: "Move to page\u2026",
|
|
830
|
+
moveToPageOption: (title, rowsFree) => `${title} (${rowsFree} ${rowsFree === 1 ? "row" : "rows"} free)`,
|
|
831
|
+
newPageOption: "New page\u2026",
|
|
832
|
+
movedToPage: (title, pageTitle) => `${title} moved to ${pageTitle}`,
|
|
833
|
+
pageFull: (pageTitle, maxRows) => `Page "${pageTitle}" is full (${maxRows} rows). Move or hide a widget first.`,
|
|
834
|
+
emptyPage: "This page has no widgets. Move one here from another page.",
|
|
540
835
|
opensInNewTab: "(opens in a new tab)",
|
|
541
836
|
view: (title) => `View ${title}`,
|
|
542
837
|
close: "Close",
|
|
@@ -1202,7 +1497,8 @@ function GaugeWidget({
|
|
|
1202
1497
|
}
|
|
1203
1498
|
function TableWidget({
|
|
1204
1499
|
data,
|
|
1205
|
-
controls
|
|
1500
|
+
controls,
|
|
1501
|
+
initialSort
|
|
1206
1502
|
}) {
|
|
1207
1503
|
if (controls === void 0) {
|
|
1208
1504
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "dwt-table-wrap", children: [
|
|
@@ -1210,7 +1506,14 @@ function TableWidget({
|
|
|
1210
1506
|
data.footer !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "dwt-note", children: data.footer })
|
|
1211
1507
|
] });
|
|
1212
1508
|
}
|
|
1213
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1509
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1510
|
+
InteractiveTable,
|
|
1511
|
+
{
|
|
1512
|
+
data,
|
|
1513
|
+
controls,
|
|
1514
|
+
initialSort
|
|
1515
|
+
}
|
|
1516
|
+
);
|
|
1214
1517
|
}
|
|
1215
1518
|
function TableView({
|
|
1216
1519
|
data,
|
|
@@ -1263,13 +1566,14 @@ function nextTableSort(current, column) {
|
|
|
1263
1566
|
}
|
|
1264
1567
|
function InteractiveTable({
|
|
1265
1568
|
data,
|
|
1266
|
-
controls
|
|
1569
|
+
controls,
|
|
1570
|
+
initialSort
|
|
1267
1571
|
}) {
|
|
1268
1572
|
const slot = useSlotClassName();
|
|
1269
1573
|
const { labels, locale } = useWidgetSettings();
|
|
1270
1574
|
const searchId = react.useId();
|
|
1271
1575
|
const [search, setSearch] = react.useState("");
|
|
1272
|
-
const [sort, setSort] = react.useState(
|
|
1576
|
+
const [sort, setSort] = react.useState(initialSort);
|
|
1273
1577
|
const localeTag = typeof locale === "string" ? locale : locale?.[0];
|
|
1274
1578
|
const query = {
|
|
1275
1579
|
page: 1,
|
|
@@ -1371,7 +1675,8 @@ function AlertListWidget({
|
|
|
1371
1675
|
}
|
|
1372
1676
|
function WidgetContent({
|
|
1373
1677
|
data,
|
|
1374
|
-
tableControls
|
|
1678
|
+
tableControls,
|
|
1679
|
+
initialSort
|
|
1375
1680
|
}) {
|
|
1376
1681
|
switch (data.kind) {
|
|
1377
1682
|
case "TEXT":
|
|
@@ -1381,7 +1686,14 @@ function WidgetContent({
|
|
|
1381
1686
|
case "GAUGE":
|
|
1382
1687
|
return /* @__PURE__ */ jsxRuntime.jsx(GaugeWidget, { data });
|
|
1383
1688
|
case "TABLE":
|
|
1384
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1689
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1690
|
+
TableWidget,
|
|
1691
|
+
{
|
|
1692
|
+
data,
|
|
1693
|
+
controls: tableControls,
|
|
1694
|
+
initialSort
|
|
1695
|
+
}
|
|
1696
|
+
);
|
|
1385
1697
|
case "BAR_LIST":
|
|
1386
1698
|
return /* @__PURE__ */ jsxRuntime.jsx(BarListWidget, { data });
|
|
1387
1699
|
case "ALERT_LIST":
|
|
@@ -1578,7 +1890,8 @@ function ResolvedWidgetCard({
|
|
|
1578
1890
|
WidgetContent,
|
|
1579
1891
|
{
|
|
1580
1892
|
data: widget.data,
|
|
1581
|
-
tableControls: resolveTableControls(definition)
|
|
1893
|
+
tableControls: resolveTableControls(definition),
|
|
1894
|
+
initialSort: sortFromOptions(definition, widget.options)
|
|
1582
1895
|
}
|
|
1583
1896
|
) : null
|
|
1584
1897
|
}
|
|
@@ -1625,6 +1938,9 @@ function isWidgetVisibleTo(definition, roles) {
|
|
|
1625
1938
|
}
|
|
1626
1939
|
|
|
1627
1940
|
// src/core/layout.ts
|
|
1941
|
+
var MAX_PAGE_TITLE_LENGTH = 30;
|
|
1942
|
+
var MAX_PARSED_PAGES = 50;
|
|
1943
|
+
var MAX_PAGE_ROWS = 20;
|
|
1628
1944
|
var EMPTY_LAYOUT = Object.freeze({
|
|
1629
1945
|
order: Object.freeze([]),
|
|
1630
1946
|
hidden: Object.freeze([]),
|
|
@@ -1739,6 +2055,22 @@ function restoreWidget(layout, key) {
|
|
|
1739
2055
|
function toggleMinimized(layout, key) {
|
|
1740
2056
|
return isMinimized(layout, key) ? restoreWidget(layout, key) : minimizeWidget(layout, key);
|
|
1741
2057
|
}
|
|
2058
|
+
function mapLayoutLists(layout, change) {
|
|
2059
|
+
const top = change(layout);
|
|
2060
|
+
const pages = layout.pages?.map((page) => {
|
|
2061
|
+
const next = change(page);
|
|
2062
|
+
return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
|
|
2063
|
+
});
|
|
2064
|
+
const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
|
|
2065
|
+
if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
|
|
2066
|
+
return layout;
|
|
2067
|
+
}
|
|
2068
|
+
return {
|
|
2069
|
+
...layout,
|
|
2070
|
+
...top,
|
|
2071
|
+
...pages === void 0 ? {} : { pages }
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
1742
2074
|
function enforceLocks(definitions, layout, options = {}) {
|
|
1743
2075
|
if (options.overrideLocks === true) return layout;
|
|
1744
2076
|
const noMove = /* @__PURE__ */ new Set();
|
|
@@ -1750,11 +2082,337 @@ function enforceLocks(definitions, layout, options = {}) {
|
|
|
1750
2082
|
if (lock.hide) noHide.add(definition.key);
|
|
1751
2083
|
if (lock.minimize) noMinimize.add(definition.key);
|
|
1752
2084
|
}
|
|
1753
|
-
const
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
2085
|
+
const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
|
|
2086
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
2087
|
+
order: without2(lists.order, noMove),
|
|
2088
|
+
hidden: without2(lists.hidden, noHide),
|
|
2089
|
+
minimized: without2(lists.minimized, noMinimize)
|
|
2090
|
+
}));
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
// src/core/pages.ts
|
|
2094
|
+
var DEFAULT_PAGE_KEY = "default";
|
|
2095
|
+
var DEFAULT_PAGE_ROWS = 4;
|
|
2096
|
+
function rowsAllowed(page, options) {
|
|
2097
|
+
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
2098
|
+
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
2099
|
+
}
|
|
2100
|
+
function hasPages(layout) {
|
|
2101
|
+
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
2102
|
+
}
|
|
2103
|
+
function pageList(layout) {
|
|
2104
|
+
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
2105
|
+
return layout.pages;
|
|
2106
|
+
}
|
|
2107
|
+
return [
|
|
2108
|
+
{
|
|
2109
|
+
key: DEFAULT_PAGE_KEY,
|
|
2110
|
+
title: "Page 1",
|
|
2111
|
+
order: layout.order,
|
|
2112
|
+
hidden: layout.hidden,
|
|
2113
|
+
minimized: layout.minimized
|
|
2114
|
+
}
|
|
2115
|
+
];
|
|
2116
|
+
}
|
|
2117
|
+
function withExplicitPages(layout) {
|
|
2118
|
+
if (hasPages(layout)) return layout;
|
|
2119
|
+
return {
|
|
2120
|
+
...layout,
|
|
2121
|
+
order: [],
|
|
2122
|
+
hidden: [],
|
|
2123
|
+
minimized: [],
|
|
2124
|
+
pages: pageList(layout)
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
function sameKeys(a, b) {
|
|
2128
|
+
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
2129
|
+
}
|
|
2130
|
+
function pageLayout(layout, pageKey) {
|
|
2131
|
+
if (!hasPages(layout))
|
|
2132
|
+
return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
|
|
2133
|
+
const page = layout.pages?.find(
|
|
2134
|
+
(p) => p.key === pageKey
|
|
2135
|
+
);
|
|
2136
|
+
if (page === void 0) return void 0;
|
|
2137
|
+
const { pages, ...rest } = layout;
|
|
2138
|
+
return {
|
|
2139
|
+
...rest,
|
|
2140
|
+
order: page.order,
|
|
2141
|
+
hidden: page.hidden,
|
|
2142
|
+
minimized: page.minimized
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
function withPageLayout(layout, pageKey, next) {
|
|
2146
|
+
const current = pageLayout(layout, pageKey);
|
|
2147
|
+
if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
|
|
2148
|
+
return layout;
|
|
2149
|
+
}
|
|
2150
|
+
if (!hasPages(layout)) {
|
|
2151
|
+
return {
|
|
2152
|
+
...layout,
|
|
2153
|
+
order: next.order,
|
|
2154
|
+
hidden: next.hidden,
|
|
2155
|
+
minimized: next.minimized
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
return {
|
|
2159
|
+
...layout,
|
|
2160
|
+
pages: (layout.pages ?? []).map(
|
|
2161
|
+
(page) => page.key === pageKey ? {
|
|
2162
|
+
...page,
|
|
2163
|
+
order: next.order,
|
|
2164
|
+
hidden: next.hidden,
|
|
2165
|
+
minimized: next.minimized
|
|
2166
|
+
} : page
|
|
2167
|
+
)
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
function normalizedTitle(title) {
|
|
2171
|
+
return title.trim().normalize("NFKC").toLowerCase();
|
|
2172
|
+
}
|
|
2173
|
+
function resolvePageKey(layout, reference) {
|
|
2174
|
+
if (reference === void 0 || reference === "") return void 0;
|
|
2175
|
+
const pages = pageList(layout);
|
|
2176
|
+
const byKey = pages.find((p) => p.key === reference);
|
|
2177
|
+
if (byKey !== void 0) return byKey.key;
|
|
2178
|
+
const wanted = normalizedTitle(reference);
|
|
2179
|
+
return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
|
|
2180
|
+
}
|
|
2181
|
+
function homePageKey(layout, item) {
|
|
2182
|
+
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
2183
|
+
}
|
|
2184
|
+
function assignPages(definitions, layout) {
|
|
2185
|
+
const pages = pageList(layout);
|
|
2186
|
+
const assigned = new Map(pages.map((p) => [p.key, []]));
|
|
2187
|
+
for (const definition of definitions) {
|
|
2188
|
+
let pageKey;
|
|
2189
|
+
if (!resolveWidgetLock(definition).move) {
|
|
2190
|
+
pageKey = pages.find(
|
|
2191
|
+
(p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
|
|
2192
|
+
)?.key;
|
|
2193
|
+
}
|
|
2194
|
+
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
2195
|
+
}
|
|
2196
|
+
return assigned;
|
|
2197
|
+
}
|
|
2198
|
+
function pageOf(definitions, layout, key) {
|
|
2199
|
+
for (const [pageKey, widgets] of assignPages(definitions, layout)) {
|
|
2200
|
+
if (widgets.some((w) => w.key === key)) return pageKey;
|
|
2201
|
+
}
|
|
2202
|
+
return void 0;
|
|
2203
|
+
}
|
|
2204
|
+
function pageWidgets(definitions, layout, pageKey) {
|
|
2205
|
+
const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
|
|
2206
|
+
const view = pageLayout(layout, pageKey);
|
|
2207
|
+
return view === void 0 ? [] : visibleWidgets(onPage, view);
|
|
2208
|
+
}
|
|
2209
|
+
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
2210
|
+
const page = pageList(layout).find(
|
|
2211
|
+
(p) => p.key === pageKey
|
|
2212
|
+
);
|
|
2213
|
+
if (page === void 0) return void 0;
|
|
2214
|
+
const maxRows = rowsAllowed(page, options);
|
|
2215
|
+
const rowsUsed = placeRows(
|
|
2216
|
+
pageWidgets(definitions, layout, pageKey)
|
|
2217
|
+
).length;
|
|
2218
|
+
return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
|
|
2219
|
+
}
|
|
2220
|
+
function describePage(page) {
|
|
2221
|
+
return `Page "${page.title}"`;
|
|
2222
|
+
}
|
|
2223
|
+
function titleOf(item) {
|
|
2224
|
+
return item.title ?? item.key;
|
|
2225
|
+
}
|
|
2226
|
+
function checkTitle(layout, title, exceptKey) {
|
|
2227
|
+
const trimmed = title.trim();
|
|
2228
|
+
if (trimmed === "") return err("A page title must not be empty.");
|
|
2229
|
+
if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
|
|
2230
|
+
return err(
|
|
2231
|
+
`Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
|
|
2232
|
+
);
|
|
2233
|
+
}
|
|
2234
|
+
const wanted = normalizedTitle(trimmed);
|
|
2235
|
+
const clash = pageList(layout).find(
|
|
2236
|
+
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
2237
|
+
);
|
|
2238
|
+
if (clash !== void 0) {
|
|
2239
|
+
return err(`Another page is already called "${clash.title}".`);
|
|
2240
|
+
}
|
|
2241
|
+
return ok(trimmed);
|
|
2242
|
+
}
|
|
2243
|
+
function nextPageKey(layout) {
|
|
2244
|
+
const keys = new Set(pageList(layout).map((p) => p.key));
|
|
2245
|
+
let n = 1;
|
|
2246
|
+
while (keys.has(`page-${n}`)) n += 1;
|
|
2247
|
+
return `page-${n}`;
|
|
2248
|
+
}
|
|
2249
|
+
function nextPageTitle(layout) {
|
|
2250
|
+
const used = new Set(
|
|
2251
|
+
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
2252
|
+
);
|
|
2253
|
+
let n = pageList(layout).length + 1;
|
|
2254
|
+
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
2255
|
+
return `Page ${n}`;
|
|
2256
|
+
}
|
|
2257
|
+
function addPage(layout, options = {}) {
|
|
2258
|
+
if (pageList(layout).length >= MAX_PARSED_PAGES) {
|
|
2259
|
+
return err(
|
|
2260
|
+
`The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
|
|
2261
|
+
);
|
|
2262
|
+
}
|
|
2263
|
+
const key = options.key ?? nextPageKey(layout);
|
|
2264
|
+
if (pageList(layout).some((p) => p.key === key)) {
|
|
2265
|
+
return err(`A page with the key "${key}" already exists.`);
|
|
2266
|
+
}
|
|
2267
|
+
const title = checkTitle(
|
|
2268
|
+
layout,
|
|
2269
|
+
options.title ?? nextPageTitle(layout)
|
|
2270
|
+
);
|
|
2271
|
+
if (!title.ok) return title;
|
|
2272
|
+
const base = withExplicitPages(layout);
|
|
2273
|
+
const page = {
|
|
2274
|
+
key,
|
|
2275
|
+
title: title.value,
|
|
2276
|
+
order: [],
|
|
2277
|
+
hidden: [],
|
|
2278
|
+
minimized: []
|
|
2279
|
+
};
|
|
2280
|
+
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
2281
|
+
}
|
|
2282
|
+
function renamePage(layout, pageKey, title) {
|
|
2283
|
+
const page = pageList(layout).find(
|
|
2284
|
+
(p) => p.key === pageKey
|
|
2285
|
+
);
|
|
2286
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2287
|
+
const checked = checkTitle(layout, title, pageKey);
|
|
2288
|
+
if (!checked.ok) return checked;
|
|
2289
|
+
if (checked.value === page.title) return ok(layout);
|
|
2290
|
+
const base = withExplicitPages(layout);
|
|
2291
|
+
return ok({
|
|
2292
|
+
...base,
|
|
2293
|
+
pages: (base.pages ?? []).map(
|
|
2294
|
+
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
2295
|
+
)
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
function movePage(layout, pageKey, offset) {
|
|
2299
|
+
const pages = layout.pages ?? [];
|
|
2300
|
+
const from = pages.findIndex((p) => p.key === pageKey);
|
|
2301
|
+
if (from === -1) return layout;
|
|
2302
|
+
const to = Math.max(
|
|
2303
|
+
0,
|
|
2304
|
+
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
2305
|
+
);
|
|
2306
|
+
if (to === from) return layout;
|
|
2307
|
+
const next = [...pages];
|
|
2308
|
+
const [moved] = next.splice(from, 1);
|
|
2309
|
+
next.splice(to, 0, moved);
|
|
2310
|
+
return { ...layout, pages: next };
|
|
2311
|
+
}
|
|
2312
|
+
function overflowing(definitions, layout, options) {
|
|
2313
|
+
return pageList(layout).find(
|
|
2314
|
+
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
function removePage(definitions, layout, pageKey, options = {}) {
|
|
2318
|
+
const pages = pageList(layout);
|
|
2319
|
+
const index = pages.findIndex((p) => p.key === pageKey);
|
|
2320
|
+
const page = pages[index];
|
|
2321
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2322
|
+
if (pages.length === 1) {
|
|
2323
|
+
return err(
|
|
2324
|
+
`${describePage(page)} is the only page, so it can't be removed.`
|
|
2325
|
+
);
|
|
2326
|
+
}
|
|
2327
|
+
const target = pages[index > 0 ? index - 1 : 1];
|
|
2328
|
+
const merged = {
|
|
2329
|
+
...layout,
|
|
2330
|
+
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
2331
|
+
(p) => p.key === target.key ? {
|
|
2332
|
+
...p,
|
|
2333
|
+
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
2334
|
+
hidden: [...p.hidden, ...page.hidden],
|
|
2335
|
+
minimized: [...p.minimized, ...page.minimized]
|
|
2336
|
+
} : p
|
|
2337
|
+
)
|
|
2338
|
+
};
|
|
2339
|
+
const full = overflowing(
|
|
2340
|
+
definitions,
|
|
2341
|
+
merged,
|
|
2342
|
+
options
|
|
2343
|
+
);
|
|
2344
|
+
if (full !== void 0) {
|
|
2345
|
+
return err(
|
|
2346
|
+
`${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.`
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
return ok(merged);
|
|
2350
|
+
}
|
|
2351
|
+
function without(keys, key) {
|
|
2352
|
+
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
2353
|
+
}
|
|
2354
|
+
function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
|
|
2355
|
+
const widget = definitions.find((d) => d.key === widgetKey);
|
|
2356
|
+
if (widget === void 0) {
|
|
2357
|
+
return err(
|
|
2358
|
+
`Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
|
|
2359
|
+
);
|
|
2360
|
+
}
|
|
2361
|
+
const pages = pageList(layout);
|
|
2362
|
+
const target = pages.find((p) => p.key === targetKey);
|
|
2363
|
+
if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
|
|
2364
|
+
const sourceKey = pageOf(definitions, layout, widgetKey);
|
|
2365
|
+
if (sourceKey === targetKey) return ok(layout);
|
|
2366
|
+
if (resolveWidgetLock(widget).move) {
|
|
2367
|
+
return err(
|
|
2368
|
+
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
2371
|
+
const base = withExplicitPages(layout);
|
|
2372
|
+
const source = (base.pages ?? []).find(
|
|
2373
|
+
(p) => p.key === sourceKey
|
|
2374
|
+
);
|
|
2375
|
+
const hidden = source.hidden.includes(widgetKey);
|
|
2376
|
+
const minimized = source.minimized.includes(widgetKey);
|
|
2377
|
+
const targetOrder = pageWidgets(
|
|
2378
|
+
definitions,
|
|
2379
|
+
base,
|
|
2380
|
+
targetKey
|
|
2381
|
+
).map((d) => d.key);
|
|
2382
|
+
const moved = {
|
|
2383
|
+
...base,
|
|
2384
|
+
pages: (base.pages ?? []).map((p) => {
|
|
2385
|
+
if (p.key === sourceKey) {
|
|
2386
|
+
return {
|
|
2387
|
+
...p,
|
|
2388
|
+
order: without(p.order, widgetKey),
|
|
2389
|
+
hidden: without(p.hidden, widgetKey),
|
|
2390
|
+
minimized: without(p.minimized, widgetKey)
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
if (p.key === targetKey) {
|
|
2394
|
+
return {
|
|
2395
|
+
...p,
|
|
2396
|
+
order: hidden ? p.order : [...targetOrder, widgetKey],
|
|
2397
|
+
hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
|
|
2398
|
+
minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
return p;
|
|
2402
|
+
})
|
|
2403
|
+
};
|
|
2404
|
+
if (!hidden) {
|
|
2405
|
+
const allowed = rowsAllowed(target, options);
|
|
2406
|
+
const needed = placeRows(
|
|
2407
|
+
pageWidgets(definitions, moved, targetKey)
|
|
2408
|
+
).length;
|
|
2409
|
+
if (needed > allowed) {
|
|
2410
|
+
return err(
|
|
2411
|
+
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return ok(moved);
|
|
1758
2416
|
}
|
|
1759
2417
|
function nextSort(current, column) {
|
|
1760
2418
|
if (current?.column !== column) return { column, direction: "asc" };
|
|
@@ -1995,7 +2653,8 @@ function WidgetDetailDialog({
|
|
|
1995
2653
|
}
|
|
1996
2654
|
);
|
|
1997
2655
|
}
|
|
1998
|
-
|
|
2656
|
+
var NO_OPTION_VALUES = Object.freeze({});
|
|
2657
|
+
function useDetailData(definition, load, enabled, fallback, optionValues = NO_OPTION_VALUES) {
|
|
1999
2658
|
const [state, setState] = react.useState({ status: "loading" });
|
|
2000
2659
|
const [attempt, setAttempt] = react.useState(0);
|
|
2001
2660
|
react.useEffect(() => {
|
|
@@ -2005,6 +2664,7 @@ function useDetailData(definition, load, enabled, fallback) {
|
|
|
2005
2664
|
Promise.resolve().then(
|
|
2006
2665
|
() => load === void 0 ? void 0 : load(definition, {
|
|
2007
2666
|
signal: controller.signal,
|
|
2667
|
+
options: optionValues,
|
|
2008
2668
|
query: defaultDetailQuery()
|
|
2009
2669
|
})
|
|
2010
2670
|
).then((raw) => {
|
|
@@ -2032,9 +2692,28 @@ function useDetailData(definition, load, enabled, fallback) {
|
|
|
2032
2692
|
});
|
|
2033
2693
|
});
|
|
2034
2694
|
return () => controller.abort();
|
|
2035
|
-
}, [enabled, load, fallback, definition, attempt]);
|
|
2695
|
+
}, [enabled, load, fallback, definition, optionValues, attempt]);
|
|
2036
2696
|
return { state, reload: () => setAttempt((count) => count + 1) };
|
|
2037
2697
|
}
|
|
2698
|
+
var NEW_PAGE_VALUE = "__new-page__";
|
|
2699
|
+
function PagePanel({
|
|
2700
|
+
paged,
|
|
2701
|
+
id,
|
|
2702
|
+
labelledBy,
|
|
2703
|
+
children
|
|
2704
|
+
}) {
|
|
2705
|
+
if (!paged) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
|
|
2706
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2707
|
+
"div",
|
|
2708
|
+
{
|
|
2709
|
+
role: "tabpanel",
|
|
2710
|
+
id,
|
|
2711
|
+
"aria-labelledby": labelledBy,
|
|
2712
|
+
className: "dwt-page-panel",
|
|
2713
|
+
children
|
|
2714
|
+
}
|
|
2715
|
+
);
|
|
2716
|
+
}
|
|
2038
2717
|
var NARROW_VIEWPORT_QUERY = "(max-width: 640px)";
|
|
2039
2718
|
var WIDE_GRID_PX = 900;
|
|
2040
2719
|
function useFillSpans(definitions, gridRef, twelve) {
|
|
@@ -2129,7 +2808,14 @@ function DetailHost({
|
|
|
2129
2808
|
const { definition } = widget;
|
|
2130
2809
|
const options = resolveDetailOptions(definition);
|
|
2131
2810
|
const pageSize = options?.pageSize ?? 25;
|
|
2132
|
-
const
|
|
2811
|
+
const optionSort = sortFromOptions(
|
|
2812
|
+
definition,
|
|
2813
|
+
widget.options
|
|
2814
|
+
);
|
|
2815
|
+
const [query, setQuery] = react.useState({
|
|
2816
|
+
...defaultDetailQuery(pageSize),
|
|
2817
|
+
...optionSort === void 0 ? {} : { sort: optionSort }
|
|
2818
|
+
});
|
|
2133
2819
|
const derived = react.useMemo(
|
|
2134
2820
|
() => widget.status === "ok" ? deriveDetailData(widget.data, locale) : void 0,
|
|
2135
2821
|
[widget, locale]
|
|
@@ -2138,7 +2824,8 @@ function DetailHost({
|
|
|
2138
2824
|
definition,
|
|
2139
2825
|
loadDetail,
|
|
2140
2826
|
true,
|
|
2141
|
-
derived
|
|
2827
|
+
derived,
|
|
2828
|
+
widget.options
|
|
2142
2829
|
);
|
|
2143
2830
|
const title = options?.title ?? definition.title;
|
|
2144
2831
|
return /* @__PURE__ */ jsxRuntime.jsx(WidgetDetailDialog, { title, open: true, onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2205,6 +2892,181 @@ function InlineConfirm({
|
|
|
2205
2892
|
}
|
|
2206
2893
|
);
|
|
2207
2894
|
}
|
|
2895
|
+
function PageTabs({
|
|
2896
|
+
pages,
|
|
2897
|
+
activeKey,
|
|
2898
|
+
idPrefix,
|
|
2899
|
+
panelId,
|
|
2900
|
+
announcement,
|
|
2901
|
+
onSelect
|
|
2902
|
+
}) {
|
|
2903
|
+
const slot = useSlotClassName();
|
|
2904
|
+
const { labels } = useWidgetSettings();
|
|
2905
|
+
const tabs = react.useRef(/* @__PURE__ */ new Map());
|
|
2906
|
+
const activeIndex = pages.findIndex((p) => p.key === activeKey);
|
|
2907
|
+
function onKeyDown(event, index) {
|
|
2908
|
+
const last = pages.length - 1;
|
|
2909
|
+
let next;
|
|
2910
|
+
switch (event.key) {
|
|
2911
|
+
case "ArrowRight":
|
|
2912
|
+
case "ArrowDown":
|
|
2913
|
+
next = index === last ? 0 : index + 1;
|
|
2914
|
+
break;
|
|
2915
|
+
case "ArrowLeft":
|
|
2916
|
+
case "ArrowUp":
|
|
2917
|
+
next = index === 0 ? last : index - 1;
|
|
2918
|
+
break;
|
|
2919
|
+
case "Home":
|
|
2920
|
+
next = 0;
|
|
2921
|
+
break;
|
|
2922
|
+
case "End":
|
|
2923
|
+
next = last;
|
|
2924
|
+
break;
|
|
2925
|
+
default:
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
event.preventDefault();
|
|
2929
|
+
const target = pages[next];
|
|
2930
|
+
if (target === void 0) return;
|
|
2931
|
+
onSelect(target.key);
|
|
2932
|
+
tabs.current.get(target.key)?.focus();
|
|
2933
|
+
}
|
|
2934
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: slot("pageBar", "dwt-pages"), children: [
|
|
2935
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2936
|
+
"button",
|
|
2937
|
+
{
|
|
2938
|
+
type: "button",
|
|
2939
|
+
className: slot(
|
|
2940
|
+
"button",
|
|
2941
|
+
"dwt-button",
|
|
2942
|
+
"dwt-icon-button",
|
|
2943
|
+
"dwt-page-nav"
|
|
2944
|
+
),
|
|
2945
|
+
"aria-label": labels.previousPageButton,
|
|
2946
|
+
disabled: activeIndex <= 0,
|
|
2947
|
+
onClick: () => onSelect(pages[activeIndex - 1].key),
|
|
2948
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2039" })
|
|
2949
|
+
}
|
|
2950
|
+
),
|
|
2951
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { role: "tablist", "aria-label": labels.pageBar, className: "dwt-page-tabs", children: pages.map((page, index) => {
|
|
2952
|
+
const selected = page.key === activeKey;
|
|
2953
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2954
|
+
"button",
|
|
2955
|
+
{
|
|
2956
|
+
ref: (element) => {
|
|
2957
|
+
if (element === null) tabs.current.delete(page.key);
|
|
2958
|
+
else tabs.current.set(page.key, element);
|
|
2959
|
+
},
|
|
2960
|
+
type: "button",
|
|
2961
|
+
role: "tab",
|
|
2962
|
+
id: `${idPrefix}-tab-${index}`,
|
|
2963
|
+
"aria-selected": selected,
|
|
2964
|
+
"aria-controls": panelId,
|
|
2965
|
+
"aria-label": labels.pageTabName(
|
|
2966
|
+
index + 1,
|
|
2967
|
+
pages.length,
|
|
2968
|
+
page.title
|
|
2969
|
+
),
|
|
2970
|
+
title: page.title,
|
|
2971
|
+
tabIndex: selected ? 0 : -1,
|
|
2972
|
+
className: slot("button", "dwt-page-tab"),
|
|
2973
|
+
onClick: () => onSelect(page.key),
|
|
2974
|
+
onKeyDown: (event) => onKeyDown(event, index),
|
|
2975
|
+
children: [
|
|
2976
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-page-tab-title", children: page.title }),
|
|
2977
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-page-dot", "aria-hidden": "true" })
|
|
2978
|
+
]
|
|
2979
|
+
},
|
|
2980
|
+
page.key
|
|
2981
|
+
);
|
|
2982
|
+
}) }),
|
|
2983
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2984
|
+
"button",
|
|
2985
|
+
{
|
|
2986
|
+
type: "button",
|
|
2987
|
+
className: slot(
|
|
2988
|
+
"button",
|
|
2989
|
+
"dwt-button",
|
|
2990
|
+
"dwt-icon-button",
|
|
2991
|
+
"dwt-page-nav"
|
|
2992
|
+
),
|
|
2993
|
+
"aria-label": labels.nextPageButton,
|
|
2994
|
+
disabled: activeIndex === pages.length - 1,
|
|
2995
|
+
onClick: () => onSelect(pages[activeIndex + 1].key),
|
|
2996
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u203A" })
|
|
2997
|
+
}
|
|
2998
|
+
),
|
|
2999
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-visually-hidden", "aria-live": "polite", children: announcement })
|
|
3000
|
+
] });
|
|
3001
|
+
}
|
|
3002
|
+
function PageRenameForm({
|
|
3003
|
+
initial,
|
|
3004
|
+
onSubmit,
|
|
3005
|
+
onCancel
|
|
3006
|
+
}) {
|
|
3007
|
+
const slot = useSlotClassName();
|
|
3008
|
+
const { labels } = useWidgetSettings();
|
|
3009
|
+
const [title, setTitle] = react.useState(initial);
|
|
3010
|
+
const [error, setError] = react.useState(void 0);
|
|
3011
|
+
const inputRef = react.useRef(null);
|
|
3012
|
+
react.useEffect(() => {
|
|
3013
|
+
inputRef.current?.focus();
|
|
3014
|
+
inputRef.current?.select();
|
|
3015
|
+
}, []);
|
|
3016
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3017
|
+
"form",
|
|
3018
|
+
{
|
|
3019
|
+
className: "dwt-page-rename",
|
|
3020
|
+
onSubmit: (event) => {
|
|
3021
|
+
event.preventDefault();
|
|
3022
|
+
setError(onSubmit(title));
|
|
3023
|
+
},
|
|
3024
|
+
onKeyDown: (event) => {
|
|
3025
|
+
if (event.key === "Escape") {
|
|
3026
|
+
event.stopPropagation();
|
|
3027
|
+
onCancel();
|
|
3028
|
+
}
|
|
3029
|
+
},
|
|
3030
|
+
children: [
|
|
3031
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3032
|
+
"input",
|
|
3033
|
+
{
|
|
3034
|
+
ref: inputRef,
|
|
3035
|
+
type: "text",
|
|
3036
|
+
className: "dwt-page-title-input",
|
|
3037
|
+
"aria-label": labels.pageTitle,
|
|
3038
|
+
"aria-invalid": error !== void 0,
|
|
3039
|
+
maxLength: MAX_PAGE_TITLE_LENGTH,
|
|
3040
|
+
value: title,
|
|
3041
|
+
onChange: (event) => setTitle(event.target.value)
|
|
3042
|
+
}
|
|
3043
|
+
),
|
|
3044
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3045
|
+
"button",
|
|
3046
|
+
{
|
|
3047
|
+
type: "submit",
|
|
3048
|
+
className: slot("button", "dwt-button", "dwt-icon-button"),
|
|
3049
|
+
"aria-label": labels.savePageTitle,
|
|
3050
|
+
title: labels.savePageTitle,
|
|
3051
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2713" })
|
|
3052
|
+
}
|
|
3053
|
+
),
|
|
3054
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3055
|
+
"button",
|
|
3056
|
+
{
|
|
3057
|
+
type: "button",
|
|
3058
|
+
className: slot("button", "dwt-button", "dwt-icon-button"),
|
|
3059
|
+
"aria-label": labels.cancelPageTitle,
|
|
3060
|
+
title: labels.cancelPageTitle,
|
|
3061
|
+
onClick: onCancel,
|
|
3062
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2715" })
|
|
3063
|
+
}
|
|
3064
|
+
),
|
|
3065
|
+
error !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "dwt-page-error", children: error })
|
|
3066
|
+
]
|
|
3067
|
+
}
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
2208
3070
|
function withoutLock(definition) {
|
|
2209
3071
|
if (definition.locked === void 0) return definition;
|
|
2210
3072
|
const copy = { ...definition };
|
|
@@ -2222,6 +3084,11 @@ function DashboardInner({
|
|
|
2222
3084
|
onEditingChange,
|
|
2223
3085
|
defaultLayout,
|
|
2224
3086
|
toolbar = true,
|
|
3087
|
+
activePage: activePageProp,
|
|
3088
|
+
defaultActivePage,
|
|
3089
|
+
onActivePageChange,
|
|
3090
|
+
maxRows,
|
|
3091
|
+
maxPages,
|
|
2225
3092
|
onRetry,
|
|
2226
3093
|
loadDetail,
|
|
2227
3094
|
onOpenDetail,
|
|
@@ -2237,8 +3104,19 @@ function DashboardInner({
|
|
|
2237
3104
|
const byKey = new Map(
|
|
2238
3105
|
widgets.map((w) => [w.definition.key, w])
|
|
2239
3106
|
);
|
|
2240
|
-
const
|
|
2241
|
-
const
|
|
3107
|
+
const pages = pageList(layout);
|
|
3108
|
+
const paged = pages.length >= 2;
|
|
3109
|
+
const [activeState, setActiveState] = react.useState(
|
|
3110
|
+
defaultActivePage
|
|
3111
|
+
);
|
|
3112
|
+
const requested = activePageProp ?? activeState;
|
|
3113
|
+
const activePage = pages.find((page) => page.key === requested) ?? pages[0];
|
|
3114
|
+
const activeKey = activePage.key;
|
|
3115
|
+
const pageDefs = assignPages(definitions, layout).get(activeKey) ?? [];
|
|
3116
|
+
const view = pageLayout(layout, activeKey) ?? layout;
|
|
3117
|
+
const roomOptions = maxRows === void 0 ? {} : { maxRows };
|
|
3118
|
+
const visible = visibleWidgets(pageDefs, view);
|
|
3119
|
+
const hidden = hiddenWidgets(pageDefs, view);
|
|
2242
3120
|
const editable = onLayoutChange !== void 0;
|
|
2243
3121
|
const toggleMode = editMode === "toggle";
|
|
2244
3122
|
const [editingState, setEditingState] = react.useState(defaultEditing);
|
|
@@ -2257,10 +3135,16 @@ function DashboardInner({
|
|
|
2257
3135
|
);
|
|
2258
3136
|
const pending = editing ? confirming : void 0;
|
|
2259
3137
|
const [announcement, setAnnouncement] = react.useState("");
|
|
3138
|
+
const [pageAnnouncement, setPageAnnouncement] = react.useState("");
|
|
3139
|
+
const [pageError, setPageError] = react.useState(void 0);
|
|
3140
|
+
const [pageEditor, setPageEditor] = react.useState(void 0);
|
|
3141
|
+
const editorMode = pageEditor?.key === activeKey ? pageEditor.mode : void 0;
|
|
3142
|
+
const instanceId = react.useId();
|
|
3143
|
+
const panelId = `${instanceId}-panel`;
|
|
2260
3144
|
const toggleRef = react.useRef(null);
|
|
2261
3145
|
const resetRef = react.useRef(null);
|
|
2262
|
-
const minimized = editable ? visible.filter((definition) => isMinimized(
|
|
2263
|
-
const shown = editable ? visible.filter((definition) => !isMinimized(
|
|
3146
|
+
const minimized = editable ? visible.filter((definition) => isMinimized(view, definition.key)) : [];
|
|
3147
|
+
const shown = editable ? visible.filter((definition) => !isMinimized(view, definition.key)) : visible;
|
|
2264
3148
|
const [detailKey, setDetailKey] = react.useState(void 0);
|
|
2265
3149
|
const gridRef = react.useRef(null);
|
|
2266
3150
|
const twelve = usesWidthColumns(shown);
|
|
@@ -2272,6 +3156,110 @@ function DashboardInner({
|
|
|
2272
3156
|
function change(next) {
|
|
2273
3157
|
if (next !== layout) onLayoutChange?.(next);
|
|
2274
3158
|
}
|
|
3159
|
+
function changePage(next) {
|
|
3160
|
+
setPageError(void 0);
|
|
3161
|
+
change(withPageLayout(layout, activeKey, next));
|
|
3162
|
+
}
|
|
3163
|
+
function selectPage(pageKey) {
|
|
3164
|
+
const index = pages.findIndex((page) => page.key === pageKey);
|
|
3165
|
+
if (index === -1) return;
|
|
3166
|
+
if (activePageProp === void 0) setActiveState(pageKey);
|
|
3167
|
+
onActivePageChange?.(pageKey);
|
|
3168
|
+
setPageError(void 0);
|
|
3169
|
+
setPageEditor(void 0);
|
|
3170
|
+
setPageAnnouncement(
|
|
3171
|
+
labels.pageTabName(
|
|
3172
|
+
index + 1,
|
|
3173
|
+
pages.length,
|
|
3174
|
+
pages[index].title
|
|
3175
|
+
)
|
|
3176
|
+
);
|
|
3177
|
+
}
|
|
3178
|
+
function addPageAction() {
|
|
3179
|
+
const added = addPage(layout);
|
|
3180
|
+
if (!added.ok) {
|
|
3181
|
+
setPageError(added.error);
|
|
3182
|
+
return;
|
|
3183
|
+
}
|
|
3184
|
+
change(added.value);
|
|
3185
|
+
const created = added.value.pages?.at(-1);
|
|
3186
|
+
if (created !== void 0) {
|
|
3187
|
+
if (activePageProp === void 0) setActiveState(created.key);
|
|
3188
|
+
onActivePageChange?.(created.key);
|
|
3189
|
+
setPageError(void 0);
|
|
3190
|
+
setPageEditor({ mode: "rename", key: created.key });
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
function renamePageAction(title) {
|
|
3194
|
+
const renamed = renamePage(layout, activeKey, title);
|
|
3195
|
+
if (!renamed.ok) return renamed.error;
|
|
3196
|
+
change(renamed.value);
|
|
3197
|
+
setPageEditor(void 0);
|
|
3198
|
+
return void 0;
|
|
3199
|
+
}
|
|
3200
|
+
function deletePageAction() {
|
|
3201
|
+
const index = pages.findIndex((page) => page.key === activeKey);
|
|
3202
|
+
const removed = removePage(definitions, layout, activeKey, roomOptions);
|
|
3203
|
+
setPageEditor(void 0);
|
|
3204
|
+
if (!removed.ok) {
|
|
3205
|
+
setPageError(removed.error);
|
|
3206
|
+
return;
|
|
3207
|
+
}
|
|
3208
|
+
const neighbor = pages[index > 0 ? index - 1 : 1];
|
|
3209
|
+
change(removed.value);
|
|
3210
|
+
if (neighbor !== void 0) selectPage(neighbor.key);
|
|
3211
|
+
}
|
|
3212
|
+
function moveWidgetAction(widgetKey, targetKey) {
|
|
3213
|
+
let target = targetKey;
|
|
3214
|
+
let base = layout;
|
|
3215
|
+
if (targetKey === NEW_PAGE_VALUE) {
|
|
3216
|
+
const added = addPage(layout);
|
|
3217
|
+
if (!added.ok) {
|
|
3218
|
+
setPageError(added.error);
|
|
3219
|
+
return;
|
|
3220
|
+
}
|
|
3221
|
+
base = added.value;
|
|
3222
|
+
target = (added.value.pages?.at(-1)).key;
|
|
3223
|
+
}
|
|
3224
|
+
const moved = moveWidgetToPage(
|
|
3225
|
+
definitions,
|
|
3226
|
+
base,
|
|
3227
|
+
widgetKey,
|
|
3228
|
+
target,
|
|
3229
|
+
roomOptions
|
|
3230
|
+
);
|
|
3231
|
+
if (!moved.ok) {
|
|
3232
|
+
setPageError(moved.error);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
setPageError(void 0);
|
|
3236
|
+
change(moved.value);
|
|
3237
|
+
const destination = moved.value.pages?.find(
|
|
3238
|
+
(page) => page.key === target
|
|
3239
|
+
);
|
|
3240
|
+
setPageAnnouncement(
|
|
3241
|
+
labels.movedToPage(
|
|
3242
|
+
byKey.get(widgetKey)?.definition.title ?? widgetKey,
|
|
3243
|
+
destination?.title ?? target
|
|
3244
|
+
)
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
function showAction(widgetKey) {
|
|
3248
|
+
const next = withPageLayout(
|
|
3249
|
+
layout,
|
|
3250
|
+
activeKey,
|
|
3251
|
+
showWidget(view, widgetKey)
|
|
3252
|
+
);
|
|
3253
|
+
if (hasPages(next)) {
|
|
3254
|
+
const room = pageRoom(definitions, next, activeKey, roomOptions);
|
|
3255
|
+
if (room !== void 0 && room.rowsUsed > room.maxRows) {
|
|
3256
|
+
setPageError(labels.pageFull(activePage.title, room.maxRows));
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
setPageError(void 0);
|
|
3261
|
+
change(next);
|
|
3262
|
+
}
|
|
2275
3263
|
function setEditing(next) {
|
|
2276
3264
|
if (editingProp === void 0) setEditingState(next);
|
|
2277
3265
|
onEditingChange?.(next);
|
|
@@ -2349,176 +3337,312 @@ function DashboardInner({
|
|
|
2349
3337
|
]
|
|
2350
3338
|
}
|
|
2351
3339
|
),
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
]
|
|
2365
|
-
},
|
|
2366
|
-
definition.key
|
|
2367
|
-
))
|
|
2368
|
-
] }),
|
|
2369
|
-
minimized.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3340
|
+
paged && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3341
|
+
PageTabs,
|
|
3342
|
+
{
|
|
3343
|
+
pages,
|
|
3344
|
+
activeKey,
|
|
3345
|
+
idPrefix: instanceId,
|
|
3346
|
+
panelId,
|
|
3347
|
+
announcement: pageAnnouncement,
|
|
3348
|
+
onSelect: selectPage
|
|
3349
|
+
}
|
|
3350
|
+
),
|
|
3351
|
+
controlsShown && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2370
3352
|
"div",
|
|
2371
3353
|
{
|
|
2372
|
-
|
|
3354
|
+
role: "group",
|
|
3355
|
+
"aria-label": labels.managePages,
|
|
3356
|
+
className: "dwt-page-manage",
|
|
2373
3357
|
children: [
|
|
2374
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2375
|
-
minimized.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3358
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2376
3359
|
"button",
|
|
2377
3360
|
{
|
|
2378
3361
|
type: "button",
|
|
2379
|
-
className: slot("button", "dwt-button"
|
|
2380
|
-
|
|
2381
|
-
onClick:
|
|
2382
|
-
children:
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
3362
|
+
className: slot("button", "dwt-button"),
|
|
3363
|
+
disabled: maxPages !== void 0 && pages.length >= maxPages,
|
|
3364
|
+
onClick: addPageAction,
|
|
3365
|
+
children: labels.addPage
|
|
3366
|
+
}
|
|
3367
|
+
),
|
|
3368
|
+
paged && editorMode === "rename" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3369
|
+
PageRenameForm,
|
|
3370
|
+
{
|
|
3371
|
+
initial: activePage.title,
|
|
3372
|
+
onSubmit: renamePageAction,
|
|
3373
|
+
onCancel: () => setPageEditor(void 0)
|
|
2386
3374
|
},
|
|
2387
|
-
|
|
2388
|
-
)
|
|
3375
|
+
activeKey
|
|
3376
|
+
),
|
|
3377
|
+
paged && editorMode !== "rename" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3378
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3379
|
+
"button",
|
|
3380
|
+
{
|
|
3381
|
+
type: "button",
|
|
3382
|
+
className: slot("button", "dwt-button", "dwt-icon-button"),
|
|
3383
|
+
"aria-label": labels.renamePage(activePage.title),
|
|
3384
|
+
title: labels.renamePage(activePage.title),
|
|
3385
|
+
onClick: () => setPageEditor({ mode: "rename", key: activeKey }),
|
|
3386
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u270E" })
|
|
3387
|
+
}
|
|
3388
|
+
),
|
|
3389
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3390
|
+
"button",
|
|
3391
|
+
{
|
|
3392
|
+
type: "button",
|
|
3393
|
+
className: slot("button", "dwt-button", "dwt-icon-button"),
|
|
3394
|
+
"aria-label": labels.movePageLeft(activePage.title),
|
|
3395
|
+
title: labels.movePageLeft(activePage.title),
|
|
3396
|
+
disabled: pages[0]?.key === activeKey,
|
|
3397
|
+
onClick: () => change(movePage(layout, activeKey, -1)),
|
|
3398
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2190" })
|
|
3399
|
+
}
|
|
3400
|
+
),
|
|
3401
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3402
|
+
"button",
|
|
3403
|
+
{
|
|
3404
|
+
type: "button",
|
|
3405
|
+
className: slot("button", "dwt-button", "dwt-icon-button"),
|
|
3406
|
+
"aria-label": labels.movePageRight(activePage.title),
|
|
3407
|
+
title: labels.movePageRight(activePage.title),
|
|
3408
|
+
disabled: pages.at(-1)?.key === activeKey,
|
|
3409
|
+
onClick: () => change(movePage(layout, activeKey, 1)),
|
|
3410
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2192" })
|
|
3411
|
+
}
|
|
3412
|
+
),
|
|
3413
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3414
|
+
"button",
|
|
3415
|
+
{
|
|
3416
|
+
type: "button",
|
|
3417
|
+
className: slot("button", "dwt-button", "dwt-icon-button"),
|
|
3418
|
+
"aria-label": labels.deletePage(activePage.title),
|
|
3419
|
+
title: labels.deletePage(activePage.title),
|
|
3420
|
+
onClick: () => setPageEditor({ mode: "delete", key: activeKey }),
|
|
3421
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
|
|
3422
|
+
}
|
|
3423
|
+
)
|
|
3424
|
+
] }),
|
|
3425
|
+
paged && editorMode === "delete" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3426
|
+
InlineConfirm,
|
|
3427
|
+
{
|
|
3428
|
+
message: labels.confirmDeletePage(activePage.title),
|
|
3429
|
+
onAnswer: (yes) => yes ? deletePageAction() : setPageEditor(void 0)
|
|
3430
|
+
}
|
|
3431
|
+
),
|
|
3432
|
+
pageError !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "alert", className: "dwt-page-error", children: pageError })
|
|
2389
3433
|
]
|
|
2390
3434
|
}
|
|
2391
3435
|
),
|
|
2392
|
-
|
|
2393
|
-
|
|
3436
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3437
|
+
PagePanel,
|
|
2394
3438
|
{
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
const title = definition.title;
|
|
2404
|
-
const lock = resolveWidgetLock(definition);
|
|
2405
|
-
const ownLock = resolveWidgetLock(
|
|
2406
|
-
widget.definition
|
|
2407
|
-
);
|
|
2408
|
-
const isLocked = ownLock.move || ownLock.hide || ownLock.minimize;
|
|
2409
|
-
const lockLabel = overrideLocks ? labels.lockedForViewers(title) : labels.locked(title);
|
|
2410
|
-
const canMoveEarlier = !lock.move && moveWidgetBy(definitions, layout, key, -1) !== layout;
|
|
2411
|
-
const canMoveLater = !lock.move && moveWidgetBy(definitions, layout, key, 1) !== layout;
|
|
2412
|
-
const actions = controlsShown ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2413
|
-
isLocked && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2414
|
-
"span",
|
|
3439
|
+
paged,
|
|
3440
|
+
id: panelId,
|
|
3441
|
+
labelledBy: `${instanceId}-tab-${pages.findIndex((p) => p.key === activeKey)}`,
|
|
3442
|
+
children: [
|
|
3443
|
+
controlsShown && hidden.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: slot("hiddenBar", "dwt-hidden-bar"), children: [
|
|
3444
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-hidden-label", children: labels.hiddenWidgets }),
|
|
3445
|
+
hidden.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3446
|
+
"button",
|
|
2415
3447
|
{
|
|
2416
|
-
|
|
2417
|
-
className: "dwt-
|
|
2418
|
-
"aria-label":
|
|
2419
|
-
|
|
2420
|
-
children:
|
|
2421
|
-
"
|
|
3448
|
+
type: "button",
|
|
3449
|
+
className: slot("button", "dwt-button", "dwt-show-button"),
|
|
3450
|
+
"aria-label": labels.show(definition.title),
|
|
3451
|
+
onClick: () => showAction(definition.key),
|
|
3452
|
+
children: [
|
|
3453
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "+ " }),
|
|
3454
|
+
definition.title
|
|
3455
|
+
]
|
|
3456
|
+
},
|
|
3457
|
+
definition.key
|
|
3458
|
+
))
|
|
3459
|
+
] }),
|
|
3460
|
+
minimized.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3461
|
+
"div",
|
|
3462
|
+
{
|
|
3463
|
+
className: slot("hiddenBar", "dwt-hidden-bar", "dwt-minimized-bar"),
|
|
3464
|
+
children: [
|
|
3465
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "dwt-hidden-label", children: labels.minimizedWidgets }),
|
|
3466
|
+
minimized.map((definition) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3467
|
+
"button",
|
|
2422
3468
|
{
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
focusable: "false",
|
|
3469
|
+
type: "button",
|
|
3470
|
+
className: slot("button", "dwt-button", "dwt-show-button"),
|
|
3471
|
+
"aria-label": labels.expand(definition.title),
|
|
3472
|
+
onClick: () => changePage(toggleMinimized(view, definition.key)),
|
|
2428
3473
|
children: [
|
|
2429
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
3474
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u25B8 " }),
|
|
3475
|
+
definition.title
|
|
3476
|
+
]
|
|
3477
|
+
},
|
|
3478
|
+
definition.key
|
|
3479
|
+
))
|
|
3480
|
+
]
|
|
3481
|
+
}
|
|
3482
|
+
),
|
|
3483
|
+
visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: slot("empty", "dwt-empty", "dwt-dashboard-empty"), children: paged && pageDefs.length === 0 ? labels.emptyPage : definitions.length === 0 ? labels.noWidgets : labels.allHidden }) : shown.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
3484
|
+
"div",
|
|
3485
|
+
{
|
|
3486
|
+
ref: gridRef,
|
|
3487
|
+
className: slot("grid", "dwt-grid", twelve && "dwt-grid--twelve"),
|
|
3488
|
+
children: shown.map((definition, index) => {
|
|
3489
|
+
const widget = byKey.get(
|
|
3490
|
+
definition.key
|
|
3491
|
+
);
|
|
3492
|
+
if (widget === void 0) return null;
|
|
3493
|
+
const key = definition.key;
|
|
3494
|
+
const title = definition.title;
|
|
3495
|
+
const lock = resolveWidgetLock(definition);
|
|
3496
|
+
const ownLock = resolveWidgetLock(
|
|
3497
|
+
widget.definition
|
|
3498
|
+
);
|
|
3499
|
+
const isLocked = ownLock.move || ownLock.hide || ownLock.minimize;
|
|
3500
|
+
const lockLabel = overrideLocks ? labels.lockedForViewers(title) : labels.locked(title);
|
|
3501
|
+
const canMoveEarlier = !lock.move && moveWidgetBy(pageDefs, view, key, -1) !== view;
|
|
3502
|
+
const canMoveLater = !lock.move && moveWidgetBy(pageDefs, view, key, 1) !== view;
|
|
3503
|
+
const actions = controlsShown ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3504
|
+
isLocked && /* @__PURE__ */ jsxRuntime.jsx(
|
|
3505
|
+
"span",
|
|
3506
|
+
{
|
|
3507
|
+
role: "img",
|
|
3508
|
+
className: "dwt-lock",
|
|
3509
|
+
"aria-label": lockLabel,
|
|
3510
|
+
title: lockLabel,
|
|
3511
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3512
|
+
"svg",
|
|
2442
3513
|
{
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
3514
|
+
viewBox: "0 0 16 16",
|
|
3515
|
+
width: "14",
|
|
3516
|
+
height: "14",
|
|
3517
|
+
"aria-hidden": "true",
|
|
3518
|
+
focusable: "false",
|
|
3519
|
+
children: [
|
|
3520
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3521
|
+
"rect",
|
|
3522
|
+
{
|
|
3523
|
+
x: "3",
|
|
3524
|
+
y: "7",
|
|
3525
|
+
width: "10",
|
|
3526
|
+
height: "7",
|
|
3527
|
+
rx: "1.5",
|
|
3528
|
+
fill: "currentColor"
|
|
3529
|
+
}
|
|
3530
|
+
),
|
|
3531
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3532
|
+
"path",
|
|
3533
|
+
{
|
|
3534
|
+
d: "M5.5 7V5a2.5 2.5 0 0 1 5 0v2",
|
|
3535
|
+
fill: "none",
|
|
3536
|
+
stroke: "currentColor",
|
|
3537
|
+
strokeWidth: "1.4"
|
|
3538
|
+
}
|
|
3539
|
+
)
|
|
3540
|
+
]
|
|
2447
3541
|
}
|
|
2448
3542
|
)
|
|
2449
|
-
|
|
2450
|
-
}
|
|
2451
|
-
)
|
|
2452
|
-
}
|
|
2453
|
-
),
|
|
2454
|
-
!lock.move && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2455
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2456
|
-
"button",
|
|
2457
|
-
{
|
|
2458
|
-
type: "button",
|
|
2459
|
-
className: slot(
|
|
2460
|
-
"button",
|
|
2461
|
-
"dwt-button",
|
|
2462
|
-
"dwt-icon-button"
|
|
3543
|
+
}
|
|
2463
3544
|
),
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
3545
|
+
!lock.move && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3546
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3547
|
+
"button",
|
|
3548
|
+
{
|
|
3549
|
+
type: "button",
|
|
3550
|
+
className: slot(
|
|
3551
|
+
"button",
|
|
3552
|
+
"dwt-button",
|
|
3553
|
+
"dwt-icon-button"
|
|
3554
|
+
),
|
|
3555
|
+
"aria-label": labels.moveEarlier(title),
|
|
3556
|
+
title: labels.moveEarlier(title),
|
|
3557
|
+
disabled: index === 0 || !canMoveEarlier,
|
|
3558
|
+
onClick: () => changePage(moveWidgetBy(pageDefs, view, key, -1)),
|
|
3559
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2191" })
|
|
3560
|
+
}
|
|
3561
|
+
),
|
|
3562
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3563
|
+
"button",
|
|
3564
|
+
{
|
|
3565
|
+
type: "button",
|
|
3566
|
+
className: slot(
|
|
3567
|
+
"button",
|
|
3568
|
+
"dwt-button",
|
|
3569
|
+
"dwt-icon-button"
|
|
3570
|
+
),
|
|
3571
|
+
"aria-label": labels.moveLater(title),
|
|
3572
|
+
title: labels.moveLater(title),
|
|
3573
|
+
disabled: index === shown.length - 1 || !canMoveLater,
|
|
3574
|
+
onClick: () => changePage(moveWidgetBy(pageDefs, view, key, 1)),
|
|
3575
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\u2193" })
|
|
3576
|
+
}
|
|
3577
|
+
),
|
|
3578
|
+
paged && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3579
|
+
"select",
|
|
3580
|
+
{
|
|
3581
|
+
className: "dwt-move-select",
|
|
3582
|
+
"aria-label": labels.moveToPage(title),
|
|
3583
|
+
value: "",
|
|
3584
|
+
onChange: (event) => {
|
|
3585
|
+
if (event.target.value !== "") {
|
|
3586
|
+
moveWidgetAction(key, event.target.value);
|
|
3587
|
+
}
|
|
3588
|
+
},
|
|
3589
|
+
children: [
|
|
3590
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: labels.moveToPagePlaceholder }),
|
|
3591
|
+
pages.filter((page) => page.key !== activeKey).map((page) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: page.key, children: labels.moveToPageOption(
|
|
3592
|
+
page.title,
|
|
3593
|
+
pageRoom(
|
|
3594
|
+
definitions,
|
|
3595
|
+
layout,
|
|
3596
|
+
page.key,
|
|
3597
|
+
roomOptions
|
|
3598
|
+
)?.rowsFree ?? 0
|
|
3599
|
+
) }, page.key)),
|
|
3600
|
+
(maxPages === void 0 || pages.length < maxPages) && /* @__PURE__ */ jsxRuntime.jsx("option", { value: NEW_PAGE_VALUE, children: labels.newPageOption })
|
|
3601
|
+
]
|
|
3602
|
+
}
|
|
3603
|
+
)
|
|
3604
|
+
] }),
|
|
3605
|
+
!lock.hide && /* @__PURE__ */ jsxRuntime.jsx(
|
|
2476
3606
|
"button",
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
...headingLevel === void 0 ? {} : { headingLevel },
|
|
2517
|
-
...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
|
|
2518
|
-
},
|
|
2519
|
-
key
|
|
2520
|
-
);
|
|
2521
|
-
})
|
|
3607
|
+
{
|
|
3608
|
+
type: "button",
|
|
3609
|
+
className: slot(
|
|
3610
|
+
"button",
|
|
3611
|
+
"dwt-button",
|
|
3612
|
+
"dwt-icon-button"
|
|
3613
|
+
),
|
|
3614
|
+
"aria-label": labels.hide(title),
|
|
3615
|
+
title: labels.hide(title),
|
|
3616
|
+
onClick: () => changePage(hideWidget(view, key)),
|
|
3617
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
|
|
3618
|
+
}
|
|
3619
|
+
)
|
|
3620
|
+
] }) : void 0;
|
|
3621
|
+
const viewable = resolveDetailOptions(definition) !== void 0 && (onOpenDetail !== void 0 || loadDetail !== void 0 || widget.status === "ok" && deriveDetailData(widget.data) !== void 0);
|
|
3622
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3623
|
+
ResolvedWidgetCard,
|
|
3624
|
+
{
|
|
3625
|
+
widget,
|
|
3626
|
+
...viewable ? {
|
|
3627
|
+
onView: () => onOpenDetail === void 0 ? setDetailKey(key) : onOpenDetail(key)
|
|
3628
|
+
} : {},
|
|
3629
|
+
...spanProps(spans, key),
|
|
3630
|
+
...widthProps(twelve, definition),
|
|
3631
|
+
minimized: isMinimized(view, key),
|
|
3632
|
+
editing: toggleMode && controlsShown,
|
|
3633
|
+
...actions === void 0 ? {} : { actions },
|
|
3634
|
+
...editable && !lock.minimize ? {
|
|
3635
|
+
onToggleMinimized: () => changePage(toggleMinimized(view, key))
|
|
3636
|
+
} : {},
|
|
3637
|
+
...headingLevel === void 0 ? {} : { headingLevel },
|
|
3638
|
+
...onRetry === void 0 || widget.status !== "error" ? {} : { onRetry: () => onRetry(key) }
|
|
3639
|
+
},
|
|
3640
|
+
key
|
|
3641
|
+
);
|
|
3642
|
+
})
|
|
3643
|
+
}
|
|
3644
|
+
)
|
|
3645
|
+
]
|
|
2522
3646
|
}
|
|
2523
3647
|
),
|
|
2524
3648
|
detailKey !== void 0 && byKey.get(detailKey) !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -2555,6 +3679,196 @@ function Dashboard({
|
|
|
2555
3679
|
}
|
|
2556
3680
|
);
|
|
2557
3681
|
}
|
|
3682
|
+
function useStoredLayout(options) {
|
|
3683
|
+
const {
|
|
3684
|
+
persistence,
|
|
3685
|
+
scope,
|
|
3686
|
+
definitions,
|
|
3687
|
+
defaultLayout,
|
|
3688
|
+
saveDelayMs = 500,
|
|
3689
|
+
initialLayout,
|
|
3690
|
+
normalizeOptions
|
|
3691
|
+
} = options;
|
|
3692
|
+
const scopeKey = persistence.keyFor(scope);
|
|
3693
|
+
const [layout, setLayoutState] = react.useState(
|
|
3694
|
+
initialLayout ?? defaultLayout
|
|
3695
|
+
);
|
|
3696
|
+
const [backup, setBackupState] = react.useState(null);
|
|
3697
|
+
const [status, setStatus] = react.useState("loading");
|
|
3698
|
+
const [error, setError] = react.useState(null);
|
|
3699
|
+
const [conflict, setConflict] = react.useState(null);
|
|
3700
|
+
const [remoteLayout, setRemoteLayout] = react.useState(
|
|
3701
|
+
null
|
|
3702
|
+
);
|
|
3703
|
+
const latest = react.useRef(layout);
|
|
3704
|
+
const scopeRef = react.useRef(scope);
|
|
3705
|
+
scopeRef.current = scope;
|
|
3706
|
+
const normalizeRef = react.useRef(normalizeOptions);
|
|
3707
|
+
normalizeRef.current = normalizeOptions;
|
|
3708
|
+
const timer = react.useRef(void 0);
|
|
3709
|
+
const dirty = react.useRef(false);
|
|
3710
|
+
const apply = react.useCallback((next) => {
|
|
3711
|
+
latest.current = next;
|
|
3712
|
+
setLayoutState(next);
|
|
3713
|
+
}, []);
|
|
3714
|
+
const finishSave = react.useCallback(
|
|
3715
|
+
(result, mine) => {
|
|
3716
|
+
if (!result.ok) {
|
|
3717
|
+
setError(result.error);
|
|
3718
|
+
setStatus("error");
|
|
3719
|
+
return;
|
|
3720
|
+
}
|
|
3721
|
+
const saved = result.value;
|
|
3722
|
+
if (saved.outcome === "saved") {
|
|
3723
|
+
dirty.current = latest.current !== mine;
|
|
3724
|
+
setError(null);
|
|
3725
|
+
setStatus(dirty.current ? "saving" : "ready");
|
|
3726
|
+
return;
|
|
3727
|
+
}
|
|
3728
|
+
if (saved.outcome === "kept-theirs") {
|
|
3729
|
+
dirty.current = false;
|
|
3730
|
+
apply(saved.layout);
|
|
3731
|
+
setError(null);
|
|
3732
|
+
setStatus("ready");
|
|
3733
|
+
return;
|
|
3734
|
+
}
|
|
3735
|
+
setConflict({ mine, theirs: saved.layout });
|
|
3736
|
+
setStatus("conflict");
|
|
3737
|
+
},
|
|
3738
|
+
[apply]
|
|
3739
|
+
);
|
|
3740
|
+
const flush = react.useCallback(() => {
|
|
3741
|
+
if (timer.current !== void 0) {
|
|
3742
|
+
clearTimeout(timer.current);
|
|
3743
|
+
timer.current = void 0;
|
|
3744
|
+
}
|
|
3745
|
+
if (!dirty.current) return;
|
|
3746
|
+
const mine = latest.current;
|
|
3747
|
+
setStatus("saving");
|
|
3748
|
+
void persistence.save(scopeRef.current, mine).then((result) => finishSave(result, mine));
|
|
3749
|
+
}, [persistence, finishSave]);
|
|
3750
|
+
react.useEffect(() => {
|
|
3751
|
+
let current = true;
|
|
3752
|
+
setStatus("loading");
|
|
3753
|
+
setConflict(null);
|
|
3754
|
+
setRemoteLayout(null);
|
|
3755
|
+
void persistence.load(scopeRef.current, definitions, normalizeRef.current).then((result) => {
|
|
3756
|
+
if (!current) return;
|
|
3757
|
+
if (!result.ok) {
|
|
3758
|
+
setError(result.error);
|
|
3759
|
+
setStatus("error");
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
setError(null);
|
|
3763
|
+
if (!dirty.current && result.value.layout !== null) {
|
|
3764
|
+
apply(result.value.layout);
|
|
3765
|
+
}
|
|
3766
|
+
setStatus(dirty.current ? "saving" : "ready");
|
|
3767
|
+
});
|
|
3768
|
+
void persistence.loadBackup(scopeRef.current).then((result) => {
|
|
3769
|
+
if (current && result.ok) setBackupState(result.value);
|
|
3770
|
+
});
|
|
3771
|
+
return () => {
|
|
3772
|
+
current = false;
|
|
3773
|
+
};
|
|
3774
|
+
}, [persistence, scopeKey, definitions, apply]);
|
|
3775
|
+
react.useEffect(() => {
|
|
3776
|
+
return persistence.watch(
|
|
3777
|
+
scopeRef.current,
|
|
3778
|
+
definitions,
|
|
3779
|
+
(loaded) => {
|
|
3780
|
+
if (loaded.layout === null) return;
|
|
3781
|
+
if (dirty.current) {
|
|
3782
|
+
setRemoteLayout(loaded.layout);
|
|
3783
|
+
return;
|
|
3784
|
+
}
|
|
3785
|
+
apply(loaded.layout);
|
|
3786
|
+
},
|
|
3787
|
+
normalizeRef.current
|
|
3788
|
+
);
|
|
3789
|
+
}, [persistence, scopeKey, definitions, apply]);
|
|
3790
|
+
react.useEffect(() => {
|
|
3791
|
+
const onHide = () => {
|
|
3792
|
+
if (typeof document === "undefined" || document.visibilityState !== "hidden") {
|
|
3793
|
+
return;
|
|
3794
|
+
}
|
|
3795
|
+
flush();
|
|
3796
|
+
};
|
|
3797
|
+
if (typeof window !== "undefined") {
|
|
3798
|
+
window.addEventListener("pagehide", flush);
|
|
3799
|
+
document.addEventListener("visibilitychange", onHide);
|
|
3800
|
+
}
|
|
3801
|
+
return () => {
|
|
3802
|
+
if (typeof window !== "undefined") {
|
|
3803
|
+
window.removeEventListener("pagehide", flush);
|
|
3804
|
+
document.removeEventListener("visibilitychange", onHide);
|
|
3805
|
+
}
|
|
3806
|
+
flush();
|
|
3807
|
+
};
|
|
3808
|
+
}, [flush]);
|
|
3809
|
+
const setLayout = react.useCallback(
|
|
3810
|
+
(next) => {
|
|
3811
|
+
apply(next);
|
|
3812
|
+
dirty.current = true;
|
|
3813
|
+
if (timer.current !== void 0) clearTimeout(timer.current);
|
|
3814
|
+
timer.current = setTimeout(flush, Math.max(0, saveDelayMs));
|
|
3815
|
+
},
|
|
3816
|
+
[apply, flush, saveDelayMs]
|
|
3817
|
+
);
|
|
3818
|
+
const setBackup = react.useCallback(
|
|
3819
|
+
(next) => {
|
|
3820
|
+
setBackupState(next);
|
|
3821
|
+
if (next === null) return;
|
|
3822
|
+
void persistence.saveBackup(scopeRef.current, next).then((result) => {
|
|
3823
|
+
if (!result.ok) setError(result.error);
|
|
3824
|
+
});
|
|
3825
|
+
},
|
|
3826
|
+
[persistence]
|
|
3827
|
+
);
|
|
3828
|
+
const resolveConflict = react.useCallback(
|
|
3829
|
+
(choice) => {
|
|
3830
|
+
const pending = conflict;
|
|
3831
|
+
if (pending === null) return;
|
|
3832
|
+
setConflict(null);
|
|
3833
|
+
if (choice === "theirs") {
|
|
3834
|
+
dirty.current = false;
|
|
3835
|
+
apply(pending.theirs);
|
|
3836
|
+
setStatus("ready");
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
setStatus("saving");
|
|
3840
|
+
void persistence.save(scopeRef.current, pending.mine, { force: true }).then((result) => finishSave(result, pending.mine));
|
|
3841
|
+
},
|
|
3842
|
+
[conflict, persistence, apply, finishSave]
|
|
3843
|
+
);
|
|
3844
|
+
const resolveRemote = react.useCallback(
|
|
3845
|
+
(choice) => {
|
|
3846
|
+
const remote = remoteLayout;
|
|
3847
|
+
setRemoteLayout(null);
|
|
3848
|
+
if (choice === "use" && remote !== null) {
|
|
3849
|
+
if (timer.current !== void 0) {
|
|
3850
|
+
clearTimeout(timer.current);
|
|
3851
|
+
timer.current = void 0;
|
|
3852
|
+
}
|
|
3853
|
+
dirty.current = false;
|
|
3854
|
+
apply(remote);
|
|
3855
|
+
}
|
|
3856
|
+
},
|
|
3857
|
+
[remoteLayout, apply]
|
|
3858
|
+
);
|
|
3859
|
+
return {
|
|
3860
|
+
layout,
|
|
3861
|
+
setLayout,
|
|
3862
|
+
backup,
|
|
3863
|
+
setBackup,
|
|
3864
|
+
status,
|
|
3865
|
+
error,
|
|
3866
|
+
conflict,
|
|
3867
|
+
resolveConflict,
|
|
3868
|
+
remoteLayout,
|
|
3869
|
+
resolveRemote
|
|
3870
|
+
};
|
|
3871
|
+
}
|
|
2558
3872
|
|
|
2559
3873
|
// src/core/payload.ts
|
|
2560
3874
|
var WIDGET_KINDS = [
|
|
@@ -2898,12 +4212,33 @@ function failedWidget(definition, error) {
|
|
|
2898
4212
|
function loadingWidgets(definitions) {
|
|
2899
4213
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
2900
4214
|
}
|
|
2901
|
-
function cacheKeyFor(definition, options) {
|
|
2902
|
-
|
|
4215
|
+
function cacheKeyFor(definition, options, values) {
|
|
4216
|
+
const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
4217
|
+
return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
|
|
4218
|
+
}
|
|
4219
|
+
function optionValuesFor(definition, options) {
|
|
4220
|
+
return resolveOptionValues(
|
|
4221
|
+
definition,
|
|
4222
|
+
options.optionValues?.[definition.key],
|
|
4223
|
+
options
|
|
4224
|
+
);
|
|
4225
|
+
}
|
|
4226
|
+
function finalizeWidget(widget, values) {
|
|
4227
|
+
if (Object.keys(values).length === 0) return widget;
|
|
4228
|
+
if (widget.status === "ok") {
|
|
4229
|
+
const data = applyClientOptions(
|
|
4230
|
+
widget.definition,
|
|
4231
|
+
widget.data,
|
|
4232
|
+
values
|
|
4233
|
+
);
|
|
4234
|
+
return { ...widget, data, options: values };
|
|
4235
|
+
}
|
|
4236
|
+
return { ...widget, options: values };
|
|
2903
4237
|
}
|
|
2904
4238
|
var LoadInterruption = class extends Error {
|
|
2905
4239
|
};
|
|
2906
|
-
async function
|
|
4240
|
+
async function resolveWidgetUnapplied(definition, provider, context, options = {}) {
|
|
4241
|
+
const values = optionValuesFor(definition, options);
|
|
2907
4242
|
const quotedKey = JSON.stringify(definition.key);
|
|
2908
4243
|
if (provider === void 0) {
|
|
2909
4244
|
return failedWidget(
|
|
@@ -2938,7 +4273,10 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
2938
4273
|
payload = await Promise.race([
|
|
2939
4274
|
Promise.resolve().then(() => {
|
|
2940
4275
|
if (controller.signal.aborted) throw new LoadInterruption();
|
|
2941
|
-
return provider(context, definition, {
|
|
4276
|
+
return provider(context, definition, {
|
|
4277
|
+
signal: controller.signal,
|
|
4278
|
+
options: values
|
|
4279
|
+
});
|
|
2942
4280
|
}),
|
|
2943
4281
|
interrupted
|
|
2944
4282
|
]);
|
|
@@ -2962,7 +4300,7 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
2962
4300
|
const storedAt = Date.now();
|
|
2963
4301
|
if (options.cache !== void 0) {
|
|
2964
4302
|
try {
|
|
2965
|
-
await options.cache.set(cacheKeyFor(definition, options), {
|
|
4303
|
+
await options.cache.set(cacheKeyFor(definition, options, values), {
|
|
2966
4304
|
payload: resolved.data,
|
|
2967
4305
|
storedAt
|
|
2968
4306
|
});
|
|
@@ -2979,7 +4317,17 @@ function widgetsFor(definitions, roles) {
|
|
|
2979
4317
|
|
|
2980
4318
|
// src/core/loader.ts
|
|
2981
4319
|
function createWidgetLoader(options) {
|
|
2982
|
-
const { definitions, providers, context, ...
|
|
4320
|
+
const { definitions, providers, context, ...baseOptions } = options;
|
|
4321
|
+
const chosen = new Map(
|
|
4322
|
+
Object.entries(baseOptions.optionValues ?? {}).flatMap(
|
|
4323
|
+
([key, values]) => values === void 0 ? [] : [[key, values]]
|
|
4324
|
+
)
|
|
4325
|
+
);
|
|
4326
|
+
const resolveOptions = baseOptions;
|
|
4327
|
+
function currentOptions() {
|
|
4328
|
+
return { ...resolveOptions, optionValues: Object.fromEntries(chosen) };
|
|
4329
|
+
}
|
|
4330
|
+
const unapplied = /* @__PURE__ */ new Map();
|
|
2983
4331
|
const visible = widgetsFor(definitions, context.roles);
|
|
2984
4332
|
let snapshot = loadingWidgets(visible);
|
|
2985
4333
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -3020,19 +4368,19 @@ function createWidgetLoader(options) {
|
|
|
3020
4368
|
const controller = new AbortController();
|
|
3021
4369
|
inFlight.set(key, controller);
|
|
3022
4370
|
const isCurrent = () => !disposed && generations.get(key) === generation;
|
|
3023
|
-
|
|
4371
|
+
const runOptions = currentOptions();
|
|
4372
|
+
const values = optionValuesFor(definition, runOptions);
|
|
4373
|
+
if (readCache && runOptions.cache !== void 0) {
|
|
3024
4374
|
try {
|
|
3025
|
-
const entry = await
|
|
3026
|
-
cacheKeyFor(definition,
|
|
4375
|
+
const entry = await runOptions.cache.get(
|
|
4376
|
+
cacheKeyFor(definition, runOptions, values)
|
|
3027
4377
|
);
|
|
3028
4378
|
if (entry !== void 0 && isCurrent()) {
|
|
3029
|
-
const cached = resolvePayload(
|
|
3030
|
-
definition,
|
|
3031
|
-
entry.payload,
|
|
3032
|
-
resolveOptions
|
|
3033
|
-
);
|
|
4379
|
+
const cached = resolvePayload(definition, entry.payload, runOptions);
|
|
3034
4380
|
if (cached.status === "ok") {
|
|
3035
|
-
|
|
4381
|
+
const stale = { ...cached, updatedAt: entry.storedAt, stale: true };
|
|
4382
|
+
unapplied.set(key, stale);
|
|
4383
|
+
replace(key, finalizeWidget(stale, values));
|
|
3036
4384
|
}
|
|
3037
4385
|
}
|
|
3038
4386
|
} catch {
|
|
@@ -3040,13 +4388,15 @@ function createWidgetLoader(options) {
|
|
|
3040
4388
|
}
|
|
3041
4389
|
if (!isCurrent()) return;
|
|
3042
4390
|
const provider = Object.prototype.hasOwnProperty.call(providers, key) ? providers[key] : void 0;
|
|
3043
|
-
const result = await
|
|
3044
|
-
...
|
|
4391
|
+
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
4392
|
+
...runOptions,
|
|
3045
4393
|
signal: controller.signal
|
|
3046
4394
|
});
|
|
3047
4395
|
if (!isCurrent()) return;
|
|
3048
4396
|
inFlight.delete(key);
|
|
3049
|
-
|
|
4397
|
+
if (result.status === "ok") unapplied.set(key, result);
|
|
4398
|
+
else unapplied.delete(key);
|
|
4399
|
+
replace(key, finalizeWidget(result, values));
|
|
3050
4400
|
}
|
|
3051
4401
|
function start(definition, readCache) {
|
|
3052
4402
|
started.add(definition.key);
|
|
@@ -3081,6 +4431,29 @@ function createWidgetLoader(options) {
|
|
|
3081
4431
|
start(definition, false);
|
|
3082
4432
|
}
|
|
3083
4433
|
}
|
|
4434
|
+
function setOptions(key, values) {
|
|
4435
|
+
if (disposed) return;
|
|
4436
|
+
const definition = definitionFor(key);
|
|
4437
|
+
if (definition === void 0) return;
|
|
4438
|
+
const before = optionValuesFor(definition, currentOptions());
|
|
4439
|
+
chosen.set(key, { ...values });
|
|
4440
|
+
const runOptions = currentOptions();
|
|
4441
|
+
const after = optionValuesFor(definition, runOptions);
|
|
4442
|
+
if (sameOptionValues(before, after) || !started.has(key)) return;
|
|
4443
|
+
const kept = unapplied.get(key);
|
|
4444
|
+
if (kept !== void 0 && optionsCacheSuffix(definition, before, runOptions) === optionsCacheSuffix(definition, after, runOptions)) {
|
|
4445
|
+
replace(key, finalizeWidget(kept, after));
|
|
4446
|
+
return;
|
|
4447
|
+
}
|
|
4448
|
+
const current = snapshot.find(
|
|
4449
|
+
(widget) => widget.definition.key === key
|
|
4450
|
+
);
|
|
4451
|
+
if (current?.status === "ok") replace(key, { ...current, stale: true });
|
|
4452
|
+
else if (current?.status === "error") {
|
|
4453
|
+
replace(key, { definition, status: "loading" });
|
|
4454
|
+
}
|
|
4455
|
+
start(definition, true);
|
|
4456
|
+
}
|
|
3084
4457
|
function dispose() {
|
|
3085
4458
|
disposed = true;
|
|
3086
4459
|
abortAll(new Error("The widget loader was disposed."));
|
|
@@ -3098,6 +4471,7 @@ function createWidgetLoader(options) {
|
|
|
3098
4471
|
load,
|
|
3099
4472
|
loadAll,
|
|
3100
4473
|
refresh,
|
|
4474
|
+
setOptions,
|
|
3101
4475
|
dispose
|
|
3102
4476
|
};
|
|
3103
4477
|
}
|
|
@@ -3112,7 +4486,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3112
4486
|
timeoutMs,
|
|
3113
4487
|
signal,
|
|
3114
4488
|
cache,
|
|
3115
|
-
cacheKey
|
|
4489
|
+
cacheKey,
|
|
4490
|
+
optionValues
|
|
3116
4491
|
} = options;
|
|
3117
4492
|
const placeholders = react.useMemo(
|
|
3118
4493
|
() => loadingWidgets(widgetsFor(definitions, context.roles)),
|
|
@@ -3122,6 +4497,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3122
4497
|
const [loader, setLoader] = react.useState(null);
|
|
3123
4498
|
const loaderRef = react.useRef(null);
|
|
3124
4499
|
const gridRef = react.useRef(null);
|
|
4500
|
+
const optionValuesRef = react.useRef(optionValues);
|
|
4501
|
+
optionValuesRef.current = optionValues;
|
|
3125
4502
|
react.useEffect(() => {
|
|
3126
4503
|
const created = createWidgetLoader({
|
|
3127
4504
|
definitions,
|
|
@@ -3131,7 +4508,8 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3131
4508
|
...timeoutMs === void 0 ? {} : { timeoutMs },
|
|
3132
4509
|
...signal === void 0 ? {} : { signal },
|
|
3133
4510
|
...cache === void 0 ? {} : { cache },
|
|
3134
|
-
...cacheKey === void 0 ? {} : { cacheKey }
|
|
4511
|
+
...cacheKey === void 0 ? {} : { cacheKey },
|
|
4512
|
+
...optionValuesRef.current === void 0 ? {} : { optionValues: optionValuesRef.current }
|
|
3135
4513
|
});
|
|
3136
4514
|
loaderRef.current = created;
|
|
3137
4515
|
setWidgets(created.getSnapshot());
|
|
@@ -3177,8 +4555,21 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3177
4555
|
},
|
|
3178
4556
|
{ rootMargin }
|
|
3179
4557
|
);
|
|
3180
|
-
|
|
3181
|
-
|
|
4558
|
+
const observed = /* @__PURE__ */ new WeakSet();
|
|
4559
|
+
const observeNewCards = () => {
|
|
4560
|
+
grid.querySelectorAll("[data-widget-key]").forEach((card) => {
|
|
4561
|
+
if (observed.has(card)) return;
|
|
4562
|
+
observed.add(card);
|
|
4563
|
+
observer.observe(card);
|
|
4564
|
+
});
|
|
4565
|
+
};
|
|
4566
|
+
observeNewCards();
|
|
4567
|
+
const mutations = typeof MutationObserver === "undefined" ? void 0 : new MutationObserver(observeNewCards);
|
|
4568
|
+
mutations?.observe(grid, { childList: true, subtree: true });
|
|
4569
|
+
return () => {
|
|
4570
|
+
mutations?.disconnect();
|
|
4571
|
+
observer.disconnect();
|
|
4572
|
+
};
|
|
3182
4573
|
}, [loader, loadWhen, rootMargin, cardKeys]);
|
|
3183
4574
|
react.useEffect(() => {
|
|
3184
4575
|
if (loader === null || !(refreshMs > 0)) return void 0;
|
|
@@ -3190,10 +4581,23 @@ function useWidgets(definitions, providers, context, options = {}) {
|
|
|
3190
4581
|
}, refreshMs);
|
|
3191
4582
|
return () => clearInterval(timer);
|
|
3192
4583
|
}, [loader, refreshMs]);
|
|
4584
|
+
const optionSignature = JSON.stringify(optionValues ?? {});
|
|
4585
|
+
react.useEffect(() => {
|
|
4586
|
+
if (loader === null) return;
|
|
4587
|
+
for (const [key, chosen] of Object.entries(optionValuesRef.current ?? {})) {
|
|
4588
|
+
if (chosen !== void 0) loader.setOptions(key, chosen);
|
|
4589
|
+
}
|
|
4590
|
+
}, [loader, optionSignature]);
|
|
4591
|
+
const setOptions = react.useCallback(
|
|
4592
|
+
(key, chosen) => {
|
|
4593
|
+
loaderRef.current?.setOptions(key, chosen);
|
|
4594
|
+
},
|
|
4595
|
+
[]
|
|
4596
|
+
);
|
|
3193
4597
|
const refresh = react.useCallback((key) => {
|
|
3194
4598
|
loaderRef.current?.refresh(key);
|
|
3195
4599
|
}, []);
|
|
3196
|
-
return { widgets, refresh, gridRef };
|
|
4600
|
+
return { widgets, refresh, setOptions, gridRef };
|
|
3197
4601
|
}
|
|
3198
4602
|
|
|
3199
4603
|
exports.AlertListWidget = AlertListWidget;
|
|
@@ -3221,6 +4625,7 @@ exports.describeChart = describeChart;
|
|
|
3221
4625
|
exports.joinClassNames = joinClassNames;
|
|
3222
4626
|
exports.seriesColor = seriesColor;
|
|
3223
4627
|
exports.useDetailData = useDetailData;
|
|
4628
|
+
exports.useStoredLayout = useStoredLayout;
|
|
3224
4629
|
exports.useWidgetSettings = useWidgetSettings;
|
|
3225
4630
|
exports.useWidgets = useWidgets;
|
|
3226
4631
|
exports.widgetTitle = widgetTitle;
|