@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/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,289 @@ 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
|
+
}));
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// src/core/options.ts
|
|
946
|
+
var DATE_RANGE_PRESETS = [
|
|
947
|
+
"today",
|
|
948
|
+
"yesterday",
|
|
949
|
+
"last7",
|
|
950
|
+
"last30",
|
|
951
|
+
"last90",
|
|
952
|
+
"thisMonth",
|
|
953
|
+
"lastMonth",
|
|
954
|
+
"thisYear",
|
|
955
|
+
"lastYear"
|
|
956
|
+
];
|
|
957
|
+
var WIDGET_OPTION_TYPES = [
|
|
958
|
+
"choice",
|
|
959
|
+
"number",
|
|
960
|
+
"boolean",
|
|
961
|
+
"text",
|
|
962
|
+
"dateRange",
|
|
963
|
+
"color",
|
|
964
|
+
"sort",
|
|
965
|
+
"columns"
|
|
966
|
+
];
|
|
967
|
+
var MAX_WIDGET_OPTIONS = 20;
|
|
968
|
+
var MAX_TEXT_OPTION_LENGTH = 500;
|
|
969
|
+
var OPTION_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,39}$/;
|
|
970
|
+
var COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
971
|
+
var INTERVAL_PATTERN = /^(\d{4}-\d{2}-\d{2})\/(\d{4}-\d{2}-\d{2})$/;
|
|
972
|
+
var CONTROL_CHARACTERS = /[\u0000-\u001f\u007f]/;
|
|
973
|
+
function isColumnKeyFor(kind, key) {
|
|
974
|
+
if (kind === "TABLE") return /^c\d+$/.test(key);
|
|
975
|
+
if (kind === "BAR_LIST") return key === "label" || key === "value";
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
function isClientApplied(option) {
|
|
979
|
+
return (option.type === "sort" || option.type === "columns") && option.apply === "client";
|
|
980
|
+
}
|
|
981
|
+
function civilToday(context) {
|
|
982
|
+
const now = context.now ?? Date.now();
|
|
983
|
+
let timeZone = context.timeZone ?? "UTC";
|
|
984
|
+
try {
|
|
985
|
+
new Intl.DateTimeFormat("en-US", { timeZone });
|
|
986
|
+
} catch {
|
|
987
|
+
timeZone = "UTC";
|
|
988
|
+
}
|
|
989
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
990
|
+
timeZone,
|
|
991
|
+
year: "numeric",
|
|
992
|
+
month: "2-digit",
|
|
993
|
+
day: "2-digit"
|
|
994
|
+
}).formatToParts(new Date(now));
|
|
995
|
+
const pick = (type) => Number(parts.find((part) => part.type === type)?.value ?? "0");
|
|
996
|
+
return { year: pick("year"), month: pick("month"), day: pick("day") };
|
|
997
|
+
}
|
|
998
|
+
function toIso(epochMs) {
|
|
999
|
+
return new Date(epochMs).toISOString().slice(0, 10);
|
|
1000
|
+
}
|
|
1001
|
+
var DAY_MS = 864e5;
|
|
1002
|
+
function parseIsoDay(text) {
|
|
1003
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
1004
|
+
if (match === null) return void 0;
|
|
1005
|
+
const epoch = Date.UTC(
|
|
1006
|
+
Number(match[1]),
|
|
1007
|
+
Number(match[2]) - 1,
|
|
1008
|
+
Number(match[3])
|
|
1009
|
+
);
|
|
1010
|
+
return toIso(epoch) === text ? epoch : void 0;
|
|
1011
|
+
}
|
|
1012
|
+
function isIsoInterval(value) {
|
|
1013
|
+
const match = INTERVAL_PATTERN.exec(value);
|
|
1014
|
+
if (match === null) return false;
|
|
1015
|
+
const start = parseIsoDay(match[1]);
|
|
1016
|
+
const end = parseIsoDay(match[2]);
|
|
1017
|
+
return start !== void 0 && end !== void 0 && start <= end;
|
|
1018
|
+
}
|
|
1019
|
+
function isPreset(value) {
|
|
1020
|
+
return DATE_RANGE_PRESETS.includes(value);
|
|
1021
|
+
}
|
|
1022
|
+
function resolveDateRange(value, context = {}) {
|
|
1023
|
+
if (isIsoInterval(value)) return value;
|
|
1024
|
+
if (!isPreset(value)) return void 0;
|
|
1025
|
+
const today = civilToday(context);
|
|
1026
|
+
const todayMs = Date.UTC(today.year, today.month - 1, today.day);
|
|
1027
|
+
const range = (startMs, endMs) => `${toIso(startMs)}/${toIso(endMs)}`;
|
|
1028
|
+
switch (value) {
|
|
1029
|
+
case "today":
|
|
1030
|
+
return range(todayMs, todayMs);
|
|
1031
|
+
case "yesterday":
|
|
1032
|
+
return range(todayMs - DAY_MS, todayMs - DAY_MS);
|
|
1033
|
+
case "last7":
|
|
1034
|
+
return range(todayMs - 6 * DAY_MS, todayMs);
|
|
1035
|
+
case "last30":
|
|
1036
|
+
return range(todayMs - 29 * DAY_MS, todayMs);
|
|
1037
|
+
case "last90":
|
|
1038
|
+
return range(todayMs - 89 * DAY_MS, todayMs);
|
|
1039
|
+
case "thisMonth":
|
|
1040
|
+
return range(Date.UTC(today.year, today.month - 1, 1), todayMs);
|
|
1041
|
+
case "lastMonth":
|
|
1042
|
+
return range(
|
|
1043
|
+
Date.UTC(today.year, today.month - 2, 1),
|
|
1044
|
+
Date.UTC(today.year, today.month - 1, 1) - DAY_MS
|
|
1045
|
+
);
|
|
1046
|
+
case "thisYear":
|
|
1047
|
+
return range(Date.UTC(today.year, 0, 1), todayMs);
|
|
1048
|
+
case "lastYear":
|
|
1049
|
+
return range(
|
|
1050
|
+
Date.UTC(today.year - 1, 0, 1),
|
|
1051
|
+
Date.UTC(today.year, 0, 1) - DAY_MS
|
|
1052
|
+
);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
function onStepGrid(option, value) {
|
|
1056
|
+
if (option.step === void 0) return true;
|
|
1057
|
+
const steps = (value - option.min) / option.step;
|
|
1058
|
+
return Math.abs(steps - Math.round(steps)) < 1e-9;
|
|
1059
|
+
}
|
|
1060
|
+
function parseSortValue(value) {
|
|
1061
|
+
const separator = value.lastIndexOf(":");
|
|
1062
|
+
if (separator <= 0) return void 0;
|
|
1063
|
+
const column = value.slice(0, separator);
|
|
1064
|
+
const direction = value.slice(separator + 1);
|
|
1065
|
+
if (direction !== "asc" && direction !== "desc") return void 0;
|
|
1066
|
+
return { column, direction };
|
|
1067
|
+
}
|
|
1068
|
+
function sortFromOptions(definition, values) {
|
|
1069
|
+
if (values === void 0) return void 0;
|
|
1070
|
+
const sortOption = definition.options?.find(
|
|
1071
|
+
(option) => option.type === "sort"
|
|
1072
|
+
);
|
|
1073
|
+
if (sortOption === void 0) return void 0;
|
|
1074
|
+
const chosen = values[sortOption.key];
|
|
1075
|
+
return typeof chosen === "string" ? parseSortValue(chosen) : void 0;
|
|
1076
|
+
}
|
|
1077
|
+
function coerceOptionValue(option, value, context = {}) {
|
|
1078
|
+
switch (option.type) {
|
|
1079
|
+
case "choice":
|
|
1080
|
+
return typeof value === "string" && option.choices.some((choice) => choice.value === value) ? value : void 0;
|
|
1081
|
+
case "number":
|
|
1082
|
+
return typeof value === "number" && Number.isFinite(value) && value >= option.min && value <= option.max && onStepGrid(option, value) ? value : void 0;
|
|
1083
|
+
case "boolean":
|
|
1084
|
+
return typeof value === "boolean" ? value : void 0;
|
|
1085
|
+
case "text": {
|
|
1086
|
+
if (typeof value !== "string") return void 0;
|
|
1087
|
+
const trimmed = value.trim();
|
|
1088
|
+
return trimmed.length <= option.maxLength && !CONTROL_CHARACTERS.test(trimmed) ? trimmed : void 0;
|
|
1089
|
+
}
|
|
1090
|
+
case "dateRange": {
|
|
1091
|
+
if (typeof value !== "string") return void 0;
|
|
1092
|
+
if (isPreset(value) && option.presets !== void 0 && !option.presets.some((preset) => preset.value === value)) {
|
|
1093
|
+
return void 0;
|
|
1094
|
+
}
|
|
1095
|
+
return resolveDateRange(value, context);
|
|
1096
|
+
}
|
|
1097
|
+
case "color":
|
|
1098
|
+
return typeof value === "string" && COLOR_PATTERN.test(value) ? value.toLowerCase() : void 0;
|
|
1099
|
+
case "sort": {
|
|
1100
|
+
if (typeof value !== "string") return void 0;
|
|
1101
|
+
const sort = parseSortValue(value);
|
|
1102
|
+
return sort !== void 0 && option.columns.some((column) => column.key === sort.column) ? value : void 0;
|
|
1103
|
+
}
|
|
1104
|
+
case "columns": {
|
|
1105
|
+
if (!Array.isArray(value) || value.length === 0) return void 0;
|
|
1106
|
+
const keys = value;
|
|
1107
|
+
const allowed = new Set(option.columns.map((c) => c.key));
|
|
1108
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1109
|
+
for (const key of keys) {
|
|
1110
|
+
if (typeof key !== "string" || !allowed.has(key) || seen.has(key)) {
|
|
1111
|
+
return void 0;
|
|
1112
|
+
}
|
|
1113
|
+
seen.add(key);
|
|
1114
|
+
}
|
|
1115
|
+
return [...seen];
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function defaultOptionValue(option, context = {}) {
|
|
1120
|
+
switch (option.type) {
|
|
1121
|
+
case "dateRange":
|
|
1122
|
+
return resolveDateRange(option.default, context);
|
|
1123
|
+
case "sort":
|
|
1124
|
+
return option.default;
|
|
1125
|
+
case "columns":
|
|
1126
|
+
return [...option.default];
|
|
1127
|
+
default:
|
|
1128
|
+
return option.default;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
var NO_VALUES = Object.freeze({});
|
|
1132
|
+
function resolveOptionValues(definition, chosen, context = {}) {
|
|
1133
|
+
const options = definition.options;
|
|
1134
|
+
if (options === void 0 || options.length === 0) return NO_VALUES;
|
|
1135
|
+
const values = {};
|
|
1136
|
+
for (const option of options) {
|
|
1137
|
+
const picked = chosen?.[option.key];
|
|
1138
|
+
const value = (picked === void 0 ? void 0 : coerceOptionValue(option, picked, context)) ?? defaultOptionValue(option, context);
|
|
1139
|
+
if (value !== void 0) values[option.key] = value;
|
|
1140
|
+
}
|
|
1141
|
+
return values;
|
|
1142
|
+
}
|
|
1143
|
+
function sameValue(a, b) {
|
|
1144
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1145
|
+
return a.length === b.length && a.every((item, i) => item === b[i]);
|
|
1146
|
+
}
|
|
1147
|
+
return a === b;
|
|
1148
|
+
}
|
|
1149
|
+
function sameOptionValues(a, b) {
|
|
1150
|
+
const keys = Object.keys({ ...a, ...b });
|
|
1151
|
+
return keys.every((key) => sameValue(a[key], b[key]));
|
|
1152
|
+
}
|
|
1153
|
+
function optionsCacheSuffix(definition, values, context = {}) {
|
|
1154
|
+
const parts = [];
|
|
1155
|
+
for (const option of definition.options ?? []) {
|
|
1156
|
+
if (isClientApplied(option)) continue;
|
|
1157
|
+
const value = values[option.key];
|
|
1158
|
+
if (sameValue(value, defaultOptionValue(option, context))) continue;
|
|
1159
|
+
parts.push(
|
|
1160
|
+
`${option.key}=${encodeURIComponent(JSON.stringify(value ?? null))}`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
parts.sort();
|
|
1164
|
+
return parts.length === 0 ? "" : `?${parts.join("&")}`;
|
|
1165
|
+
}
|
|
1166
|
+
function tableColumnIndex(key) {
|
|
1167
|
+
const match = /^c(\d+)$/.exec(key);
|
|
1168
|
+
return match === null ? void 0 : Number(match[1]);
|
|
1169
|
+
}
|
|
1170
|
+
function sortBarItems(items, sort) {
|
|
1171
|
+
const sign = sort.direction === "desc" ? -1 : 1;
|
|
1172
|
+
return items.map((item, index) => ({ item, index })).sort((a, b) => {
|
|
1173
|
+
const order = sort.column === "value" ? a.item.value - b.item.value : a.item.label.localeCompare(b.item.label);
|
|
1174
|
+
return order * sign || a.index - b.index;
|
|
1175
|
+
}).map(({ item }) => item);
|
|
1176
|
+
}
|
|
1177
|
+
function applyClientOptions(definition, data, values) {
|
|
1178
|
+
const options = definition.options ?? [];
|
|
1179
|
+
const sortOption = options.find(
|
|
1180
|
+
(o) => o.type === "sort" && o.apply === "client"
|
|
1181
|
+
);
|
|
1182
|
+
const columnsOption = options.find(
|
|
1183
|
+
(o) => o.type === "columns" && o.apply === "client"
|
|
1184
|
+
);
|
|
1185
|
+
const sortValue = sortOption === void 0 ? void 0 : values[sortOption.key];
|
|
1186
|
+
const columnsValue = columnsOption === void 0 ? void 0 : values[columnsOption.key];
|
|
1187
|
+
let next = data;
|
|
1188
|
+
if (typeof sortValue === "string") {
|
|
1189
|
+
const sort = parseSortValue(sortValue);
|
|
1190
|
+
if (sort !== void 0 && next.kind === "BAR_LIST") {
|
|
1191
|
+
if (sort.column === "label" || sort.column === "value") {
|
|
1192
|
+
next = { ...next, items: sortBarItems(next.items, sort) };
|
|
1193
|
+
}
|
|
1194
|
+
} else if (sort !== void 0 && next.kind === "TABLE") {
|
|
1195
|
+
const index = tableColumnIndex(sort.column);
|
|
1196
|
+
if (index !== void 0 && index < next.columns.length) {
|
|
1197
|
+
const sorted = queryRows(tableDetailData(next), {
|
|
1198
|
+
page: 1,
|
|
1199
|
+
pageSize: Math.max(1, next.rows.length),
|
|
1200
|
+
sort
|
|
1201
|
+
});
|
|
1202
|
+
if (sorted.ok) next = { ...next, rows: sorted.value.rows };
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
if (Array.isArray(columnsValue) && next.kind === "TABLE") {
|
|
1207
|
+
const table = next;
|
|
1208
|
+
const indexes = columnsValue.map(tableColumnIndex).filter(
|
|
1209
|
+
(index) => index !== void 0 && index < table.columns.length
|
|
1210
|
+
);
|
|
1211
|
+
if (indexes.length > 0) {
|
|
1212
|
+
next = {
|
|
1213
|
+
...table,
|
|
1214
|
+
columns: indexes.map((i) => table.columns[i]),
|
|
1215
|
+
rows: table.rows.map((row) => indexes.map((i) => row[i]))
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return next;
|
|
851
1220
|
}
|
|
852
1221
|
|
|
853
1222
|
// src/core/validate.ts
|
|
@@ -1138,6 +1507,284 @@ function checkLockedOption(locked, problems) {
|
|
|
1138
1507
|
optionalBoolean(locked, "hide", "locked", problems);
|
|
1139
1508
|
optionalBoolean(locked, "minimize", "locked", problems);
|
|
1140
1509
|
}
|
|
1510
|
+
function checkOptionColumns(option, path, kind, problems) {
|
|
1511
|
+
const columns = option["columns"];
|
|
1512
|
+
if (!Array.isArray(columns) || columns.length === 0) {
|
|
1513
|
+
problems.push(
|
|
1514
|
+
`${join(path, "columns")} must be a non-empty array of { key, label }, got ${describeType2(columns)}.`
|
|
1515
|
+
);
|
|
1516
|
+
return [];
|
|
1517
|
+
}
|
|
1518
|
+
const keys = [];
|
|
1519
|
+
columns.forEach((entry, index) => {
|
|
1520
|
+
const entryPath = `${path}.columns[${index}]`;
|
|
1521
|
+
if (!isRecord(entry)) {
|
|
1522
|
+
problems.push(
|
|
1523
|
+
`${entryPath} must be an object, got ${describeType2(entry)}.`
|
|
1524
|
+
);
|
|
1525
|
+
return;
|
|
1526
|
+
}
|
|
1527
|
+
requireString(entry, "label", entryPath, problems);
|
|
1528
|
+
const key = entry["key"];
|
|
1529
|
+
if (typeof key !== "string") {
|
|
1530
|
+
problems.push(
|
|
1531
|
+
`${entryPath}.key must be a string, got ${describeType2(key)}.`
|
|
1532
|
+
);
|
|
1533
|
+
} else if (typeof kind === "string" && !isColumnKeyFor(kind, key)) {
|
|
1534
|
+
problems.push(
|
|
1535
|
+
`${entryPath}.key "${key}" is not a column of a ${kind} widget (${kind === "TABLE" ? '"c0", "c1", ...' : '"label" or "value"'}).`
|
|
1536
|
+
);
|
|
1537
|
+
} else if (keys.includes(key)) {
|
|
1538
|
+
problems.push(`${entryPath}.key "${key}" is used twice.`);
|
|
1539
|
+
} else {
|
|
1540
|
+
keys.push(key);
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
return keys;
|
|
1544
|
+
}
|
|
1545
|
+
function checkOption(option, path, kind, seenKeys, seenTypes, problems) {
|
|
1546
|
+
if (!isRecord(option)) {
|
|
1547
|
+
problems.push(`${path} must be an object, got ${describeType2(option)}.`);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const type = option["type"];
|
|
1551
|
+
if (typeof type !== "string" || !WIDGET_OPTION_TYPES.includes(type)) {
|
|
1552
|
+
requireOneOf(option, "type", WIDGET_OPTION_TYPES, path, problems);
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
const key = option["key"];
|
|
1556
|
+
if (typeof key !== "string" || !OPTION_KEY_PATTERN.test(key)) {
|
|
1557
|
+
problems.push(
|
|
1558
|
+
`${path}.key must be letters, digits, "_" or "-", starting with a letter (40 characters at most), got ${typeof key === "string" ? JSON.stringify(key) : describeType2(key)}.`
|
|
1559
|
+
);
|
|
1560
|
+
} else if (seenKeys.has(key)) {
|
|
1561
|
+
problems.push(`${path}.key "${key}" is used by another option.`);
|
|
1562
|
+
} else {
|
|
1563
|
+
seenKeys.add(key);
|
|
1564
|
+
}
|
|
1565
|
+
requireString(option, "label", path, problems);
|
|
1566
|
+
const fallback = option["default"];
|
|
1567
|
+
switch (type) {
|
|
1568
|
+
case "choice": {
|
|
1569
|
+
const choices = option["choices"];
|
|
1570
|
+
const values = [];
|
|
1571
|
+
if (!Array.isArray(choices) || choices.length === 0) {
|
|
1572
|
+
problems.push(
|
|
1573
|
+
`${path}.choices must be a non-empty array of { value, label }, got ${describeType2(choices)}.`
|
|
1574
|
+
);
|
|
1575
|
+
} else {
|
|
1576
|
+
choices.forEach((choice, index) => {
|
|
1577
|
+
const choicePath = `${path}.choices[${index}]`;
|
|
1578
|
+
if (!isRecord(choice)) {
|
|
1579
|
+
problems.push(
|
|
1580
|
+
`${choicePath} must be an object, got ${describeType2(choice)}.`
|
|
1581
|
+
);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
requireString(choice, "label", choicePath, problems);
|
|
1585
|
+
const value = choice["value"];
|
|
1586
|
+
if (typeof value !== "string" || value === "") {
|
|
1587
|
+
problems.push(
|
|
1588
|
+
`${choicePath}.value must be a non-empty string, got ${describeType2(value)}.`
|
|
1589
|
+
);
|
|
1590
|
+
} else if (values.includes(value)) {
|
|
1591
|
+
problems.push(`${choicePath}.value "${value}" is used twice.`);
|
|
1592
|
+
} else {
|
|
1593
|
+
values.push(value);
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
if (values.length > 0)
|
|
1598
|
+
requireOneOf(option, "default", values, path, problems);
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
case "number": {
|
|
1602
|
+
const { min, max, step } = option;
|
|
1603
|
+
requireNumber(option, "min", path, problems);
|
|
1604
|
+
requireNumber(option, "max", path, problems);
|
|
1605
|
+
if (typeof min === "number" && typeof max === "number" && min > max) {
|
|
1606
|
+
problems.push(
|
|
1607
|
+
`${path}.min (${min}) must not be more than max (${max}).`
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
if (step !== void 0 && !(typeof step === "number" && Number.isFinite(step) && step > 0)) {
|
|
1611
|
+
problems.push(
|
|
1612
|
+
`${path}.step must be a number above 0, got ${describeType2(step)}.`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
if (typeof fallback !== "number" || !Number.isFinite(fallback)) {
|
|
1616
|
+
problems.push(
|
|
1617
|
+
`${path}.default must be a finite number, got ${describeType2(fallback)}.`
|
|
1618
|
+
);
|
|
1619
|
+
} else if (typeof min === "number" && typeof max === "number" && (fallback < min || fallback > max)) {
|
|
1620
|
+
problems.push(
|
|
1621
|
+
`${path}.default (${fallback}) must be between min (${min}) and max (${max}).`
|
|
1622
|
+
);
|
|
1623
|
+
} else if (typeof min === "number" && typeof step === "number" && step > 0 && Math.abs(
|
|
1624
|
+
(fallback - min) / step - Math.round((fallback - min) / step)
|
|
1625
|
+
) > 1e-9) {
|
|
1626
|
+
problems.push(
|
|
1627
|
+
`${path}.default (${fallback}) must be ${min} plus a whole number of steps of ${step}.`
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
break;
|
|
1631
|
+
}
|
|
1632
|
+
case "boolean":
|
|
1633
|
+
if (typeof fallback !== "boolean") {
|
|
1634
|
+
problems.push(
|
|
1635
|
+
`${path}.default must be a boolean, got ${describeType2(fallback)}.`
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
break;
|
|
1639
|
+
case "text": {
|
|
1640
|
+
const maxLength = option["maxLength"];
|
|
1641
|
+
if (typeof maxLength !== "number" || !Number.isInteger(maxLength) || maxLength < 1 || maxLength > MAX_TEXT_OPTION_LENGTH) {
|
|
1642
|
+
problems.push(
|
|
1643
|
+
`${path}.maxLength must be a whole number from 1 to ${MAX_TEXT_OPTION_LENGTH}, got ${typeof maxLength === "number" ? maxLength : describeType2(maxLength)}.`
|
|
1644
|
+
);
|
|
1645
|
+
}
|
|
1646
|
+
if (typeof fallback !== "string") {
|
|
1647
|
+
problems.push(
|
|
1648
|
+
`${path}.default must be a string, got ${describeType2(fallback)}.`
|
|
1649
|
+
);
|
|
1650
|
+
} else if (typeof maxLength === "number" && fallback.length > maxLength) {
|
|
1651
|
+
problems.push(
|
|
1652
|
+
`${path}.default is ${fallback.length} characters; maxLength is ${maxLength}.`
|
|
1653
|
+
);
|
|
1654
|
+
}
|
|
1655
|
+
break;
|
|
1656
|
+
}
|
|
1657
|
+
case "dateRange": {
|
|
1658
|
+
const presets = option["presets"];
|
|
1659
|
+
const offered = [];
|
|
1660
|
+
if (presets !== void 0) {
|
|
1661
|
+
if (!Array.isArray(presets) || presets.length === 0) {
|
|
1662
|
+
problems.push(
|
|
1663
|
+
`${path}.presets must be a non-empty array of { value, label }, got ${describeType2(presets)}.`
|
|
1664
|
+
);
|
|
1665
|
+
} else {
|
|
1666
|
+
presets.forEach((preset, index) => {
|
|
1667
|
+
const presetPath = `${path}.presets[${index}]`;
|
|
1668
|
+
if (!isRecord(preset)) {
|
|
1669
|
+
problems.push(
|
|
1670
|
+
`${presetPath} must be an object, got ${describeType2(preset)}.`
|
|
1671
|
+
);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
requireString(preset, "label", presetPath, problems);
|
|
1675
|
+
requireOneOf(
|
|
1676
|
+
preset,
|
|
1677
|
+
"value",
|
|
1678
|
+
DATE_RANGE_PRESETS,
|
|
1679
|
+
presetPath,
|
|
1680
|
+
problems
|
|
1681
|
+
);
|
|
1682
|
+
if (typeof preset["value"] === "string")
|
|
1683
|
+
offered.push(preset["value"]);
|
|
1684
|
+
});
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
const allowed = offered.length > 0 ? offered : DATE_RANGE_PRESETS;
|
|
1688
|
+
if (typeof fallback !== "string" || !(allowed.includes(fallback) || isIsoInterval(fallback))) {
|
|
1689
|
+
problems.push(
|
|
1690
|
+
`${path}.default must be one of ${allowed.map((p) => `"${p}"`).join(", ")} or an interval like "2026-01-01/2026-01-31", got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
break;
|
|
1694
|
+
}
|
|
1695
|
+
case "color":
|
|
1696
|
+
if (typeof fallback !== "string" || !/^#[0-9a-fA-F]{6}$/.test(fallback)) {
|
|
1697
|
+
problems.push(
|
|
1698
|
+
`${path}.default must be a color like "#1c5cab", got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
break;
|
|
1702
|
+
case "sort":
|
|
1703
|
+
case "columns": {
|
|
1704
|
+
const supported = type === "sort" ? kind === "TABLE" || kind === "BAR_LIST" : kind === "TABLE";
|
|
1705
|
+
if (!supported && typeof kind === "string") {
|
|
1706
|
+
problems.push(
|
|
1707
|
+
`${path} is a ${type} option, which only applies to ${type === "sort" ? "TABLE and BAR_LIST" : "TABLE"} widgets, but kind is "${kind}".`
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
if (seenTypes.has(type)) {
|
|
1711
|
+
problems.push(
|
|
1712
|
+
`${path} is a second ${type} option; a widget has at most one.`
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
seenTypes.add(type);
|
|
1716
|
+
const columnKeys = checkOptionColumns(
|
|
1717
|
+
option,
|
|
1718
|
+
path,
|
|
1719
|
+
supported ? kind : void 0,
|
|
1720
|
+
problems
|
|
1721
|
+
);
|
|
1722
|
+
if (option["apply"] !== void 0) {
|
|
1723
|
+
requireOneOf(option, "apply", ["provider", "client"], path, problems);
|
|
1724
|
+
}
|
|
1725
|
+
if (type === "sort") {
|
|
1726
|
+
if (fallback !== void 0) {
|
|
1727
|
+
const sort = typeof fallback === "string" ? parseSortValue(fallback) : void 0;
|
|
1728
|
+
if (sort === void 0) {
|
|
1729
|
+
problems.push(
|
|
1730
|
+
`${path}.default must look like "c1:desc" ("column:asc" or "column:desc"), got ${typeof fallback === "string" ? JSON.stringify(fallback) : describeType2(fallback)}.`
|
|
1731
|
+
);
|
|
1732
|
+
} else if (columnKeys.length > 0 && !columnKeys.includes(sort.column)) {
|
|
1733
|
+
requireOneOf(
|
|
1734
|
+
{ default: sort.column },
|
|
1735
|
+
"default",
|
|
1736
|
+
columnKeys,
|
|
1737
|
+
path,
|
|
1738
|
+
problems
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
} else if (!Array.isArray(fallback) || fallback.length === 0 || fallback.some((k) => typeof k !== "string")) {
|
|
1743
|
+
problems.push(
|
|
1744
|
+
`${path}.default must be a non-empty array of column keys, got ${describeType2(fallback)}.`
|
|
1745
|
+
);
|
|
1746
|
+
} else {
|
|
1747
|
+
const shown = /* @__PURE__ */ new Set();
|
|
1748
|
+
for (const columnKey of fallback) {
|
|
1749
|
+
if (columnKeys.length > 0 && !columnKeys.includes(columnKey)) {
|
|
1750
|
+
problems.push(
|
|
1751
|
+
`${path}.default has "${columnKey}", which is not one of the columns (${columnKeys.map((c) => `"${c}"`).join(", ")}).`
|
|
1752
|
+
);
|
|
1753
|
+
} else if (shown.has(columnKey)) {
|
|
1754
|
+
problems.push(`${path}.default lists "${columnKey}" twice.`);
|
|
1755
|
+
}
|
|
1756
|
+
shown.add(columnKey);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
break;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
function checkOptions(options, kind, problems) {
|
|
1764
|
+
if (options === void 0) return;
|
|
1765
|
+
if (!Array.isArray(options)) {
|
|
1766
|
+
problems.push(`options must be an array, got ${describeType2(options)}.`);
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
if (options.length > MAX_WIDGET_OPTIONS) {
|
|
1770
|
+
problems.push(
|
|
1771
|
+
`options lists ${options.length} options; the most a widget may declare is ${MAX_WIDGET_OPTIONS}.`
|
|
1772
|
+
);
|
|
1773
|
+
return;
|
|
1774
|
+
}
|
|
1775
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
1776
|
+
const seenTypes = /* @__PURE__ */ new Set();
|
|
1777
|
+
options.forEach((option, index) => {
|
|
1778
|
+
checkOption(
|
|
1779
|
+
option,
|
|
1780
|
+
`options[${index}]`,
|
|
1781
|
+
kind,
|
|
1782
|
+
seenKeys,
|
|
1783
|
+
seenTypes,
|
|
1784
|
+
problems
|
|
1785
|
+
);
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1141
1788
|
function formatProblems(prefix, problems) {
|
|
1142
1789
|
const shown = problems.slice(0, MAX_REPORTED_PROBLEMS);
|
|
1143
1790
|
const more = problems.length - shown.length;
|
|
@@ -1212,6 +1859,14 @@ function validateWidgetDefinition(value) {
|
|
|
1212
1859
|
checkDetailOption(value["detail"], problems);
|
|
1213
1860
|
checkTableControlsOption(value["tableControls"], value["kind"], problems);
|
|
1214
1861
|
checkLockedOption(value["locked"], problems);
|
|
1862
|
+
checkOptions(value["options"], value["kind"], problems);
|
|
1863
|
+
if (value["page"] !== void 0) {
|
|
1864
|
+
if (typeof value["page"] !== "string" || value["page"].trim() === "") {
|
|
1865
|
+
problems.push(
|
|
1866
|
+
`page must be a non-empty page key or title, got ${describeType2(value["page"])}.`
|
|
1867
|
+
);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1215
1870
|
if (value["fill"] !== void 0) {
|
|
1216
1871
|
requireOneOf(value, "fill", WIDGET_FILLS, "", problems);
|
|
1217
1872
|
}
|
|
@@ -1248,12 +1903,40 @@ function failedWidget(definition, error) {
|
|
|
1248
1903
|
function loadingWidgets(definitions) {
|
|
1249
1904
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
1250
1905
|
}
|
|
1251
|
-
function cacheKeyFor(definition, options) {
|
|
1252
|
-
|
|
1906
|
+
function cacheKeyFor(definition, options, values) {
|
|
1907
|
+
const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
1908
|
+
return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
|
|
1909
|
+
}
|
|
1910
|
+
function optionValuesFor(definition, options) {
|
|
1911
|
+
return resolveOptionValues(
|
|
1912
|
+
definition,
|
|
1913
|
+
options.optionValues?.[definition.key],
|
|
1914
|
+
options
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
function finalizeWidget(widget, values) {
|
|
1918
|
+
if (Object.keys(values).length === 0) return widget;
|
|
1919
|
+
if (widget.status === "ok") {
|
|
1920
|
+
const data = applyClientOptions(
|
|
1921
|
+
widget.definition,
|
|
1922
|
+
widget.data,
|
|
1923
|
+
values
|
|
1924
|
+
);
|
|
1925
|
+
return { ...widget, data, options: values };
|
|
1926
|
+
}
|
|
1927
|
+
return { ...widget, options: values };
|
|
1253
1928
|
}
|
|
1254
1929
|
var LoadInterruption = class extends Error {
|
|
1255
1930
|
};
|
|
1256
1931
|
async function resolveWidget(definition, provider, context, options = {}) {
|
|
1932
|
+
const values = optionValuesFor(definition, options);
|
|
1933
|
+
return finalizeWidget(
|
|
1934
|
+
await resolveWidgetUnapplied(definition, provider, context, options),
|
|
1935
|
+
values
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
async function resolveWidgetUnapplied(definition, provider, context, options = {}) {
|
|
1939
|
+
const values = optionValuesFor(definition, options);
|
|
1257
1940
|
const quotedKey = JSON.stringify(definition.key);
|
|
1258
1941
|
if (provider === void 0) {
|
|
1259
1942
|
return failedWidget(
|
|
@@ -1288,7 +1971,10 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
1288
1971
|
payload = await Promise.race([
|
|
1289
1972
|
Promise.resolve().then(() => {
|
|
1290
1973
|
if (controller.signal.aborted) throw new LoadInterruption();
|
|
1291
|
-
return provider(context, definition, {
|
|
1974
|
+
return provider(context, definition, {
|
|
1975
|
+
signal: controller.signal,
|
|
1976
|
+
options: values
|
|
1977
|
+
});
|
|
1292
1978
|
}),
|
|
1293
1979
|
interrupted
|
|
1294
1980
|
]);
|
|
@@ -1312,7 +1998,7 @@ async function resolveWidget(definition, provider, context, options = {}) {
|
|
|
1312
1998
|
const storedAt = Date.now();
|
|
1313
1999
|
if (options.cache !== void 0) {
|
|
1314
2000
|
try {
|
|
1315
|
-
await options.cache.set(cacheKeyFor(definition, options), {
|
|
2001
|
+
await options.cache.set(cacheKeyFor(definition, options, values), {
|
|
1316
2002
|
payload: resolved.data,
|
|
1317
2003
|
storedAt
|
|
1318
2004
|
});
|
|
@@ -1342,7 +2028,17 @@ async function resolveWidgets(definitions, providers, context, options = {}) {
|
|
|
1342
2028
|
|
|
1343
2029
|
// src/core/loader.ts
|
|
1344
2030
|
function createWidgetLoader(options) {
|
|
1345
|
-
const { definitions, providers, context, ...
|
|
2031
|
+
const { definitions, providers, context, ...baseOptions } = options;
|
|
2032
|
+
const chosen = new Map(
|
|
2033
|
+
Object.entries(baseOptions.optionValues ?? {}).flatMap(
|
|
2034
|
+
([key, values]) => values === void 0 ? [] : [[key, values]]
|
|
2035
|
+
)
|
|
2036
|
+
);
|
|
2037
|
+
const resolveOptions = baseOptions;
|
|
2038
|
+
function currentOptions() {
|
|
2039
|
+
return { ...resolveOptions, optionValues: Object.fromEntries(chosen) };
|
|
2040
|
+
}
|
|
2041
|
+
const unapplied = /* @__PURE__ */ new Map();
|
|
1346
2042
|
const visible = widgetsFor(definitions, context.roles);
|
|
1347
2043
|
let snapshot = loadingWidgets(visible);
|
|
1348
2044
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -1383,19 +2079,19 @@ function createWidgetLoader(options) {
|
|
|
1383
2079
|
const controller = new AbortController();
|
|
1384
2080
|
inFlight.set(key, controller);
|
|
1385
2081
|
const isCurrent = () => !disposed && generations.get(key) === generation;
|
|
1386
|
-
|
|
2082
|
+
const runOptions = currentOptions();
|
|
2083
|
+
const values = optionValuesFor(definition, runOptions);
|
|
2084
|
+
if (readCache && runOptions.cache !== void 0) {
|
|
1387
2085
|
try {
|
|
1388
|
-
const entry = await
|
|
1389
|
-
cacheKeyFor(definition,
|
|
2086
|
+
const entry = await runOptions.cache.get(
|
|
2087
|
+
cacheKeyFor(definition, runOptions, values)
|
|
1390
2088
|
);
|
|
1391
2089
|
if (entry !== void 0 && isCurrent()) {
|
|
1392
|
-
const cached = resolvePayload(
|
|
1393
|
-
definition,
|
|
1394
|
-
entry.payload,
|
|
1395
|
-
resolveOptions
|
|
1396
|
-
);
|
|
2090
|
+
const cached = resolvePayload(definition, entry.payload, runOptions);
|
|
1397
2091
|
if (cached.status === "ok") {
|
|
1398
|
-
|
|
2092
|
+
const stale = { ...cached, updatedAt: entry.storedAt, stale: true };
|
|
2093
|
+
unapplied.set(key, stale);
|
|
2094
|
+
replace(key, finalizeWidget(stale, values));
|
|
1399
2095
|
}
|
|
1400
2096
|
}
|
|
1401
2097
|
} catch {
|
|
@@ -1403,13 +2099,15 @@ function createWidgetLoader(options) {
|
|
|
1403
2099
|
}
|
|
1404
2100
|
if (!isCurrent()) return;
|
|
1405
2101
|
const provider = Object.prototype.hasOwnProperty.call(providers, key) ? providers[key] : void 0;
|
|
1406
|
-
const result = await
|
|
1407
|
-
...
|
|
2102
|
+
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
2103
|
+
...runOptions,
|
|
1408
2104
|
signal: controller.signal
|
|
1409
2105
|
});
|
|
1410
2106
|
if (!isCurrent()) return;
|
|
1411
2107
|
inFlight.delete(key);
|
|
1412
|
-
|
|
2108
|
+
if (result.status === "ok") unapplied.set(key, result);
|
|
2109
|
+
else unapplied.delete(key);
|
|
2110
|
+
replace(key, finalizeWidget(result, values));
|
|
1413
2111
|
}
|
|
1414
2112
|
function start(definition, readCache) {
|
|
1415
2113
|
started.add(definition.key);
|
|
@@ -1444,6 +2142,29 @@ function createWidgetLoader(options) {
|
|
|
1444
2142
|
start(definition, false);
|
|
1445
2143
|
}
|
|
1446
2144
|
}
|
|
2145
|
+
function setOptions(key, values) {
|
|
2146
|
+
if (disposed) return;
|
|
2147
|
+
const definition = definitionFor(key);
|
|
2148
|
+
if (definition === void 0) return;
|
|
2149
|
+
const before = optionValuesFor(definition, currentOptions());
|
|
2150
|
+
chosen.set(key, { ...values });
|
|
2151
|
+
const runOptions = currentOptions();
|
|
2152
|
+
const after = optionValuesFor(definition, runOptions);
|
|
2153
|
+
if (sameOptionValues(before, after) || !started.has(key)) return;
|
|
2154
|
+
const kept = unapplied.get(key);
|
|
2155
|
+
if (kept !== void 0 && optionsCacheSuffix(definition, before, runOptions) === optionsCacheSuffix(definition, after, runOptions)) {
|
|
2156
|
+
replace(key, finalizeWidget(kept, after));
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
const current = snapshot.find(
|
|
2160
|
+
(widget) => widget.definition.key === key
|
|
2161
|
+
);
|
|
2162
|
+
if (current?.status === "ok") replace(key, { ...current, stale: true });
|
|
2163
|
+
else if (current?.status === "error") {
|
|
2164
|
+
replace(key, { definition, status: "loading" });
|
|
2165
|
+
}
|
|
2166
|
+
start(definition, true);
|
|
2167
|
+
}
|
|
1447
2168
|
function dispose() {
|
|
1448
2169
|
disposed = true;
|
|
1449
2170
|
abortAll(new Error("The widget loader was disposed."));
|
|
@@ -1461,10 +2182,480 @@ function createWidgetLoader(options) {
|
|
|
1461
2182
|
load,
|
|
1462
2183
|
loadAll,
|
|
1463
2184
|
refresh,
|
|
2185
|
+
setOptions,
|
|
1464
2186
|
dispose
|
|
1465
2187
|
};
|
|
1466
2188
|
}
|
|
1467
2189
|
|
|
2190
|
+
// src/core/pages.ts
|
|
2191
|
+
var DEFAULT_PAGE_KEY = "default";
|
|
2192
|
+
var DEFAULT_PAGE_ROWS = 4;
|
|
2193
|
+
function rowsAllowed(page, options) {
|
|
2194
|
+
const rows = page.maxRows ?? options.maxRows ?? DEFAULT_PAGE_ROWS;
|
|
2195
|
+
return Math.max(1, Math.min(MAX_PAGE_ROWS, Math.floor(rows)));
|
|
2196
|
+
}
|
|
2197
|
+
function hasPages(layout) {
|
|
2198
|
+
return layout.pages !== void 0 && layout.pages.length > 0;
|
|
2199
|
+
}
|
|
2200
|
+
function pageList(layout) {
|
|
2201
|
+
if (layout.pages !== void 0 && layout.pages.length > 0) {
|
|
2202
|
+
return layout.pages;
|
|
2203
|
+
}
|
|
2204
|
+
return [
|
|
2205
|
+
{
|
|
2206
|
+
key: DEFAULT_PAGE_KEY,
|
|
2207
|
+
title: "Page 1",
|
|
2208
|
+
order: layout.order,
|
|
2209
|
+
hidden: layout.hidden,
|
|
2210
|
+
minimized: layout.minimized
|
|
2211
|
+
}
|
|
2212
|
+
];
|
|
2213
|
+
}
|
|
2214
|
+
function withExplicitPages(layout) {
|
|
2215
|
+
if (hasPages(layout)) return layout;
|
|
2216
|
+
return {
|
|
2217
|
+
...layout,
|
|
2218
|
+
order: [],
|
|
2219
|
+
hidden: [],
|
|
2220
|
+
minimized: [],
|
|
2221
|
+
pages: pageList(layout)
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
function sameKeys(a, b) {
|
|
2225
|
+
return a === b || a.length === b.length && a.every((k, i) => k === b[i]);
|
|
2226
|
+
}
|
|
2227
|
+
function pageLayout(layout, pageKey) {
|
|
2228
|
+
if (!hasPages(layout))
|
|
2229
|
+
return pageKey === DEFAULT_PAGE_KEY ? layout : void 0;
|
|
2230
|
+
const page = layout.pages?.find(
|
|
2231
|
+
(p) => p.key === pageKey
|
|
2232
|
+
);
|
|
2233
|
+
if (page === void 0) return void 0;
|
|
2234
|
+
const { pages, ...rest } = layout;
|
|
2235
|
+
return {
|
|
2236
|
+
...rest,
|
|
2237
|
+
order: page.order,
|
|
2238
|
+
hidden: page.hidden,
|
|
2239
|
+
minimized: page.minimized
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2242
|
+
function withPageLayout(layout, pageKey, next) {
|
|
2243
|
+
const current = pageLayout(layout, pageKey);
|
|
2244
|
+
if (current === void 0 || sameKeys(current.order, next.order) && sameKeys(current.hidden, next.hidden) && sameKeys(current.minimized, next.minimized)) {
|
|
2245
|
+
return layout;
|
|
2246
|
+
}
|
|
2247
|
+
if (!hasPages(layout)) {
|
|
2248
|
+
return {
|
|
2249
|
+
...layout,
|
|
2250
|
+
order: next.order,
|
|
2251
|
+
hidden: next.hidden,
|
|
2252
|
+
minimized: next.minimized
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
return {
|
|
2256
|
+
...layout,
|
|
2257
|
+
pages: (layout.pages ?? []).map(
|
|
2258
|
+
(page) => page.key === pageKey ? {
|
|
2259
|
+
...page,
|
|
2260
|
+
order: next.order,
|
|
2261
|
+
hidden: next.hidden,
|
|
2262
|
+
minimized: next.minimized
|
|
2263
|
+
} : page
|
|
2264
|
+
)
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
function normalizedTitle(title) {
|
|
2268
|
+
return title.trim().normalize("NFKC").toLowerCase();
|
|
2269
|
+
}
|
|
2270
|
+
function resolvePageKey(layout, reference) {
|
|
2271
|
+
if (reference === void 0 || reference === "") return void 0;
|
|
2272
|
+
const pages = pageList(layout);
|
|
2273
|
+
const byKey = pages.find((p) => p.key === reference);
|
|
2274
|
+
if (byKey !== void 0) return byKey.key;
|
|
2275
|
+
const wanted = normalizedTitle(reference);
|
|
2276
|
+
return pages.find((p) => normalizedTitle(p.title) === wanted)?.key;
|
|
2277
|
+
}
|
|
2278
|
+
function homePageKey(layout, item) {
|
|
2279
|
+
return resolvePageKey(layout, item.page) ?? pageList(layout)[0].key;
|
|
2280
|
+
}
|
|
2281
|
+
function assignPages(definitions, layout) {
|
|
2282
|
+
const pages = pageList(layout);
|
|
2283
|
+
const assigned = new Map(pages.map((p) => [p.key, []]));
|
|
2284
|
+
for (const definition of definitions) {
|
|
2285
|
+
let pageKey;
|
|
2286
|
+
if (!resolveWidgetLock(definition).move) {
|
|
2287
|
+
pageKey = pages.find(
|
|
2288
|
+
(p) => p.order.includes(definition.key) || p.hidden.includes(definition.key)
|
|
2289
|
+
)?.key;
|
|
2290
|
+
}
|
|
2291
|
+
assigned.get(pageKey ?? homePageKey(layout, definition))?.push(definition);
|
|
2292
|
+
}
|
|
2293
|
+
return assigned;
|
|
2294
|
+
}
|
|
2295
|
+
function pageOf(definitions, layout, key) {
|
|
2296
|
+
for (const [pageKey, widgets] of assignPages(definitions, layout)) {
|
|
2297
|
+
if (widgets.some((w) => w.key === key)) return pageKey;
|
|
2298
|
+
}
|
|
2299
|
+
return void 0;
|
|
2300
|
+
}
|
|
2301
|
+
function pageWidgets(definitions, layout, pageKey) {
|
|
2302
|
+
const onPage = assignPages(definitions, layout).get(pageKey) ?? [];
|
|
2303
|
+
const view = pageLayout(layout, pageKey);
|
|
2304
|
+
return view === void 0 ? [] : visibleWidgets(onPage, view);
|
|
2305
|
+
}
|
|
2306
|
+
function pageRoom(definitions, layout, pageKey, options = {}) {
|
|
2307
|
+
const page = pageList(layout).find(
|
|
2308
|
+
(p) => p.key === pageKey
|
|
2309
|
+
);
|
|
2310
|
+
if (page === void 0) return void 0;
|
|
2311
|
+
const maxRows = rowsAllowed(page, options);
|
|
2312
|
+
const rowsUsed = placeRows(
|
|
2313
|
+
pageWidgets(definitions, layout, pageKey)
|
|
2314
|
+
).length;
|
|
2315
|
+
return { maxRows, rowsUsed, rowsFree: Math.max(0, maxRows - rowsUsed) };
|
|
2316
|
+
}
|
|
2317
|
+
function describePage(page) {
|
|
2318
|
+
return `Page "${page.title}"`;
|
|
2319
|
+
}
|
|
2320
|
+
function titleOf(item) {
|
|
2321
|
+
return item.title ?? item.key;
|
|
2322
|
+
}
|
|
2323
|
+
function checkTitle(layout, title, exceptKey) {
|
|
2324
|
+
const trimmed = title.trim();
|
|
2325
|
+
if (trimmed === "") return err("A page title must not be empty.");
|
|
2326
|
+
if (trimmed.length > MAX_PAGE_TITLE_LENGTH) {
|
|
2327
|
+
return err(
|
|
2328
|
+
`Page title "${trimmed}" is ${trimmed.length} characters; the most is ${MAX_PAGE_TITLE_LENGTH}.`
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
const wanted = normalizedTitle(trimmed);
|
|
2332
|
+
const clash = pageList(layout).find(
|
|
2333
|
+
(p) => p.key !== exceptKey && normalizedTitle(p.title) === wanted
|
|
2334
|
+
);
|
|
2335
|
+
if (clash !== void 0) {
|
|
2336
|
+
return err(`Another page is already called "${clash.title}".`);
|
|
2337
|
+
}
|
|
2338
|
+
return ok(trimmed);
|
|
2339
|
+
}
|
|
2340
|
+
function nextPageKey(layout) {
|
|
2341
|
+
const keys = new Set(pageList(layout).map((p) => p.key));
|
|
2342
|
+
let n = 1;
|
|
2343
|
+
while (keys.has(`page-${n}`)) n += 1;
|
|
2344
|
+
return `page-${n}`;
|
|
2345
|
+
}
|
|
2346
|
+
function nextPageTitle(layout) {
|
|
2347
|
+
const used = new Set(
|
|
2348
|
+
pageList(layout).map((p) => normalizedTitle(p.title))
|
|
2349
|
+
);
|
|
2350
|
+
let n = pageList(layout).length + 1;
|
|
2351
|
+
while (used.has(normalizedTitle(`Page ${n}`))) n += 1;
|
|
2352
|
+
return `Page ${n}`;
|
|
2353
|
+
}
|
|
2354
|
+
function addPage(layout, options = {}) {
|
|
2355
|
+
if (pageList(layout).length >= MAX_PARSED_PAGES) {
|
|
2356
|
+
return err(
|
|
2357
|
+
`The layout already has ${MAX_PARSED_PAGES} pages, the most it can keep.`
|
|
2358
|
+
);
|
|
2359
|
+
}
|
|
2360
|
+
const key = options.key ?? nextPageKey(layout);
|
|
2361
|
+
if (pageList(layout).some((p) => p.key === key)) {
|
|
2362
|
+
return err(`A page with the key "${key}" already exists.`);
|
|
2363
|
+
}
|
|
2364
|
+
const title = checkTitle(
|
|
2365
|
+
layout,
|
|
2366
|
+
options.title ?? nextPageTitle(layout)
|
|
2367
|
+
);
|
|
2368
|
+
if (!title.ok) return title;
|
|
2369
|
+
const base = withExplicitPages(layout);
|
|
2370
|
+
const page = {
|
|
2371
|
+
key,
|
|
2372
|
+
title: title.value,
|
|
2373
|
+
order: [],
|
|
2374
|
+
hidden: [],
|
|
2375
|
+
minimized: []
|
|
2376
|
+
};
|
|
2377
|
+
return ok({ ...base, pages: [...base.pages ?? [], page] });
|
|
2378
|
+
}
|
|
2379
|
+
function renamePage(layout, pageKey, title) {
|
|
2380
|
+
const page = pageList(layout).find(
|
|
2381
|
+
(p) => p.key === pageKey
|
|
2382
|
+
);
|
|
2383
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2384
|
+
const checked = checkTitle(layout, title, pageKey);
|
|
2385
|
+
if (!checked.ok) return checked;
|
|
2386
|
+
if (checked.value === page.title) return ok(layout);
|
|
2387
|
+
const base = withExplicitPages(layout);
|
|
2388
|
+
return ok({
|
|
2389
|
+
...base,
|
|
2390
|
+
pages: (base.pages ?? []).map(
|
|
2391
|
+
(p) => p.key === pageKey ? { ...p, title: checked.value } : p
|
|
2392
|
+
)
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
function movePage(layout, pageKey, offset) {
|
|
2396
|
+
const pages = layout.pages ?? [];
|
|
2397
|
+
const from = pages.findIndex((p) => p.key === pageKey);
|
|
2398
|
+
if (from === -1) return layout;
|
|
2399
|
+
const to = Math.max(
|
|
2400
|
+
0,
|
|
2401
|
+
Math.min(pages.length - 1, from + Math.trunc(offset))
|
|
2402
|
+
);
|
|
2403
|
+
if (to === from) return layout;
|
|
2404
|
+
const next = [...pages];
|
|
2405
|
+
const [moved] = next.splice(from, 1);
|
|
2406
|
+
next.splice(to, 0, moved);
|
|
2407
|
+
return { ...layout, pages: next };
|
|
2408
|
+
}
|
|
2409
|
+
function overflowing(definitions, layout, options) {
|
|
2410
|
+
return pageList(layout).find(
|
|
2411
|
+
(page) => placeRows(pageWidgets(definitions, layout, page.key)).length > rowsAllowed(page, options)
|
|
2412
|
+
);
|
|
2413
|
+
}
|
|
2414
|
+
function removePage(definitions, layout, pageKey, options = {}) {
|
|
2415
|
+
const pages = pageList(layout);
|
|
2416
|
+
const index = pages.findIndex((p) => p.key === pageKey);
|
|
2417
|
+
const page = pages[index];
|
|
2418
|
+
if (page === void 0) return err(`Page "${pageKey}" doesn't exist.`);
|
|
2419
|
+
if (pages.length === 1) {
|
|
2420
|
+
return err(
|
|
2421
|
+
`${describePage(page)} is the only page, so it can't be removed.`
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
const target = pages[index > 0 ? index - 1 : 1];
|
|
2425
|
+
const merged = {
|
|
2426
|
+
...layout,
|
|
2427
|
+
pages: pages.filter((p) => p.key !== pageKey).map(
|
|
2428
|
+
(p) => p.key === target.key ? {
|
|
2429
|
+
...p,
|
|
2430
|
+
order: index > 0 ? [...p.order, ...page.order] : [...page.order, ...p.order],
|
|
2431
|
+
hidden: [...p.hidden, ...page.hidden],
|
|
2432
|
+
minimized: [...p.minimized, ...page.minimized]
|
|
2433
|
+
} : p
|
|
2434
|
+
)
|
|
2435
|
+
};
|
|
2436
|
+
const full = overflowing(
|
|
2437
|
+
definitions,
|
|
2438
|
+
merged,
|
|
2439
|
+
options
|
|
2440
|
+
);
|
|
2441
|
+
if (full !== void 0) {
|
|
2442
|
+
return err(
|
|
2443
|
+
`${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.`
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2446
|
+
return ok(merged);
|
|
2447
|
+
}
|
|
2448
|
+
function without(keys, key) {
|
|
2449
|
+
return keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
2450
|
+
}
|
|
2451
|
+
function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {}) {
|
|
2452
|
+
const widget = definitions.find((d) => d.key === widgetKey);
|
|
2453
|
+
if (widget === void 0) {
|
|
2454
|
+
return err(
|
|
2455
|
+
`Widget "${widgetKey}" isn't one of the widgets, so it can't be moved to a page.`
|
|
2456
|
+
);
|
|
2457
|
+
}
|
|
2458
|
+
const pages = pageList(layout);
|
|
2459
|
+
const target = pages.find((p) => p.key === targetKey);
|
|
2460
|
+
if (target === void 0) return err(`Page "${targetKey}" doesn't exist.`);
|
|
2461
|
+
const sourceKey = pageOf(definitions, layout, widgetKey);
|
|
2462
|
+
if (sourceKey === targetKey) return ok(layout);
|
|
2463
|
+
if (resolveWidgetLock(widget).move) {
|
|
2464
|
+
return err(
|
|
2465
|
+
`Widget "${titleOf(widget)}" is locked against moving, so it can't be moved to ${describePage(target)}.`
|
|
2466
|
+
);
|
|
2467
|
+
}
|
|
2468
|
+
const base = withExplicitPages(layout);
|
|
2469
|
+
const source = (base.pages ?? []).find(
|
|
2470
|
+
(p) => p.key === sourceKey
|
|
2471
|
+
);
|
|
2472
|
+
const hidden = source.hidden.includes(widgetKey);
|
|
2473
|
+
const minimized = source.minimized.includes(widgetKey);
|
|
2474
|
+
const targetOrder = pageWidgets(
|
|
2475
|
+
definitions,
|
|
2476
|
+
base,
|
|
2477
|
+
targetKey
|
|
2478
|
+
).map((d) => d.key);
|
|
2479
|
+
const moved = {
|
|
2480
|
+
...base,
|
|
2481
|
+
pages: (base.pages ?? []).map((p) => {
|
|
2482
|
+
if (p.key === sourceKey) {
|
|
2483
|
+
return {
|
|
2484
|
+
...p,
|
|
2485
|
+
order: without(p.order, widgetKey),
|
|
2486
|
+
hidden: without(p.hidden, widgetKey),
|
|
2487
|
+
minimized: without(p.minimized, widgetKey)
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
if (p.key === targetKey) {
|
|
2491
|
+
return {
|
|
2492
|
+
...p,
|
|
2493
|
+
order: hidden ? p.order : [...targetOrder, widgetKey],
|
|
2494
|
+
hidden: hidden ? [...p.hidden, widgetKey] : p.hidden,
|
|
2495
|
+
minimized: minimized ? [...p.minimized, widgetKey] : p.minimized
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
return p;
|
|
2499
|
+
})
|
|
2500
|
+
};
|
|
2501
|
+
if (!hidden) {
|
|
2502
|
+
const allowed = rowsAllowed(target, options);
|
|
2503
|
+
const needed = placeRows(
|
|
2504
|
+
pageWidgets(definitions, moved, targetKey)
|
|
2505
|
+
).length;
|
|
2506
|
+
if (needed > allowed) {
|
|
2507
|
+
return err(
|
|
2508
|
+
`${describePage(target)} has no room for "${titleOf(widget)}": it would need row ${needed}, and the page allows ${allowed}.`
|
|
2509
|
+
);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return ok(moved);
|
|
2513
|
+
}
|
|
2514
|
+
function withoutLockField(definition) {
|
|
2515
|
+
if (definition.locked === void 0) return definition;
|
|
2516
|
+
const copy = { ...definition };
|
|
2517
|
+
delete copy.locked;
|
|
2518
|
+
return copy;
|
|
2519
|
+
}
|
|
2520
|
+
function uniqueTitle(taken, title, index) {
|
|
2521
|
+
let base = title.trim().slice(0, MAX_PAGE_TITLE_LENGTH);
|
|
2522
|
+
if (base === "") base = `Page ${index + 1}`;
|
|
2523
|
+
let candidate = base;
|
|
2524
|
+
let n = 1;
|
|
2525
|
+
while (taken.has(normalizedTitle(candidate))) {
|
|
2526
|
+
const suffix = ` (#${n})`;
|
|
2527
|
+
candidate = `${base.slice(0, MAX_PAGE_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
2528
|
+
n += 1;
|
|
2529
|
+
}
|
|
2530
|
+
taken.add(normalizedTitle(candidate));
|
|
2531
|
+
return candidate;
|
|
2532
|
+
}
|
|
2533
|
+
function samePages(a, b) {
|
|
2534
|
+
return a.length === b.length && a.every((p, i) => {
|
|
2535
|
+
const q = b[i];
|
|
2536
|
+
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);
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
function normalizeLayout(definitions, layout, options = {}) {
|
|
2540
|
+
const known = pruneLayout(definitions, layout);
|
|
2541
|
+
const locked = enforceLocks(definitions, known, options);
|
|
2542
|
+
if (!hasPages(locked)) return locked === layout ? layout : locked;
|
|
2543
|
+
const defs = options.overrideLocks === true ? definitions.map(withoutLockField) : definitions;
|
|
2544
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2545
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
2546
|
+
const held = /* @__PURE__ */ new Set();
|
|
2547
|
+
let pages = [];
|
|
2548
|
+
(locked.pages ?? []).forEach((page, index) => {
|
|
2549
|
+
if (seenKeys.has(page.key)) return;
|
|
2550
|
+
seenKeys.add(page.key);
|
|
2551
|
+
const keep = (list) => [
|
|
2552
|
+
...new Set(list.filter((k) => !held.has(k)))
|
|
2553
|
+
];
|
|
2554
|
+
const next = {
|
|
2555
|
+
...page,
|
|
2556
|
+
title: uniqueTitle(taken, page.title, index),
|
|
2557
|
+
order: keep(page.order),
|
|
2558
|
+
hidden: keep(page.hidden),
|
|
2559
|
+
minimized: keep(page.minimized)
|
|
2560
|
+
};
|
|
2561
|
+
for (const k of [...next.order, ...next.hidden]) held.add(k);
|
|
2562
|
+
pages.push(next);
|
|
2563
|
+
});
|
|
2564
|
+
const work = () => ({ ...locked, pages });
|
|
2565
|
+
const pinned = defs.filter((d) => resolveWidgetLock(d).move);
|
|
2566
|
+
for (const widget of pinned) {
|
|
2567
|
+
const home = homePageKey(work(), widget);
|
|
2568
|
+
const keysHere = (p, list) => p.key !== home && p[list].includes(widget.key);
|
|
2569
|
+
const wasHidden = pages.some((p) => keysHere(p, "hidden"));
|
|
2570
|
+
const wasMinimized = pages.some((p) => keysHere(p, "minimized"));
|
|
2571
|
+
if (!wasHidden && !wasMinimized) continue;
|
|
2572
|
+
pages = pages.map((p) => {
|
|
2573
|
+
let next = p;
|
|
2574
|
+
if (keysHere(p, "hidden")) {
|
|
2575
|
+
next = { ...next, hidden: without(next.hidden, widget.key) };
|
|
2576
|
+
}
|
|
2577
|
+
if (keysHere(p, "minimized")) {
|
|
2578
|
+
next = { ...next, minimized: without(next.minimized, widget.key) };
|
|
2579
|
+
}
|
|
2580
|
+
if (p.key === home) {
|
|
2581
|
+
next = {
|
|
2582
|
+
...next,
|
|
2583
|
+
hidden: wasHidden && !next.hidden.includes(widget.key) ? [...next.hidden, widget.key] : next.hidden,
|
|
2584
|
+
minimized: wasMinimized && !next.minimized.includes(widget.key) ? [...next.minimized, widget.key] : next.minimized
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
return next;
|
|
2588
|
+
});
|
|
2589
|
+
}
|
|
2590
|
+
const placedKeys = /* @__PURE__ */ new Set();
|
|
2591
|
+
for (const p of pages) {
|
|
2592
|
+
for (const k of [...p.order, ...p.hidden]) placedKeys.add(k);
|
|
2593
|
+
}
|
|
2594
|
+
const pinnedKeys = new Set(pinned.map((d) => d.key));
|
|
2595
|
+
const unplaced = defs.filter((d) => !placedKeys.has(d.key) && !pinnedKeys.has(d.key)).map((definition, index) => ({ definition, index })).sort(
|
|
2596
|
+
(a, b) => (a.definition.sortOrder ?? 0) - (b.definition.sortOrder ?? 0) || a.index - b.index
|
|
2597
|
+
).map(({ definition }) => definition);
|
|
2598
|
+
for (const widget of unplaced) {
|
|
2599
|
+
const home = homePageKey(work(), widget);
|
|
2600
|
+
pages = pages.map(
|
|
2601
|
+
(p) => p.key === home ? { ...p, order: [...p.order, widget.key] } : p
|
|
2602
|
+
);
|
|
2603
|
+
}
|
|
2604
|
+
for (let i = 0; i < pages.length; i += 1) {
|
|
2605
|
+
const page = pages[i];
|
|
2606
|
+
const shown = pageWidgets(defs, work(), page.key);
|
|
2607
|
+
const fits = fitCount(shown, rowsAllowed(page, options));
|
|
2608
|
+
if (fits >= shown.length) continue;
|
|
2609
|
+
const moving = shown.slice(fits).filter((d) => !pinnedKeys.has(d.key)).map((d) => d.key);
|
|
2610
|
+
if (moving.length === 0) continue;
|
|
2611
|
+
const moveSet = new Set(moving);
|
|
2612
|
+
const movedMinimized = page.minimized.filter(
|
|
2613
|
+
(k) => moveSet.has(k)
|
|
2614
|
+
);
|
|
2615
|
+
pages = pages.map(
|
|
2616
|
+
(p) => p.key === page.key ? {
|
|
2617
|
+
...p,
|
|
2618
|
+
order: p.order.filter((k) => !moveSet.has(k)),
|
|
2619
|
+
minimized: p.minimized.filter((k) => !moveSet.has(k))
|
|
2620
|
+
} : p
|
|
2621
|
+
);
|
|
2622
|
+
let placed = false;
|
|
2623
|
+
for (let j = i + 1; j < pages.length && !placed; j += 1) {
|
|
2624
|
+
const candidate = pages[j];
|
|
2625
|
+
const trial = pages.map(
|
|
2626
|
+
(p) => p.key === candidate.key ? {
|
|
2627
|
+
...p,
|
|
2628
|
+
order: [...moving, ...p.order],
|
|
2629
|
+
minimized: [...p.minimized, ...movedMinimized]
|
|
2630
|
+
} : p
|
|
2631
|
+
);
|
|
2632
|
+
const items = pageWidgets(
|
|
2633
|
+
defs,
|
|
2634
|
+
{ ...locked, pages: trial },
|
|
2635
|
+
candidate.key
|
|
2636
|
+
);
|
|
2637
|
+
if (placeRows(items).length <= rowsAllowed(candidate, options)) {
|
|
2638
|
+
pages = trial;
|
|
2639
|
+
placed = true;
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
if (!placed) {
|
|
2643
|
+
const key = nextPageKey(work());
|
|
2644
|
+
const title = uniqueTitle(
|
|
2645
|
+
new Set(pages.map((p) => normalizedTitle(p.title))),
|
|
2646
|
+
`Page ${pages.length + 1}`,
|
|
2647
|
+
pages.length
|
|
2648
|
+
);
|
|
2649
|
+
pages = [
|
|
2650
|
+
...pages,
|
|
2651
|
+
{ key, title, order: moving, hidden: [], minimized: movedMinimized }
|
|
2652
|
+
];
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
const result = work();
|
|
2656
|
+
return samePages(layout.pages ?? [], pages) && sameKeys(layout.order, result.order) ? layout : result;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
1468
2659
|
// src/core/scale.ts
|
|
1469
2660
|
function niceStep(value) {
|
|
1470
2661
|
if (!(value > 0) || !Number.isFinite(value)) return 1;
|
|
@@ -1495,26 +2686,594 @@ function labelStride(count, maxLabels = 6) {
|
|
|
1495
2686
|
return Math.max(1, Math.ceil(count / Math.max(1, maxLabels)));
|
|
1496
2687
|
}
|
|
1497
2688
|
|
|
2689
|
+
// src/core/storage.ts
|
|
2690
|
+
function failure(code, error) {
|
|
2691
|
+
return { ok: false, code, error };
|
|
2692
|
+
}
|
|
2693
|
+
function utf8Length(text) {
|
|
2694
|
+
let bytes = 0;
|
|
2695
|
+
for (const char of text) {
|
|
2696
|
+
const point = char.codePointAt(0) ?? 0;
|
|
2697
|
+
bytes += point < 128 ? 1 : point < 2048 ? 2 : point < 65536 ? 3 : 4;
|
|
2698
|
+
}
|
|
2699
|
+
return bytes;
|
|
2700
|
+
}
|
|
2701
|
+
function memoryAdapter(initial = {}) {
|
|
2702
|
+
const entries = /* @__PURE__ */ new Map();
|
|
2703
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
2704
|
+
let counter = 0;
|
|
2705
|
+
for (const [key, value] of Object.entries(initial)) {
|
|
2706
|
+
counter += 1;
|
|
2707
|
+
entries.set(key, { value, revision: String(counter) });
|
|
2708
|
+
}
|
|
2709
|
+
const notify = (key, stored) => {
|
|
2710
|
+
for (const listener of listeners.get(key) ?? []) listener(stored);
|
|
2711
|
+
};
|
|
2712
|
+
return {
|
|
2713
|
+
get: (key) => Promise.resolve({ ok: true, value: entries.get(key) ?? null }),
|
|
2714
|
+
set: (key, value, options) => {
|
|
2715
|
+
const current = entries.get(key);
|
|
2716
|
+
if (options.ifRevision !== void 0 && (current?.revision ?? void 0) !== options.ifRevision) {
|
|
2717
|
+
return Promise.resolve(
|
|
2718
|
+
failure(
|
|
2719
|
+
"conflict",
|
|
2720
|
+
`Could not save "${key}": it changed since revision ${options.ifRevision} (now ${current?.revision ?? "none"}).`
|
|
2721
|
+
)
|
|
2722
|
+
);
|
|
2723
|
+
}
|
|
2724
|
+
counter += 1;
|
|
2725
|
+
const stored = { value, revision: String(counter) };
|
|
2726
|
+
entries.set(key, stored);
|
|
2727
|
+
notify(key, stored);
|
|
2728
|
+
return Promise.resolve({ ok: true, value: stored });
|
|
2729
|
+
},
|
|
2730
|
+
remove: (key) => {
|
|
2731
|
+
entries.delete(key);
|
|
2732
|
+
notify(key, null);
|
|
2733
|
+
return Promise.resolve({ ok: true, value: null });
|
|
2734
|
+
},
|
|
2735
|
+
subscribe: (key, onChange) => {
|
|
2736
|
+
const set = listeners.get(key) ?? /* @__PURE__ */ new Set();
|
|
2737
|
+
set.add(onChange);
|
|
2738
|
+
listeners.set(key, set);
|
|
2739
|
+
return () => {
|
|
2740
|
+
set.delete(onChange);
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
function withPrefix(adapter, prefix) {
|
|
2746
|
+
return {
|
|
2747
|
+
get: (key, options) => adapter.get(prefix + key, options),
|
|
2748
|
+
set: (key, value, options) => adapter.set(prefix + key, value, options),
|
|
2749
|
+
...adapter.remove === void 0 ? {} : {
|
|
2750
|
+
remove: (key, options) => adapter.remove?.(prefix + key, options) ?? Promise.resolve(failure("invalid", "remove is not supported."))
|
|
2751
|
+
},
|
|
2752
|
+
...adapter.subscribe === void 0 ? {} : {
|
|
2753
|
+
subscribe: (key, onChange) => adapter.subscribe?.(prefix + key, onChange) ?? (() => void 0)
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
function withFallback(primary, secondary) {
|
|
2758
|
+
return {
|
|
2759
|
+
async get(key, options) {
|
|
2760
|
+
const first = await primary.get(key, options);
|
|
2761
|
+
if (first.ok && first.value !== null) return first;
|
|
2762
|
+
const second = await secondary.get(key, options);
|
|
2763
|
+
if (second.ok && second.value !== null) return second;
|
|
2764
|
+
return first;
|
|
2765
|
+
},
|
|
2766
|
+
async set(key, value, options) {
|
|
2767
|
+
const first = await primary.set(key, value, options);
|
|
2768
|
+
if (!first.ok && first.code === "conflict") return first;
|
|
2769
|
+
const second = await secondary.set(key, value, {
|
|
2770
|
+
signal: options.signal
|
|
2771
|
+
});
|
|
2772
|
+
if (first.ok) return first;
|
|
2773
|
+
return second.ok ? second : first;
|
|
2774
|
+
},
|
|
2775
|
+
async remove(key, options) {
|
|
2776
|
+
const first = await primary.remove?.(key, options) ?? {
|
|
2777
|
+
ok: true,
|
|
2778
|
+
value: null
|
|
2779
|
+
};
|
|
2780
|
+
await secondary.remove?.(key, options);
|
|
2781
|
+
return first;
|
|
2782
|
+
},
|
|
2783
|
+
subscribe: (key, onChange) => {
|
|
2784
|
+
const stops = [primary, secondary].flatMap((adapter) => {
|
|
2785
|
+
const stop = adapter.subscribe?.(
|
|
2786
|
+
key,
|
|
2787
|
+
onChange
|
|
2788
|
+
);
|
|
2789
|
+
return stop === void 0 ? [] : [stop];
|
|
2790
|
+
});
|
|
2791
|
+
return () => {
|
|
2792
|
+
for (const stop of stops) stop();
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
function withReadCache(adapter) {
|
|
2798
|
+
const cache = /* @__PURE__ */ new Map();
|
|
2799
|
+
return {
|
|
2800
|
+
async get(key, options) {
|
|
2801
|
+
const cached = cache.get(key);
|
|
2802
|
+
if (cached !== void 0) return { ok: true, value: cached };
|
|
2803
|
+
const result = await adapter.get(key, options);
|
|
2804
|
+
if (result.ok) cache.set(key, result.value);
|
|
2805
|
+
return result;
|
|
2806
|
+
},
|
|
2807
|
+
async set(key, value, options) {
|
|
2808
|
+
const result = await adapter.set(key, value, options);
|
|
2809
|
+
if (result.ok) cache.set(key, result.value);
|
|
2810
|
+
else cache.delete(key);
|
|
2811
|
+
return result;
|
|
2812
|
+
},
|
|
2813
|
+
async remove(key, options) {
|
|
2814
|
+
cache.delete(key);
|
|
2815
|
+
return await adapter.remove?.(key, options) ?? failure(
|
|
2816
|
+
"invalid",
|
|
2817
|
+
`Could not remove "${key}": remove is not supported.`
|
|
2818
|
+
);
|
|
2819
|
+
},
|
|
2820
|
+
...adapter.subscribe === void 0 ? {} : {
|
|
2821
|
+
subscribe: (key, onChange) => adapter.subscribe?.(key, (stored) => {
|
|
2822
|
+
cache.set(key, stored);
|
|
2823
|
+
onChange(stored);
|
|
2824
|
+
}) ?? (() => void 0)
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
function readOnly(adapter) {
|
|
2829
|
+
return {
|
|
2830
|
+
get: (key, options) => adapter.get(key, options),
|
|
2831
|
+
set: (key) => Promise.resolve(
|
|
2832
|
+
failure(
|
|
2833
|
+
"forbidden",
|
|
2834
|
+
`Could not save "${key}": this store is read-only.`
|
|
2835
|
+
)
|
|
2836
|
+
),
|
|
2837
|
+
remove: (key) => Promise.resolve(
|
|
2838
|
+
failure(
|
|
2839
|
+
"forbidden",
|
|
2840
|
+
`Could not remove "${key}": this store is read-only.`
|
|
2841
|
+
)
|
|
2842
|
+
),
|
|
2843
|
+
...adapter.subscribe === void 0 ? {} : { subscribe: adapter.subscribe.bind(adapter) }
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
function withRetry(adapter, options) {
|
|
2847
|
+
const attempts = Math.max(1, Math.floor(options.attempts ?? 3));
|
|
2848
|
+
const baseDelay = options.delayMs ?? 200;
|
|
2849
|
+
const retrying = async (signal, run) => {
|
|
2850
|
+
let result = await run();
|
|
2851
|
+
for (let attempt = 1; attempt < attempts && !result.ok && result.code === "unavailable" && !signal.aborted; attempt += 1) {
|
|
2852
|
+
await options.sleep(baseDelay * 2 ** (attempt - 1), signal);
|
|
2853
|
+
result = await run();
|
|
2854
|
+
}
|
|
2855
|
+
return result;
|
|
2856
|
+
};
|
|
2857
|
+
return {
|
|
2858
|
+
get: (key, o) => retrying(o.signal, () => adapter.get(key, o)),
|
|
2859
|
+
set: (key, value, o) => retrying(o.signal, () => adapter.set(key, value, o)),
|
|
2860
|
+
...adapter.remove === void 0 ? {} : {
|
|
2861
|
+
remove: (key, o) => retrying(
|
|
2862
|
+
o.signal,
|
|
2863
|
+
() => adapter.remove?.(key, o) ?? Promise.resolve(failure("invalid", "remove is not supported."))
|
|
2864
|
+
)
|
|
2865
|
+
},
|
|
2866
|
+
...adapter.subscribe === void 0 ? {} : { subscribe: adapter.subscribe.bind(adapter) }
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
function withEncoding(adapter, codec) {
|
|
2870
|
+
const decodeStored = async (key, stored) => {
|
|
2871
|
+
try {
|
|
2872
|
+
const value = await codec.decode(stored.value);
|
|
2873
|
+
return {
|
|
2874
|
+
ok: true,
|
|
2875
|
+
value: stored.revision === void 0 ? { value } : { value, revision: stored.revision }
|
|
2876
|
+
};
|
|
2877
|
+
} catch (cause) {
|
|
2878
|
+
return failure(
|
|
2879
|
+
"corrupt",
|
|
2880
|
+
`Could not decode the value stored at "${key}": ${describeUnknownError(cause)}`
|
|
2881
|
+
);
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
return {
|
|
2885
|
+
async get(key, options) {
|
|
2886
|
+
const result = await adapter.get(key, options);
|
|
2887
|
+
if (!result.ok || result.value === null) return result;
|
|
2888
|
+
return decodeStored(key, result.value);
|
|
2889
|
+
},
|
|
2890
|
+
async set(key, value, options) {
|
|
2891
|
+
let encoded;
|
|
2892
|
+
try {
|
|
2893
|
+
encoded = await codec.encode(value);
|
|
2894
|
+
} catch (cause) {
|
|
2895
|
+
return failure(
|
|
2896
|
+
"invalid",
|
|
2897
|
+
`Could not encode the value for "${key}": ${describeUnknownError(cause)}`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
const result = await adapter.set(key, encoded, options);
|
|
2901
|
+
if (!result.ok) return result;
|
|
2902
|
+
return result.value.revision === void 0 ? { ok: true, value: { value } } : { ok: true, value: { value, revision: result.value.revision } };
|
|
2903
|
+
},
|
|
2904
|
+
...adapter.remove === void 0 ? {} : { remove: adapter.remove.bind(adapter) },
|
|
2905
|
+
...adapter.subscribe === void 0 ? {} : {
|
|
2906
|
+
subscribe: (key, onChange) => adapter.subscribe?.(key, (stored) => {
|
|
2907
|
+
if (stored === null) {
|
|
2908
|
+
onChange(null);
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
void decodeStored(key, stored).then((decoded) => {
|
|
2912
|
+
if (decoded.ok) onChange(decoded.value);
|
|
2913
|
+
});
|
|
2914
|
+
}) ?? (() => void 0)
|
|
2915
|
+
}
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
function withLogging(adapter, log) {
|
|
2919
|
+
const report = (operation, key, result) => {
|
|
2920
|
+
try {
|
|
2921
|
+
log(
|
|
2922
|
+
result.ok ? { operation, key, ok: true } : { operation, key, ok: false, code: result.code }
|
|
2923
|
+
);
|
|
2924
|
+
} catch {
|
|
2925
|
+
}
|
|
2926
|
+
return result;
|
|
2927
|
+
};
|
|
2928
|
+
return {
|
|
2929
|
+
get: async (key, o) => report("get", key, await adapter.get(key, o)),
|
|
2930
|
+
set: async (key, value, o) => report("set", key, await adapter.set(key, value, o)),
|
|
2931
|
+
...adapter.remove === void 0 ? {} : {
|
|
2932
|
+
remove: async (key, o) => report(
|
|
2933
|
+
"remove",
|
|
2934
|
+
key,
|
|
2935
|
+
await adapter.remove?.(key, o) ?? failure("invalid", "remove is not supported.")
|
|
2936
|
+
)
|
|
2937
|
+
},
|
|
2938
|
+
...adapter.subscribe === void 0 ? {} : { subscribe: adapter.subscribe.bind(adapter) }
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
var LAYOUT_ENVELOPE_VERSION = 1;
|
|
2942
|
+
var DEFAULT_MAX_LAYOUT_BYTES = 64 * 1024;
|
|
2943
|
+
function keyPart(text) {
|
|
2944
|
+
return encodeURIComponent(text);
|
|
2945
|
+
}
|
|
2946
|
+
function readEnvelope(key, stored) {
|
|
2947
|
+
let parsed;
|
|
2948
|
+
try {
|
|
2949
|
+
parsed = JSON.parse(stored.value);
|
|
2950
|
+
} catch (cause) {
|
|
2951
|
+
return failure(
|
|
2952
|
+
"corrupt",
|
|
2953
|
+
`The layout stored at "${key}" is not valid JSON: ${describeUnknownError(cause)}`
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2957
|
+
return failure(
|
|
2958
|
+
"corrupt",
|
|
2959
|
+
`The layout stored at "${key}" is not an object.`
|
|
2960
|
+
);
|
|
2961
|
+
}
|
|
2962
|
+
const record = parsed;
|
|
2963
|
+
const version = record["v"];
|
|
2964
|
+
if (version === void 0) {
|
|
2965
|
+
return { ok: true, value: { layout: parseLayout(parsed) } };
|
|
2966
|
+
}
|
|
2967
|
+
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
2968
|
+
return failure(
|
|
2969
|
+
"corrupt",
|
|
2970
|
+
`The layout stored at "${key}" has an invalid version ${JSON.stringify(version)}.`
|
|
2971
|
+
);
|
|
2972
|
+
}
|
|
2973
|
+
if (version > LAYOUT_ENVELOPE_VERSION) {
|
|
2974
|
+
return failure(
|
|
2975
|
+
"invalid",
|
|
2976
|
+
`The layout stored at "${key}" is version ${version}, newer than this toolkit reads (${LAYOUT_ENVELOPE_VERSION}). Update the package.`
|
|
2977
|
+
);
|
|
2978
|
+
}
|
|
2979
|
+
const envelope = record;
|
|
2980
|
+
return {
|
|
2981
|
+
ok: true,
|
|
2982
|
+
value: {
|
|
2983
|
+
layout: parseLayout(envelope.layout),
|
|
2984
|
+
...typeof envelope.savedAt === "string" ? { savedAt: envelope.savedAt } : {}
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
function createLayoutPersistence(adapter, options = {}) {
|
|
2989
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_LAYOUT_BYTES;
|
|
2990
|
+
const policy = options.onConflict ?? "ask";
|
|
2991
|
+
const fallbackSignal = new AbortController().signal;
|
|
2992
|
+
const signal = options.signal ?? fallbackSignal;
|
|
2993
|
+
const now = options.now ?? Date.now;
|
|
2994
|
+
const revisions = /* @__PURE__ */ new Map();
|
|
2995
|
+
const lastWritten = /* @__PURE__ */ new Map();
|
|
2996
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
2997
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2998
|
+
const keyFor = (scope, part = "layout") => `dwt:${keyPart(scope.dashboardKey)}:${keyPart(scope.userKey ?? "_")}:${part}`;
|
|
2999
|
+
const checkScope = (scope) => scope.dashboardKey.trim() === "" ? failure("invalid", "The storage scope needs a non-empty dashboardKey.") : { ok: true, value: null };
|
|
3000
|
+
const encode = (layout, key) => {
|
|
3001
|
+
const envelope = {
|
|
3002
|
+
v: LAYOUT_ENVELOPE_VERSION,
|
|
3003
|
+
savedAt: new Date(now()).toISOString(),
|
|
3004
|
+
layout: JSON.parse(serializeLayout(layout))
|
|
3005
|
+
};
|
|
3006
|
+
const text = JSON.stringify(envelope);
|
|
3007
|
+
const size = utf8Length(text);
|
|
3008
|
+
if (size > maxBytes) {
|
|
3009
|
+
return failure(
|
|
3010
|
+
"invalid",
|
|
3011
|
+
`Could not save "${key}": the layout is ${size} bytes, over the ${maxBytes} byte limit.`
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
return { ok: true, value: text };
|
|
3015
|
+
};
|
|
3016
|
+
const guarded = async (key, action, run) => {
|
|
3017
|
+
try {
|
|
3018
|
+
return await run();
|
|
3019
|
+
} catch (cause) {
|
|
3020
|
+
return failure(
|
|
3021
|
+
"unavailable",
|
|
3022
|
+
`Could not ${action} "${key}": the adapter threw ${describeUnknownError(cause)}`
|
|
3023
|
+
);
|
|
3024
|
+
}
|
|
3025
|
+
};
|
|
3026
|
+
const readLayout = async (scope, part) => {
|
|
3027
|
+
const key = keyFor(scope, part);
|
|
3028
|
+
const result = await guarded(
|
|
3029
|
+
key,
|
|
3030
|
+
"load",
|
|
3031
|
+
() => adapter.get(key, { signal })
|
|
3032
|
+
);
|
|
3033
|
+
if (!result.ok) return result;
|
|
3034
|
+
if (result.value === null) {
|
|
3035
|
+
if (part === "layout") revisions.delete(key);
|
|
3036
|
+
return { ok: true, value: { layout: null } };
|
|
3037
|
+
}
|
|
3038
|
+
const read = readEnvelope(key, result.value);
|
|
3039
|
+
if (!read.ok) return read;
|
|
3040
|
+
if (part === "layout" && result.value.revision !== void 0) {
|
|
3041
|
+
revisions.set(key, result.value.revision);
|
|
3042
|
+
}
|
|
3043
|
+
return {
|
|
3044
|
+
ok: true,
|
|
3045
|
+
value: {
|
|
3046
|
+
layout: read.value.layout,
|
|
3047
|
+
...result.value.revision === void 0 ? {} : { revision: result.value.revision },
|
|
3048
|
+
...read.value.savedAt === void 0 ? {} : { savedAt: read.value.savedAt }
|
|
3049
|
+
}
|
|
3050
|
+
};
|
|
3051
|
+
};
|
|
3052
|
+
const repair = (loaded, definitions, normalizeOptions) => loaded.layout === null ? loaded : {
|
|
3053
|
+
...loaded,
|
|
3054
|
+
layout: normalizeLayout(definitions, loaded.layout, normalizeOptions)
|
|
3055
|
+
};
|
|
3056
|
+
const write = async (scope, layout, force) => {
|
|
3057
|
+
const key = keyFor(scope);
|
|
3058
|
+
const text = encode(layout, key);
|
|
3059
|
+
if (!text.ok) return text;
|
|
3060
|
+
const known = revisions.get(key);
|
|
3061
|
+
lastWritten.set(key, text.value);
|
|
3062
|
+
const stored = await guarded(
|
|
3063
|
+
key,
|
|
3064
|
+
"save",
|
|
3065
|
+
() => adapter.set(key, text.value, {
|
|
3066
|
+
signal,
|
|
3067
|
+
...force || known === void 0 ? {} : { ifRevision: known }
|
|
3068
|
+
})
|
|
3069
|
+
);
|
|
3070
|
+
if (stored.ok) {
|
|
3071
|
+
if (stored.value.revision !== void 0) {
|
|
3072
|
+
revisions.set(key, stored.value.revision);
|
|
3073
|
+
}
|
|
3074
|
+
return {
|
|
3075
|
+
ok: true,
|
|
3076
|
+
value: {
|
|
3077
|
+
outcome: "saved",
|
|
3078
|
+
layout,
|
|
3079
|
+
...stored.value.revision === void 0 ? {} : { revision: stored.value.revision }
|
|
3080
|
+
}
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
if (stored.code !== "conflict") return stored;
|
|
3084
|
+
const theirs = await readLayout(scope, "layout");
|
|
3085
|
+
if (!theirs.ok) return theirs;
|
|
3086
|
+
const theirLayout = theirs.value.layout ?? layout;
|
|
3087
|
+
const theirRevision = theirs.value.revision;
|
|
3088
|
+
const asResult = (outcome) => ({
|
|
3089
|
+
ok: true,
|
|
3090
|
+
value: {
|
|
3091
|
+
outcome,
|
|
3092
|
+
layout: theirLayout,
|
|
3093
|
+
...theirRevision === void 0 ? {} : { revision: theirRevision }
|
|
3094
|
+
}
|
|
3095
|
+
});
|
|
3096
|
+
if (policy === "overwrite") return write(scope, layout, true);
|
|
3097
|
+
if (policy === "keep-theirs") return asResult("kept-theirs");
|
|
3098
|
+
if (policy === "ask") return asResult("conflict");
|
|
3099
|
+
let merged;
|
|
3100
|
+
try {
|
|
3101
|
+
merged = policy(layout, theirLayout);
|
|
3102
|
+
} catch (cause) {
|
|
3103
|
+
return failure(
|
|
3104
|
+
"invalid",
|
|
3105
|
+
`Could not save "${key}": the conflict merge function threw ${describeUnknownError(cause)}`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
const second = await write(scope, merged, false);
|
|
3109
|
+
if (second.ok && second.value.outcome === "saved") return second;
|
|
3110
|
+
return second.ok ? asResult("conflict") : second;
|
|
3111
|
+
};
|
|
3112
|
+
const drain = async (scope, key, first) => {
|
|
3113
|
+
let job = first;
|
|
3114
|
+
inFlight.add(key);
|
|
3115
|
+
while (job !== void 0) {
|
|
3116
|
+
const current = job;
|
|
3117
|
+
pending.delete(key);
|
|
3118
|
+
const result = await write(
|
|
3119
|
+
scope,
|
|
3120
|
+
current.layout,
|
|
3121
|
+
current.force
|
|
3122
|
+
);
|
|
3123
|
+
for (const waiter of current.waiters) waiter(result);
|
|
3124
|
+
job = pending.get(key);
|
|
3125
|
+
}
|
|
3126
|
+
inFlight.delete(key);
|
|
3127
|
+
};
|
|
3128
|
+
return {
|
|
3129
|
+
keyFor,
|
|
3130
|
+
async load(scope, definitions, normalizeOptions) {
|
|
3131
|
+
const valid = checkScope(scope);
|
|
3132
|
+
if (!valid.ok) return valid;
|
|
3133
|
+
const read = await readLayout(scope, "layout");
|
|
3134
|
+
if (!read.ok) return read;
|
|
3135
|
+
return {
|
|
3136
|
+
ok: true,
|
|
3137
|
+
value: repair(read.value, definitions, normalizeOptions)
|
|
3138
|
+
};
|
|
3139
|
+
},
|
|
3140
|
+
save(scope, layout, saveOptions = {}) {
|
|
3141
|
+
const valid = checkScope(scope);
|
|
3142
|
+
if (!valid.ok) return Promise.resolve(valid);
|
|
3143
|
+
const key = keyFor(scope);
|
|
3144
|
+
const force = saveOptions.force === true;
|
|
3145
|
+
return new Promise((resolve) => {
|
|
3146
|
+
const queued = pending.get(key);
|
|
3147
|
+
if (queued !== void 0) {
|
|
3148
|
+
queued.layout = layout;
|
|
3149
|
+
queued.force = queued.force || force;
|
|
3150
|
+
queued.waiters.push(resolve);
|
|
3151
|
+
return;
|
|
3152
|
+
}
|
|
3153
|
+
const job = { layout, force, waiters: [resolve] };
|
|
3154
|
+
if (inFlight.has(key)) {
|
|
3155
|
+
pending.set(key, job);
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
void drain(scope, key, job);
|
|
3159
|
+
});
|
|
3160
|
+
},
|
|
3161
|
+
async loadBackup(scope) {
|
|
3162
|
+
const valid = checkScope(scope);
|
|
3163
|
+
if (!valid.ok) return valid;
|
|
3164
|
+
const read = await readLayout(scope, "backup");
|
|
3165
|
+
return read.ok ? { ok: true, value: read.value.layout } : read;
|
|
3166
|
+
},
|
|
3167
|
+
async saveBackup(scope, layout) {
|
|
3168
|
+
const valid = checkScope(scope);
|
|
3169
|
+
if (!valid.ok) return valid;
|
|
3170
|
+
const key = keyFor(scope, "backup");
|
|
3171
|
+
const text = encode(layout, key);
|
|
3172
|
+
if (!text.ok) return text;
|
|
3173
|
+
const stored = await guarded(
|
|
3174
|
+
key,
|
|
3175
|
+
"save",
|
|
3176
|
+
() => adapter.set(key, text.value, { signal })
|
|
3177
|
+
);
|
|
3178
|
+
return stored.ok ? { ok: true, value: null } : stored;
|
|
3179
|
+
},
|
|
3180
|
+
async reset(scope) {
|
|
3181
|
+
const valid = checkScope(scope);
|
|
3182
|
+
if (!valid.ok) return valid;
|
|
3183
|
+
const layoutKey = keyFor(scope);
|
|
3184
|
+
const backupKey = keyFor(scope, "backup");
|
|
3185
|
+
if (adapter.remove === void 0) {
|
|
3186
|
+
return failure(
|
|
3187
|
+
"invalid",
|
|
3188
|
+
`Could not reset "${layoutKey}": the adapter has no remove method.`
|
|
3189
|
+
);
|
|
3190
|
+
}
|
|
3191
|
+
const remove = adapter.remove.bind(adapter);
|
|
3192
|
+
const first = await guarded(
|
|
3193
|
+
layoutKey,
|
|
3194
|
+
"remove",
|
|
3195
|
+
() => remove(layoutKey, { signal })
|
|
3196
|
+
);
|
|
3197
|
+
revisions.delete(layoutKey);
|
|
3198
|
+
if (!first.ok) return first;
|
|
3199
|
+
const second = await guarded(
|
|
3200
|
+
backupKey,
|
|
3201
|
+
"remove",
|
|
3202
|
+
() => remove(backupKey, { signal })
|
|
3203
|
+
);
|
|
3204
|
+
return second.ok ? { ok: true, value: null } : second;
|
|
3205
|
+
},
|
|
3206
|
+
watch(scope, definitions, onChange, normalizeOptions) {
|
|
3207
|
+
const key = keyFor(scope);
|
|
3208
|
+
const stop = adapter.subscribe?.(
|
|
3209
|
+
key,
|
|
3210
|
+
(stored) => {
|
|
3211
|
+
if (stored === null) {
|
|
3212
|
+
revisions.delete(key);
|
|
3213
|
+
onChange({ layout: null });
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
if (stored.value === lastWritten.get(key) || stored.revision !== void 0 && stored.revision === revisions.get(key)) {
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
3219
|
+
const read = readEnvelope(key, stored);
|
|
3220
|
+
if (!read.ok) return;
|
|
3221
|
+
onChange(
|
|
3222
|
+
repair(
|
|
3223
|
+
{
|
|
3224
|
+
layout: read.value.layout,
|
|
3225
|
+
...stored.revision === void 0 ? {} : { revision: stored.revision },
|
|
3226
|
+
...read.value.savedAt === void 0 ? {} : { savedAt: read.value.savedAt }
|
|
3227
|
+
},
|
|
3228
|
+
definitions,
|
|
3229
|
+
normalizeOptions
|
|
3230
|
+
)
|
|
3231
|
+
);
|
|
3232
|
+
}
|
|
3233
|
+
);
|
|
3234
|
+
return stop ?? (() => void 0);
|
|
3235
|
+
}
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
3238
|
+
|
|
3239
|
+
exports.DATE_RANGE_PRESETS = DATE_RANGE_PRESETS;
|
|
1498
3240
|
exports.DEFAULT_COMPARISON_LABEL = DEFAULT_COMPARISON_LABEL;
|
|
1499
3241
|
exports.DEFAULT_CURRENCY = DEFAULT_CURRENCY;
|
|
1500
3242
|
exports.DEFAULT_DETAIL_PAGE_SIZE = DEFAULT_DETAIL_PAGE_SIZE;
|
|
1501
3243
|
exports.DEFAULT_EMPTY_TEXT = DEFAULT_EMPTY_TEXT;
|
|
3244
|
+
exports.DEFAULT_MAX_LAYOUT_BYTES = DEFAULT_MAX_LAYOUT_BYTES;
|
|
3245
|
+
exports.DEFAULT_PAGE_KEY = DEFAULT_PAGE_KEY;
|
|
3246
|
+
exports.DEFAULT_PAGE_ROWS = DEFAULT_PAGE_ROWS;
|
|
1502
3247
|
exports.DETAIL_MODES = DETAIL_MODES;
|
|
1503
3248
|
exports.EMPTY_LAYOUT = EMPTY_LAYOUT;
|
|
3249
|
+
exports.LAYOUT_ENVELOPE_VERSION = LAYOUT_ENVELOPE_VERSION;
|
|
1504
3250
|
exports.MAX_DETAIL_PAGE_SIZE = MAX_DETAIL_PAGE_SIZE;
|
|
1505
3251
|
exports.MAX_GRAPH_SERIES = MAX_GRAPH_SERIES;
|
|
3252
|
+
exports.MAX_PAGE_ROWS = MAX_PAGE_ROWS;
|
|
3253
|
+
exports.MAX_PAGE_TITLE_LENGTH = MAX_PAGE_TITLE_LENGTH;
|
|
3254
|
+
exports.MAX_PARSED_PAGES = MAX_PARSED_PAGES;
|
|
1506
3255
|
exports.MAX_REPORTED_PROBLEMS = MAX_REPORTED_PROBLEMS;
|
|
3256
|
+
exports.MAX_TEXT_OPTION_LENGTH = MAX_TEXT_OPTION_LENGTH;
|
|
3257
|
+
exports.MAX_WIDGET_OPTIONS = MAX_WIDGET_OPTIONS;
|
|
1507
3258
|
exports.MAX_WIDGET_WIDTH = MAX_WIDGET_WIDTH;
|
|
1508
3259
|
exports.MIN_WIDGET_WIDTH = MIN_WIDGET_WIDTH;
|
|
3260
|
+
exports.OPTION_KEY_PATTERN = OPTION_KEY_PATTERN;
|
|
1509
3261
|
exports.VALUE_FORMATS = VALUE_FORMATS;
|
|
1510
3262
|
exports.WIDGET_FILLS = WIDGET_FILLS;
|
|
1511
3263
|
exports.WIDGET_KINDS = WIDGET_KINDS;
|
|
3264
|
+
exports.WIDGET_OPTION_TYPES = WIDGET_OPTION_TYPES;
|
|
1512
3265
|
exports.WIDGET_SIZES = WIDGET_SIZES;
|
|
1513
3266
|
exports.WIDTH_COLUMNS = WIDTH_COLUMNS;
|
|
3267
|
+
exports.addPage = addPage;
|
|
3268
|
+
exports.applyClientOptions = applyClientOptions;
|
|
3269
|
+
exports.assignPages = assignPages;
|
|
1514
3270
|
exports.baseColumnSpan = baseColumnSpan;
|
|
1515
3271
|
exports.cacheKeyFor = cacheKeyFor;
|
|
3272
|
+
exports.coerceOptionValue = coerceOptionValue;
|
|
3273
|
+
exports.createLayoutPersistence = createLayoutPersistence;
|
|
1516
3274
|
exports.createWidgetLoader = createWidgetLoader;
|
|
1517
3275
|
exports.defaultDetailQuery = defaultDetailQuery;
|
|
3276
|
+
exports.defaultOptionValue = defaultOptionValue;
|
|
1518
3277
|
exports.defineWidget = defineWidget;
|
|
1519
3278
|
exports.deriveDetailData = deriveDetailData;
|
|
1520
3279
|
exports.describeUnknownError = describeUnknownError;
|
|
@@ -1527,13 +3286,19 @@ exports.fillColumnSpans = fillColumnSpans;
|
|
|
1527
3286
|
exports.fillWidthSpans = fillWidthSpans;
|
|
1528
3287
|
exports.fillsHeight = fillsHeight;
|
|
1529
3288
|
exports.fillsWidth = fillsWidth;
|
|
3289
|
+
exports.finalizeWidget = finalizeWidget;
|
|
3290
|
+
exports.fitCount = fitCount;
|
|
1530
3291
|
exports.formatCount = formatCount;
|
|
1531
3292
|
exports.formatPercentChange = formatPercentChange;
|
|
1532
3293
|
exports.formatValue = formatValue;
|
|
1533
3294
|
exports.fromLegacyDefinition = fromLegacyDefinition;
|
|
3295
|
+
exports.hasPages = hasPages;
|
|
1534
3296
|
exports.hiddenWidgets = hiddenWidgets;
|
|
1535
3297
|
exports.hideWidget = hideWidget;
|
|
3298
|
+
exports.isClientApplied = isClientApplied;
|
|
3299
|
+
exports.isColumnKeyFor = isColumnKeyFor;
|
|
1536
3300
|
exports.isHidden = isHidden;
|
|
3301
|
+
exports.isIsoInterval = isIsoInterval;
|
|
1537
3302
|
exports.isMinimized = isMinimized;
|
|
1538
3303
|
exports.isSafeHref = isSafeHref;
|
|
1539
3304
|
exports.isSafeImageUrl = isSafeImageUrl;
|
|
@@ -1545,41 +3310,72 @@ exports.itemWidth = itemWidth;
|
|
|
1545
3310
|
exports.kpiDelta = kpiDelta;
|
|
1546
3311
|
exports.labelStride = labelStride;
|
|
1547
3312
|
exports.loadingWidgets = loadingWidgets;
|
|
3313
|
+
exports.memoryAdapter = memoryAdapter;
|
|
1548
3314
|
exports.minimizeWidget = minimizeWidget;
|
|
3315
|
+
exports.movePage = movePage;
|
|
1549
3316
|
exports.moveWidget = moveWidget;
|
|
1550
3317
|
exports.moveWidgetBy = moveWidgetBy;
|
|
1551
3318
|
exports.moveWidgetDown = moveWidgetDown;
|
|
3319
|
+
exports.moveWidgetToPage = moveWidgetToPage;
|
|
1552
3320
|
exports.moveWidgetUp = moveWidgetUp;
|
|
3321
|
+
exports.nextPageKey = nextPageKey;
|
|
1553
3322
|
exports.niceDomain = niceDomain;
|
|
1554
3323
|
exports.niceStep = niceStep;
|
|
3324
|
+
exports.normalizeLayout = normalizeLayout;
|
|
1555
3325
|
exports.ok = ok;
|
|
3326
|
+
exports.optionValuesFor = optionValuesFor;
|
|
3327
|
+
exports.optionsCacheSuffix = optionsCacheSuffix;
|
|
3328
|
+
exports.pageLayout = pageLayout;
|
|
3329
|
+
exports.pageList = pageList;
|
|
3330
|
+
exports.pageOf = pageOf;
|
|
3331
|
+
exports.pageRoom = pageRoom;
|
|
3332
|
+
exports.pageWidgets = pageWidgets;
|
|
1556
3333
|
exports.parseDetailQuery = parseDetailQuery;
|
|
1557
3334
|
exports.parseLayout = parseLayout;
|
|
3335
|
+
exports.parseSortValue = parseSortValue;
|
|
1558
3336
|
exports.percentChange = percentChange;
|
|
3337
|
+
exports.placeRows = placeRows;
|
|
1559
3338
|
exports.pruneLayout = pruneLayout;
|
|
1560
3339
|
exports.queryRows = queryRows;
|
|
3340
|
+
exports.readOnly = readOnly;
|
|
3341
|
+
exports.removePage = removePage;
|
|
3342
|
+
exports.renamePage = renamePage;
|
|
3343
|
+
exports.resolveDateRange = resolveDateRange;
|
|
1561
3344
|
exports.resolveDetailOptions = resolveDetailOptions;
|
|
3345
|
+
exports.resolveOptionValues = resolveOptionValues;
|
|
3346
|
+
exports.resolvePageKey = resolvePageKey;
|
|
1562
3347
|
exports.resolvePayload = resolvePayload;
|
|
1563
3348
|
exports.resolveTableControls = resolveTableControls;
|
|
1564
3349
|
exports.resolveWidget = resolveWidget;
|
|
1565
3350
|
exports.resolveWidgetLock = resolveWidgetLock;
|
|
3351
|
+
exports.resolveWidgetUnapplied = resolveWidgetUnapplied;
|
|
1566
3352
|
exports.resolveWidgets = resolveWidgets;
|
|
1567
3353
|
exports.restoreWidget = restoreWidget;
|
|
3354
|
+
exports.sameOptionValues = sameOptionValues;
|
|
1568
3355
|
exports.serializeDetailQuery = serializeDetailQuery;
|
|
1569
3356
|
exports.serializeLayout = serializeLayout;
|
|
1570
3357
|
exports.shareOf = shareOf;
|
|
1571
3358
|
exports.showWidget = showWidget;
|
|
1572
3359
|
exports.sortDefinitions = sortDefinitions;
|
|
3360
|
+
exports.sortFromOptions = sortFromOptions;
|
|
1573
3361
|
exports.tableDetailData = tableDetailData;
|
|
1574
3362
|
exports.toggleHidden = toggleHidden;
|
|
1575
3363
|
exports.toggleMinimized = toggleMinimized;
|
|
1576
3364
|
exports.upgradeLegacyWidgetData = upgradeLegacyWidgetData;
|
|
1577
3365
|
exports.usesWidthColumns = usesWidthColumns;
|
|
3366
|
+
exports.utf8Length = utf8Length;
|
|
1578
3367
|
exports.validateDetailData = validateDetailData;
|
|
1579
3368
|
exports.validateWidgetData = validateWidgetData;
|
|
1580
3369
|
exports.validateWidgetDefinition = validateWidgetDefinition;
|
|
1581
3370
|
exports.visibleWidgets = visibleWidgets;
|
|
1582
3371
|
exports.widgetsFor = widgetsFor;
|
|
1583
3372
|
exports.widthForSize = widthForSize;
|
|
3373
|
+
exports.withEncoding = withEncoding;
|
|
3374
|
+
exports.withFallback = withFallback;
|
|
3375
|
+
exports.withLogging = withLogging;
|
|
3376
|
+
exports.withPageLayout = withPageLayout;
|
|
3377
|
+
exports.withPrefix = withPrefix;
|
|
3378
|
+
exports.withReadCache = withReadCache;
|
|
3379
|
+
exports.withRetry = withRetry;
|
|
1584
3380
|
//# sourceMappingURL=core.cjs.map
|
|
1585
3381
|
//# sourceMappingURL=core.cjs.map
|