@richardmcquiston01/dashboard-widgets-toolkit 0.8.0 → 0.10.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 +605 -14
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +134 -3
- package/dist/core.d.ts +134 -3
- package/dist/core.js +583 -15
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +605 -14
- 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 +583 -15
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1202 -136
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +80 -3
- package/dist/react.d.ts +80 -3
- package/dist/react.js +1202 -136
- package/dist/react.js.map +1 -1
- package/dist/{scale-CrXxawQn.d.cts → scale-N69bo7wo.d.cts} +36 -1
- package/dist/{scale-CrXxawQn.d.ts → scale-N69bo7wo.d.ts} +36 -1
- package/dist/styles.css +173 -0
- package/package.json +1 -1
package/dist/core.cjs
CHANGED
|
@@ -634,21 +634,44 @@ function fillColumnSpans(items, columns, largeSpan = 2) {
|
|
|
634
634
|
function fillWidthSpans(items) {
|
|
635
635
|
return placeAndFill(items, WIDTH_COLUMNS, itemWidth);
|
|
636
636
|
}
|
|
637
|
+
function placeRows(items) {
|
|
638
|
+
return placeAndFillRows(items, WIDTH_COLUMNS, itemWidth).rows;
|
|
639
|
+
}
|
|
640
|
+
function fitCount(items, maxRows) {
|
|
641
|
+
const rows = placeRows(items);
|
|
642
|
+
let count = 0;
|
|
643
|
+
for (const row of rows.slice(0, Math.max(0, Math.floor(maxRows)))) {
|
|
644
|
+
count += row.keys.length;
|
|
645
|
+
}
|
|
646
|
+
return count;
|
|
647
|
+
}
|
|
637
648
|
function placeAndFill(items, columns, spanOf) {
|
|
649
|
+
return placeAndFillRows(items, columns, spanOf).spans;
|
|
650
|
+
}
|
|
651
|
+
function placeAndFillRows(items, columns, spanOf) {
|
|
638
652
|
const trackCount = Math.max(1, Math.floor(columns));
|
|
639
653
|
const spans = /* @__PURE__ */ new Map();
|
|
654
|
+
const rows = [];
|
|
640
655
|
let row = [];
|
|
641
656
|
let used = 0;
|
|
642
657
|
function closeRow() {
|
|
658
|
+
if (row.length === 0) return;
|
|
643
659
|
const fillers = row.filter(({ item }) => fillsWidth(item.fill));
|
|
644
660
|
const leftover = trackCount - used;
|
|
661
|
+
const finalSpans = /* @__PURE__ */ new Map();
|
|
645
662
|
if (fillers.length > 0) {
|
|
646
663
|
const share = Math.floor(leftover / fillers.length);
|
|
647
664
|
const remainder = leftover % fillers.length;
|
|
648
665
|
fillers.forEach(({ item, span }, index) => {
|
|
649
|
-
|
|
666
|
+
const grown = span + share + (index < remainder ? 1 : 0);
|
|
667
|
+
spans.set(item.key, grown);
|
|
668
|
+
finalSpans.set(item.key, grown);
|
|
650
669
|
});
|
|
651
670
|
}
|
|
671
|
+
rows.push({
|
|
672
|
+
keys: row.map(({ item }) => item.key),
|
|
673
|
+
spans: row.map(({ item, span }) => finalSpans.get(item.key) ?? span)
|
|
674
|
+
});
|
|
652
675
|
row = [];
|
|
653
676
|
used = 0;
|
|
654
677
|
}
|
|
@@ -659,10 +682,13 @@ function placeAndFill(items, columns, spanOf) {
|
|
|
659
682
|
used += span;
|
|
660
683
|
}
|
|
661
684
|
closeRow();
|
|
662
|
-
return spans;
|
|
685
|
+
return { spans, rows };
|
|
663
686
|
}
|
|
664
687
|
|
|
665
688
|
// src/core/layout.ts
|
|
689
|
+
var MAX_PAGE_TITLE_LENGTH = 30;
|
|
690
|
+
var MAX_PARSED_PAGES = 50;
|
|
691
|
+
var MAX_PAGE_ROWS = 20;
|
|
666
692
|
var EMPTY_LAYOUT = Object.freeze({
|
|
667
693
|
order: Object.freeze([]),
|
|
668
694
|
hidden: Object.freeze([]),
|
|
@@ -692,16 +718,66 @@ function parseLayout(raw) {
|
|
|
692
718
|
const order = uniqueStrings(record["order"]);
|
|
693
719
|
const hidden = uniqueStrings(record["hidden"]);
|
|
694
720
|
const minimized = uniqueStrings(record["minimized"]);
|
|
721
|
+
const pages = parsePages(record["pages"]);
|
|
722
|
+
if (pages.length > 0) {
|
|
723
|
+
return {
|
|
724
|
+
order: EMPTY_LAYOUT.order,
|
|
725
|
+
hidden: EMPTY_LAYOUT.hidden,
|
|
726
|
+
minimized: EMPTY_LAYOUT.minimized,
|
|
727
|
+
pages
|
|
728
|
+
};
|
|
729
|
+
}
|
|
695
730
|
if (order.length === 0 && hidden.length === 0 && minimized.length === 0) {
|
|
696
731
|
return EMPTY_LAYOUT;
|
|
697
732
|
}
|
|
698
733
|
return { order, hidden, minimized };
|
|
699
734
|
}
|
|
735
|
+
function parsePages(value) {
|
|
736
|
+
if (!Array.isArray(value)) return [];
|
|
737
|
+
const pages = [];
|
|
738
|
+
const keys = /* @__PURE__ */ new Set();
|
|
739
|
+
for (const entry of value.slice(0, MAX_PARSED_PAGES)) {
|
|
740
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
743
|
+
const record = entry;
|
|
744
|
+
const key = record["key"];
|
|
745
|
+
if (typeof key !== "string" || key === "" || keys.has(key)) continue;
|
|
746
|
+
keys.add(key);
|
|
747
|
+
const rawTitle = record["title"];
|
|
748
|
+
const title = typeof rawTitle === "string" && rawTitle.trim() !== "" ? rawTitle.trim().slice(0, MAX_PAGE_TITLE_LENGTH) : `Page ${pages.length + 1}`;
|
|
749
|
+
const maxRows = record["maxRows"];
|
|
750
|
+
const held = /* @__PURE__ */ new Set();
|
|
751
|
+
for (const earlier of pages) {
|
|
752
|
+
for (const k of [...earlier.order, ...earlier.hidden]) held.add(k);
|
|
753
|
+
}
|
|
754
|
+
const keep = (list) => uniqueStrings(list).filter((k) => !held.has(k));
|
|
755
|
+
pages.push({
|
|
756
|
+
key,
|
|
757
|
+
title,
|
|
758
|
+
order: keep(record["order"]),
|
|
759
|
+
hidden: keep(record["hidden"]),
|
|
760
|
+
minimized: keep(record["minimized"]),
|
|
761
|
+
...typeof maxRows === "number" && Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_PAGE_ROWS ? { maxRows } : {}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
return pages;
|
|
765
|
+
}
|
|
700
766
|
function serializeLayout(layout) {
|
|
701
767
|
return JSON.stringify({
|
|
702
768
|
order: [...layout.order],
|
|
703
769
|
hidden: [...layout.hidden],
|
|
704
|
-
minimized: [...layout.minimized]
|
|
770
|
+
minimized: [...layout.minimized],
|
|
771
|
+
...layout.pages === void 0 || layout.pages.length === 0 ? {} : {
|
|
772
|
+
pages: layout.pages.map((page) => ({
|
|
773
|
+
key: page.key,
|
|
774
|
+
title: page.title,
|
|
775
|
+
order: [...page.order],
|
|
776
|
+
hidden: [...page.hidden],
|
|
777
|
+
minimized: [...page.minimized],
|
|
778
|
+
...page.maxRows === void 0 ? {} : { maxRows: page.maxRows }
|
|
779
|
+
}))
|
|
780
|
+
}
|
|
705
781
|
});
|
|
706
782
|
}
|
|
707
783
|
function bySortOrder(items) {
|
|
@@ -824,13 +900,28 @@ function toggleMinimized(layout, key) {
|
|
|
824
900
|
}
|
|
825
901
|
function pruneLayout(definitions, layout) {
|
|
826
902
|
const known = new Set(definitions.map((d) => d.key));
|
|
827
|
-
const keep = (keys) => keys.filter((k) => known.has(k));
|
|
828
|
-
|
|
829
|
-
order: keep(
|
|
830
|
-
hidden: keep(
|
|
831
|
-
minimized: keep(
|
|
903
|
+
const keep = (keys) => keys.every((k) => known.has(k)) ? keys : keys.filter((k) => known.has(k));
|
|
904
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
905
|
+
order: keep(lists.order),
|
|
906
|
+
hidden: keep(lists.hidden),
|
|
907
|
+
minimized: keep(lists.minimized)
|
|
908
|
+
}));
|
|
909
|
+
}
|
|
910
|
+
function mapLayoutLists(layout, change) {
|
|
911
|
+
const top = change(layout);
|
|
912
|
+
const pages = layout.pages?.map((page) => {
|
|
913
|
+
const next = change(page);
|
|
914
|
+
return next.order === page.order && next.hidden === page.hidden && next.minimized === page.minimized ? page : { ...page, ...next };
|
|
915
|
+
});
|
|
916
|
+
const pagesChanged = pages !== void 0 && pages.some((page, i) => page !== layout.pages?.[i]);
|
|
917
|
+
if (top.order === layout.order && top.hidden === layout.hidden && top.minimized === layout.minimized && !pagesChanged) {
|
|
918
|
+
return layout;
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
...layout,
|
|
922
|
+
...top,
|
|
923
|
+
...pages === void 0 ? {} : { pages }
|
|
832
924
|
};
|
|
833
|
-
return next.order.length === layout.order.length && next.hidden.length === layout.hidden.length && next.minimized.length === layout.minimized.length ? layout : next;
|
|
834
925
|
}
|
|
835
926
|
function enforceLocks(definitions, layout, options = {}) {
|
|
836
927
|
if (options.overrideLocks === true) return layout;
|
|
@@ -843,11 +934,12 @@ function enforceLocks(definitions, layout, options = {}) {
|
|
|
843
934
|
if (lock.hide) noHide.add(definition.key);
|
|
844
935
|
if (lock.minimize) noMinimize.add(definition.key);
|
|
845
936
|
}
|
|
846
|
-
const
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
937
|
+
const without2 = (keys, drop) => keys.some((key) => drop.has(key)) ? keys.filter((key) => !drop.has(key)) : keys;
|
|
938
|
+
return mapLayoutLists(layout, (lists) => ({
|
|
939
|
+
order: without2(lists.order, noMove),
|
|
940
|
+
hidden: without2(lists.hidden, noHide),
|
|
941
|
+
minimized: without2(lists.minimized, noMinimize)
|
|
942
|
+
}));
|
|
851
943
|
}
|
|
852
944
|
|
|
853
945
|
// src/core/validate.ts
|
|
@@ -1212,6 +1304,13 @@ function validateWidgetDefinition(value) {
|
|
|
1212
1304
|
checkDetailOption(value["detail"], problems);
|
|
1213
1305
|
checkTableControlsOption(value["tableControls"], value["kind"], problems);
|
|
1214
1306
|
checkLockedOption(value["locked"], problems);
|
|
1307
|
+
if (value["page"] !== void 0) {
|
|
1308
|
+
if (typeof value["page"] !== "string" || value["page"].trim() === "") {
|
|
1309
|
+
problems.push(
|
|
1310
|
+
`page must be a non-empty page key or title, got ${describeType2(value["page"])}.`
|
|
1311
|
+
);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1215
1314
|
if (value["fill"] !== void 0) {
|
|
1216
1315
|
requireOneOf(value, "fill", WIDGET_FILLS, "", problems);
|
|
1217
1316
|
}
|
|
@@ -1465,6 +1564,475 @@ function createWidgetLoader(options) {
|
|
|
1465
1564
|
};
|
|
1466
1565
|
}
|
|
1467
1566
|
|
|
1567
|
+
// src/core/pages.ts
|
|
1568
|
+
var DEFAULT_PAGE_KEY = "default";
|
|
1569
|
+
var DEFAULT_PAGE_ROWS = 4;
|
|
1570
|
+
function rowsAllowed(page, options) {
|
|
1571
|
+
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
1572
|
+
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
1573
|
+
}
|
|
1574
|
+
function hasPages(layout) {
|
|
1575
|
+
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
1576
|
+
}
|
|
1577
|
+
function pageList(layout) {
|
|
1578
|
+
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
1579
|
+
return layout.pages;
|
|
1580
|
+
}
|
|
1581
|
+
return [
|
|
1582
|
+
{
|
|
1583
|
+
key: DEFAULT_PAGE_KEY,
|
|
1584
|
+
title: "Page 1",
|
|
1585
|
+
order: layout.order,
|
|
1586
|
+
hidden: layout.hidden,
|
|
1587
|
+
minimized: layout.minimized
|
|
1588
|
+
}
|
|
1589
|
+
];
|
|
1590
|
+
}
|
|
1591
|
+
function withExplicitPages(layout) {
|
|
1592
|
+
if (hasPages(layout)) return layout;
|
|
1593
|
+
return {
|
|
1594
|
+
...layout,
|
|
1595
|
+
order: [],
|
|
1596
|
+
hidden: [],
|
|
1597
|
+
minimized: [],
|
|
1598
|
+
pages: pageList(layout)
|
|
1599
|
+
};
|
|
1600
|
+
}
|
|
1601
|
+
function sameKeys(a, b) {
|
|
1602
|
+
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
1603
|
+
}
|
|
1604
|
+
function pageLayout(layout, pageKey) {
|
|
1605
|
+
if (!hasPages(layout))
|
|
1606
|
+
return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
|
|
1607
|
+
const page = layout.pages?.find(
|
|
1608
|
+
(p) => p.key === pageKey
|
|
1609
|
+
);
|
|
1610
|
+
if (page === void 0) return void 0;
|
|
1611
|
+
const { pages, ...rest } = layout;
|
|
1612
|
+
return {
|
|
1613
|
+
...rest,
|
|
1614
|
+
order: page.order,
|
|
1615
|
+
hidden: page.hidden,
|
|
1616
|
+
minimized: page.minimized
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1619
|
+
function withPageLayout(layout, pageKey, next) {
|
|
1620
|
+
const current = pageLayout(layout, pageKey);
|
|
1621
|
+
if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
|
|
1622
|
+
return layout;
|
|
1623
|
+
}
|
|
1624
|
+
if (!hasPages(layout)) {
|
|
1625
|
+
return {
|
|
1626
|
+
...layout,
|
|
1627
|
+
order: next.order,
|
|
1628
|
+
hidden: next.hidden,
|
|
1629
|
+
minimized: next.minimized
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
return {
|
|
1633
|
+
...layout,
|
|
1634
|
+
pages: (layout.pages ?? []).map(
|
|
1635
|
+
(page) => page.key === pageKey ? {
|
|
1636
|
+
...page,
|
|
1637
|
+
order: next.order,
|
|
1638
|
+
hidden: next.hidden,
|
|
1639
|
+
minimized: next.minimized
|
|
1640
|
+
} : page
|
|
1641
|
+
)
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
function normalizedTitle(title) {
|
|
1645
|
+
return title.trim().normalize("NFKC").toLowerCase();
|
|
1646
|
+
}
|
|
1647
|
+
function resolvePageKey(layout, reference) {
|
|
1648
|
+
if (reference === void 0 || reference === "") return void 0;
|
|
1649
|
+
const pages = pageList(layout);
|
|
1650
|
+
const byKey = pages.find((p) => p.key === reference);
|
|
1651
|
+
if (byKey !== void 0) return byKey.key;
|
|
1652
|
+
const wanted = normalizedTitle(reference);
|
|
1653
|
+
return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
|
|
1654
|
+
}
|
|
1655
|
+
function homePageKey(layout, item) {
|
|
1656
|
+
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
1657
|
+
}
|
|
1658
|
+
function assignPages(definitions, layout) {
|
|
1659
|
+
const pages = pageList(layout);
|
|
1660
|
+
const assigned = new Map(pages.map((p) => [p.key, []]));
|
|
1661
|
+
for (const definition of definitions) {
|
|
1662
|
+
let pageKey;
|
|
1663
|
+
if (!resolveWidgetLock(definition).move) {
|
|
1664
|
+
pageKey = pages.find(
|
|
1665
|
+
(p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
|
|
1666
|
+
)?.key;
|
|
1667
|
+
}
|
|
1668
|
+
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
1669
|
+
}
|
|
1670
|
+
return assigned;
|
|
1671
|
+
}
|
|
1672
|
+
function pageOf(definitions, layout, key) {
|
|
1673
|
+
for (const [pageKey, widgets] of assignPages(definitions, layout)) {
|
|
1674
|
+
if (widgets.some((w) => w.key === key)) return pageKey;
|
|
1675
|
+
}
|
|
1676
|
+
return void 0;
|
|
1677
|
+
}
|
|
1678
|
+
function pageWidgets(definitions, layout, pageKey) {
|
|
1679
|
+
const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
|
|
1680
|
+
const view = pageLayout(layout, pageKey);
|
|
1681
|
+
return view === void 0 ? [] : visibleWidgets(onPage, view);
|
|
1682
|
+
}
|
|
1683
|
+
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
1684
|
+
const page = pageList(layout).find(
|
|
1685
|
+
(p) => p.key === pageKey
|
|
1686
|
+
);
|
|
1687
|
+
if (page === void 0) return void 0;
|
|
1688
|
+
const maxRows = rowsAllowed(page, options);
|
|
1689
|
+
const rowsUsed = placeRows(
|
|
1690
|
+
pageWidgets(definitions, layout, pageKey)
|
|
1691
|
+
).length;
|
|
1692
|
+
return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
|
|
1693
|
+
}
|
|
1694
|
+
function describePage(page) {
|
|
1695
|
+
return `Page "${page.title}"`;
|
|
1696
|
+
}
|
|
1697
|
+
function titleOf(item) {
|
|
1698
|
+
return item.title ?? item.key;
|
|
1699
|
+
}
|
|
1700
|
+
function checkTitle(layout, title, exceptKey) {
|
|
1701
|
+
const trimmed = title.trim();
|
|
1702
|
+
if (trimmed === "") return err("A page title must not be empty.");
|
|
1703
|
+
if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
|
|
1704
|
+
return err(
|
|
1705
|
+
`Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
|
|
1706
|
+
);
|
|
1707
|
+
}
|
|
1708
|
+
const wanted = normalizedTitle(trimmed);
|
|
1709
|
+
const clash = pageList(layout).find(
|
|
1710
|
+
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
1711
|
+
);
|
|
1712
|
+
if (clash !== void 0) {
|
|
1713
|
+
return err(`Another page is already called "${clash.title}".`);
|
|
1714
|
+
}
|
|
1715
|
+
return ok(trimmed);
|
|
1716
|
+
}
|
|
1717
|
+
function nextPageKey(layout) {
|
|
1718
|
+
const keys = new Set(pageList(layout).map((p) => p.key));
|
|
1719
|
+
let n = 1;
|
|
1720
|
+
while (keys.has(`page-${n}`)) n += 1;
|
|
1721
|
+
return `page-${n}`;
|
|
1722
|
+
}
|
|
1723
|
+
function nextPageTitle(layout) {
|
|
1724
|
+
const used = new Set(
|
|
1725
|
+
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
1726
|
+
);
|
|
1727
|
+
let n = pageList(layout).length + 1;
|
|
1728
|
+
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
1729
|
+
return `Page ${n}`;
|
|
1730
|
+
}
|
|
1731
|
+
function addPage(layout, options = {}) {
|
|
1732
|
+
if (pageList(layout).length >= MAX_PARSED_PAGES) {
|
|
1733
|
+
return err(
|
|
1734
|
+
`The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
const key = options.key ?? nextPageKey(layout);
|
|
1738
|
+
if (pageList(layout).some((p) => p.key === key)) {
|
|
1739
|
+
return err(`A page with the key "${key}" already exists.`);
|
|
1740
|
+
}
|
|
1741
|
+
const title = checkTitle(
|
|
1742
|
+
layout,
|
|
1743
|
+
options.title ?? nextPageTitle(layout)
|
|
1744
|
+
);
|
|
1745
|
+
if (!title.ok) return title;
|
|
1746
|
+
const base = withExplicitPages(layout);
|
|
1747
|
+
const page = {
|
|
1748
|
+
key,
|
|
1749
|
+
title: title.value,
|
|
1750
|
+
order: [],
|
|
1751
|
+
hidden: [],
|
|
1752
|
+
minimized: []
|
|
1753
|
+
};
|
|
1754
|
+
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
1755
|
+
}
|
|
1756
|
+
function renamePage(layout, pageKey, title) {
|
|
1757
|
+
const page = pageList(layout).find(
|
|
1758
|
+
(p) => p.key === pageKey
|
|
1759
|
+
);
|
|
1760
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
1761
|
+
const checked = checkTitle(layout, title, pageKey);
|
|
1762
|
+
if (!checked.ok) return checked;
|
|
1763
|
+
if (checked.value === page.title) return ok(layout);
|
|
1764
|
+
const base = withExplicitPages(layout);
|
|
1765
|
+
return ok({
|
|
1766
|
+
...base,
|
|
1767
|
+
pages: (base.pages ?? []).map(
|
|
1768
|
+
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
1769
|
+
)
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
function movePage(layout, pageKey, offset) {
|
|
1773
|
+
const pages = layout.pages ?? [];
|
|
1774
|
+
const from = pages.findIndex((p) => p.key === pageKey);
|
|
1775
|
+
if (from === -1) return layout;
|
|
1776
|
+
const to = Math.max(
|
|
1777
|
+
0,
|
|
1778
|
+
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
1779
|
+
);
|
|
1780
|
+
if (to === from) return layout;
|
|
1781
|
+
const next = [...pages];
|
|
1782
|
+
const [moved] = next.splice(from, 1);
|
|
1783
|
+
next.splice(to, 0, moved);
|
|
1784
|
+
return { ...layout, pages: next };
|
|
1785
|
+
}
|
|
1786
|
+
function overflowing(definitions, layout, options) {
|
|
1787
|
+
return pageList(layout).find(
|
|
1788
|
+
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
function removePage(definitions, layout, pageKey, options = {}) {
|
|
1792
|
+
const pages = pageList(layout);
|
|
1793
|
+
const index = pages.findIndex((p) => p.key === pageKey);
|
|
1794
|
+
const page = pages[index];
|
|
1795
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
1796
|
+
if (pages.length === 1) {
|
|
1797
|
+
return err(
|
|
1798
|
+
`${describePage(page)} is the only page, so it can't be removed.`
|
|
1799
|
+
);
|
|
1800
|
+
}
|
|
1801
|
+
const target = pages[index > 0 ? index - 1 : 1];
|
|
1802
|
+
const merged = {
|
|
1803
|
+
...layout,
|
|
1804
|
+
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
1805
|
+
(p) => p.key === target.key ? {
|
|
1806
|
+
...p,
|
|
1807
|
+
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
1808
|
+
hidden: [...p.hidden, ...page.hidden],
|
|
1809
|
+
minimized: [...p.minimized, ...page.minimized]
|
|
1810
|
+
} : p
|
|
1811
|
+
)
|
|
1812
|
+
};
|
|
1813
|
+
const full = overflowing(
|
|
1814
|
+
definitions,
|
|
1815
|
+
merged,
|
|
1816
|
+
options
|
|
1817
|
+
);
|
|
1818
|
+
if (full !== void 0) {
|
|
1819
|
+
return err(
|
|
1820
|
+
`${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.`
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
return ok(merged);
|
|
1824
|
+
}
|
|
1825
|
+
function without(keys, key) {
|
|
1826
|
+
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
1827
|
+
}
|
|
1828
|
+
function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
|
|
1829
|
+
const widget = definitions.find((d) => d.key === widgetKey);
|
|
1830
|
+
if (widget === void 0) {
|
|
1831
|
+
return err(
|
|
1832
|
+
`Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
|
|
1833
|
+
);
|
|
1834
|
+
}
|
|
1835
|
+
const pages = pageList(layout);
|
|
1836
|
+
const target = pages.find((p) => p.key === targetKey);
|
|
1837
|
+
if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
|
|
1838
|
+
const sourceKey = pageOf(definitions, layout, widgetKey);
|
|
1839
|
+
if (sourceKey === targetKey) return ok(layout);
|
|
1840
|
+
if (resolveWidgetLock(widget).move) {
|
|
1841
|
+
return err(
|
|
1842
|
+
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
const base = withExplicitPages(layout);
|
|
1846
|
+
const source = (base.pages ?? []).find(
|
|
1847
|
+
(p) => p.key === sourceKey
|
|
1848
|
+
);
|
|
1849
|
+
const hidden = source.hidden.includes(widgetKey);
|
|
1850
|
+
const minimized = source.minimized.includes(widgetKey);
|
|
1851
|
+
const targetOrder = pageWidgets(
|
|
1852
|
+
definitions,
|
|
1853
|
+
base,
|
|
1854
|
+
targetKey
|
|
1855
|
+
).map((d) => d.key);
|
|
1856
|
+
const moved = {
|
|
1857
|
+
...base,
|
|
1858
|
+
pages: (base.pages ?? []).map((p) => {
|
|
1859
|
+
if (p.key === sourceKey) {
|
|
1860
|
+
return {
|
|
1861
|
+
...p,
|
|
1862
|
+
order: without(p.order, widgetKey),
|
|
1863
|
+
hidden: without(p.hidden, widgetKey),
|
|
1864
|
+
minimized: without(p.minimized, widgetKey)
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
if (p.key === targetKey) {
|
|
1868
|
+
return {
|
|
1869
|
+
...p,
|
|
1870
|
+
order: hidden ? p.order : [...targetOrder, widgetKey],
|
|
1871
|
+
hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
|
|
1872
|
+
minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
return p;
|
|
1876
|
+
})
|
|
1877
|
+
};
|
|
1878
|
+
if (!hidden) {
|
|
1879
|
+
const allowed = rowsAllowed(target, options);
|
|
1880
|
+
const needed = placeRows(
|
|
1881
|
+
pageWidgets(definitions, moved, targetKey)
|
|
1882
|
+
).length;
|
|
1883
|
+
if (needed > allowed) {
|
|
1884
|
+
return err(
|
|
1885
|
+
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return ok(moved);
|
|
1890
|
+
}
|
|
1891
|
+
function withoutLockField(definition) {
|
|
1892
|
+
if (definition.locked === void 0) return definition;
|
|
1893
|
+
const copy = { ...definition };
|
|
1894
|
+
delete copy.locked;
|
|
1895
|
+
return copy;
|
|
1896
|
+
}
|
|
1897
|
+
function uniqueTitle(taken, title, index) {
|
|
1898
|
+
let base = title.trim().slice(0, MAX_PAGE_TITLE_LENGTH);
|
|
1899
|
+
if (base === "") base = `Page ${index + 1}`;
|
|
1900
|
+
let candidate = base;
|
|
1901
|
+
let n = 1;
|
|
1902
|
+
while (taken.has(normalizedTitle(candidate))) {
|
|
1903
|
+
const suffix = ` (#${n})`;
|
|
1904
|
+
candidate = `${base.slice(0, MAX_PAGE_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
1905
|
+
n += 1;
|
|
1906
|
+
}
|
|
1907
|
+
taken.add(normalizedTitle(candidate));
|
|
1908
|
+
return candidate;
|
|
1909
|
+
}
|
|
1910
|
+
function samePages(a, b) {
|
|
1911
|
+
return a.length === b.length && a.every((p, i) => {
|
|
1912
|
+
const q = b[i];
|
|
1913
|
+
return p.key === q.key && p.title === q.title && p.maxRows === q.maxRows && sameKeys(p.order, q.order) && sameKeys(p.hidden, q.hidden) && sameKeys(p.minimized, q.minimized);
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
function normalizeLayout(definitions, layout, options = {}) {
|
|
1917
|
+
const known = pruneLayout(definitions, layout);
|
|
1918
|
+
const locked = enforceLocks(definitions, known, options);
|
|
1919
|
+
if (!hasPages(locked)) return locked === layout ? layout : locked;
|
|
1920
|
+
const defs = options.overrideLocks === true ? definitions.map(withoutLockField) : definitions;
|
|
1921
|
+
const taken = /* @__PURE__ */ new Set();
|
|
1922
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1923
|
+
const held = /* @__PURE__ */ new Set();
|
|
1924
|
+
let pages = [];
|
|
1925
|
+
(locked.pages ?? []).forEach((page, index) => {
|
|
1926
|
+
if (seenKeys.has(page.key)) return;
|
|
1927
|
+
seenKeys.add(page.key);
|
|
1928
|
+
const keep = (list) => [
|
|
1929
|
+
...new Set(list.filter((k) => !held.has(k)))
|
|
1930
|
+
];
|
|
1931
|
+
const next = {
|
|
1932
|
+
...page,
|
|
1933
|
+
title: uniqueTitle(taken, page.title, index),
|
|
1934
|
+
order: keep(page.order),
|
|
1935
|
+
hidden: keep(page.hidden),
|
|
1936
|
+
minimized: keep(page.minimized)
|
|
1937
|
+
};
|
|
1938
|
+
for (const k of [...next.order, ...next.hidden]) held.add(k);
|
|
1939
|
+
pages.push(next);
|
|
1940
|
+
});
|
|
1941
|
+
const work = () => ({ ...locked, pages });
|
|
1942
|
+
const pinned = defs.filter((d) => resolveWidgetLock(d).move);
|
|
1943
|
+
for (const widget of pinned) {
|
|
1944
|
+
const home = homePageKey(work(), widget);
|
|
1945
|
+
const keysHere = (p, list) => p.key !== home && p[list].includes(widget.key);
|
|
1946
|
+
const wasHidden = pages.some((p) => keysHere(p, "hidden"));
|
|
1947
|
+
const wasMinimized = pages.some((p) => keysHere(p, "minimized"));
|
|
1948
|
+
if (!wasHidden && !wasMinimized) continue;
|
|
1949
|
+
pages = pages.map((p) => {
|
|
1950
|
+
let next = p;
|
|
1951
|
+
if (keysHere(p, "hidden")) {
|
|
1952
|
+
next = { ...next, hidden: without(next.hidden, widget.key) };
|
|
1953
|
+
}
|
|
1954
|
+
if (keysHere(p, "minimized")) {
|
|
1955
|
+
next = { ...next, minimized: without(next.minimized, widget.key) };
|
|
1956
|
+
}
|
|
1957
|
+
if (p.key === home) {
|
|
1958
|
+
next = {
|
|
1959
|
+
...next,
|
|
1960
|
+
hidden: wasHidden && !next.hidden.includes(widget.key) ? [...next.hidden, widget.key] : next.hidden,
|
|
1961
|
+
minimized: wasMinimized && !next.minimized.includes(widget.key) ? [...next.minimized, widget.key] : next.minimized
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
return next;
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
const placedKeys = /* @__PURE__ */ new Set();
|
|
1968
|
+
for (const p of pages) {
|
|
1969
|
+
for (const k of [...p.order, ...p.hidden]) placedKeys.add(k);
|
|
1970
|
+
}
|
|
1971
|
+
const pinnedKeys = new Set(pinned.map((d) => d.key));
|
|
1972
|
+
const unplaced = defs.filter((d) => !placedKeys.has(d.key) && !pinnedKeys.has(d.key)).map((definition, index) => ({ definition, index })).sort(
|
|
1973
|
+
(a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
|
|
1974
|
+
).map(({ definition }) => definition);
|
|
1975
|
+
for (const widget of unplaced) {
|
|
1976
|
+
const home = homePageKey(work(), widget);
|
|
1977
|
+
pages = pages.map(
|
|
1978
|
+
(p) => p.key === home ? { ...p, order: [...p.order, widget.key] } : p
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
for (let i = 0; i < pages.length; i += 1) {
|
|
1982
|
+
const page = pages[i];
|
|
1983
|
+
const shown = pageWidgets(defs, work(), page.key);
|
|
1984
|
+
const fits = fitCount(shown, rowsAllowed(page, options));
|
|
1985
|
+
if (fits >= shown.length) continue;
|
|
1986
|
+
const moving = shown.slice(fits).filter((d) => !pinnedKeys.has(d.key)).map((d) => d.key);
|
|
1987
|
+
if (moving.length === 0) continue;
|
|
1988
|
+
const moveSet = new Set(moving);
|
|
1989
|
+
const movedMinimized = page.minimized.filter(
|
|
1990
|
+
(k) => moveSet.has(k)
|
|
1991
|
+
);
|
|
1992
|
+
pages = pages.map(
|
|
1993
|
+
(p) => p.key === page.key ? {
|
|
1994
|
+
...p,
|
|
1995
|
+
order: p.order.filter((k) => !moveSet.has(k)),
|
|
1996
|
+
minimized: p.minimized.filter((k) => !moveSet.has(k))
|
|
1997
|
+
} : p
|
|
1998
|
+
);
|
|
1999
|
+
let placed = false;
|
|
2000
|
+
for (let j = i + 1; j < pages.length && !placed; j += 1) {
|
|
2001
|
+
const candidate = pages[j];
|
|
2002
|
+
const trial = pages.map(
|
|
2003
|
+
(p) => p.key === candidate.key ? {
|
|
2004
|
+
...p,
|
|
2005
|
+
order: [...moving, ...p.order],
|
|
2006
|
+
minimized: [...p.minimized, ...movedMinimized]
|
|
2007
|
+
} : p
|
|
2008
|
+
);
|
|
2009
|
+
const items = pageWidgets(
|
|
2010
|
+
defs,
|
|
2011
|
+
{ ...locked, pages: trial },
|
|
2012
|
+
candidate.key
|
|
2013
|
+
);
|
|
2014
|
+
if (placeRows(items).length <= rowsAllowed(candidate, options)) {
|
|
2015
|
+
pages = trial;
|
|
2016
|
+
placed = true;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
if (!placed) {
|
|
2020
|
+
const key = nextPageKey(work());
|
|
2021
|
+
const title = uniqueTitle(
|
|
2022
|
+
new Set(pages.map((p) => normalizedTitle(p.title))),
|
|
2023
|
+
`Page ${pages.length + 1}`,
|
|
2024
|
+
pages.length
|
|
2025
|
+
);
|
|
2026
|
+
pages = [
|
|
2027
|
+
...pages,
|
|
2028
|
+
{ key, title, order: moving, hidden: [], minimized: movedMinimized }
|
|
2029
|
+
];
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
const result = work();
|
|
2033
|
+
return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) ? layout : result;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
1468
2036
|
// src/core/scale.ts
|
|
1469
2037
|
function niceStep(value) {
|
|
1470
2038
|
if (!(value > 0) || !Number.isFinite(value)) return 1;
|
|
@@ -1499,10 +2067,15 @@ exports.DEFAULT_COMPARISON_LABEL = DEFAULT_COMPARISON_LABEL;
|
|
|
1499
2067
|
exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
|
|
1500
2068
|
exports.DEFAULT_DETAIL_PAGE_SIZE = DEFAULT_DETAIL_PAGE_SIZE;
|
|
1501
2069
|
exports.DEFAULT_EMPTY_TEXT = DEFAULT_EMPTY_TEXT;
|
|
2070
|
+
exports.DEFAULT_PAGE_KEY = DEFAULT_PAGE_KEY;
|
|
2071
|
+
exports.DEFAULT_PAGE_ROWS = DEFAULT_PAGE_ROWS;
|
|
1502
2072
|
exports.DETAIL_MODES = DETAIL_MODES;
|
|
1503
2073
|
exports.EMPTY_LAYOUT = EMPTY_LAYOUT;
|
|
1504
2074
|
exports.MAX_DETAIL_PAGE_SIZE = MAX_DETAIL_PAGE_SIZE;
|
|
1505
2075
|
exports.MAX_GRAPH_SERIES = MAX_GRAPH_SERIES;
|
|
2076
|
+
exports.MAX_PAGE_ROWS = MAX_PAGE_ROWS;
|
|
2077
|
+
exports.MAX_PAGE_TITLE_LENGTH = MAX_PAGE_TITLE_LENGTH;
|
|
2078
|
+
exports.MAX_PARSED_PAGES = MAX_PARSED_PAGES;
|
|
1506
2079
|
exports.MAX_REPORTED_PROBLEMS = MAX_REPORTED_PROBLEMS;
|
|
1507
2080
|
exports.MAX_WIDGET_WIDTH = MAX_WIDGET_WIDTH;
|
|
1508
2081
|
exports.MIN_WIDGET_WIDTH = MIN_WIDGET_WIDTH;
|
|
@@ -1511,6 +2084,8 @@ exports.WIDGET_FILLS = WIDGET_FILLS;
|
|
|
1511
2084
|
exports.WIDGET_KINDS = WIDGET_KINDS;
|
|
1512
2085
|
exports.WIDGET_SIZES = WIDGET_SIZES;
|
|
1513
2086
|
exports.WIDTH_COLUMNS = WIDTH_COLUMNS;
|
|
2087
|
+
exports.addPage = addPage;
|
|
2088
|
+
exports.assignPages = assignPages;
|
|
1514
2089
|
exports.baseColumnSpan = baseColumnSpan;
|
|
1515
2090
|
exports.cacheKeyFor = cacheKeyFor;
|
|
1516
2091
|
exports.createWidgetLoader = createWidgetLoader;
|
|
@@ -1527,10 +2102,12 @@ exports.fillColumnSpans = fillColumnSpans;
|
|
|
1527
2102
|
exports.fillWidthSpans = fillWidthSpans;
|
|
1528
2103
|
exports.fillsHeight = fillsHeight;
|
|
1529
2104
|
exports.fillsWidth = fillsWidth;
|
|
2105
|
+
exports.fitCount = fitCount;
|
|
1530
2106
|
exports.formatCount = formatCount;
|
|
1531
2107
|
exports.formatPercentChange = formatPercentChange;
|
|
1532
2108
|
exports.formatValue = formatValue;
|
|
1533
2109
|
exports.fromLegacyDefinition = fromLegacyDefinition;
|
|
2110
|
+
exports.hasPages = hasPages;
|
|
1534
2111
|
exports.hiddenWidgets = hiddenWidgets;
|
|
1535
2112
|
exports.hideWidget = hideWidget;
|
|
1536
2113
|
exports.isHidden = isHidden;
|
|
@@ -1546,19 +2123,32 @@ exports.kpiDelta = kpiDelta;
|
|
|
1546
2123
|
exports.labelStride = labelStride;
|
|
1547
2124
|
exports.loadingWidgets = loadingWidgets;
|
|
1548
2125
|
exports.minimizeWidget = minimizeWidget;
|
|
2126
|
+
exports.movePage = movePage;
|
|
1549
2127
|
exports.moveWidget = moveWidget;
|
|
1550
2128
|
exports.moveWidgetBy = moveWidgetBy;
|
|
1551
2129
|
exports.moveWidgetDown = moveWidgetDown;
|
|
2130
|
+
exports.moveWidgetToPage = moveWidgetToPage;
|
|
1552
2131
|
exports.moveWidgetUp = moveWidgetUp;
|
|
2132
|
+
exports.nextPageKey = nextPageKey;
|
|
1553
2133
|
exports.niceDomain = niceDomain;
|
|
1554
2134
|
exports.niceStep = niceStep;
|
|
2135
|
+
exports.normalizeLayout = normalizeLayout;
|
|
1555
2136
|
exports.ok = ok;
|
|
2137
|
+
exports.pageLayout = pageLayout;
|
|
2138
|
+
exports.pageList = pageList;
|
|
2139
|
+
exports.pageOf = pageOf;
|
|
2140
|
+
exports.pageRoom = pageRoom;
|
|
2141
|
+
exports.pageWidgets = pageWidgets;
|
|
1556
2142
|
exports.parseDetailQuery = parseDetailQuery;
|
|
1557
2143
|
exports.parseLayout = parseLayout;
|
|
1558
2144
|
exports.percentChange = percentChange;
|
|
2145
|
+
exports.placeRows = placeRows;
|
|
1559
2146
|
exports.pruneLayout = pruneLayout;
|
|
1560
2147
|
exports.queryRows = queryRows;
|
|
2148
|
+
exports.removePage = removePage;
|
|
2149
|
+
exports.renamePage = renamePage;
|
|
1561
2150
|
exports.resolveDetailOptions = resolveDetailOptions;
|
|
2151
|
+
exports.resolvePageKey = resolvePageKey;
|
|
1562
2152
|
exports.resolvePayload = resolvePayload;
|
|
1563
2153
|
exports.resolveTableControls = resolveTableControls;
|
|
1564
2154
|
exports.resolveWidget = resolveWidget;
|
|
@@ -1581,5 +2171,6 @@ exports.validateWidgetDefinition = validateWidgetDefinition;
|
|
|
1581
2171
|
exports.visibleWidgets = visibleWidgets;
|
|
1582
2172
|
exports.widgetsFor = widgetsFor;
|
|
1583
2173
|
exports.widthForSize = widthForSize;
|
|
2174
|
+
exports.withPageLayout = withPageLayout;
|
|
1584
2175
|
//# sourceMappingURL=core.cjs.map
|
|
1585
2176
|
//# sourceMappingURL=core.cjs.map
|