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