@powerportalspro/react-fluent 5.1.0 → 6.1.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/EULA.txt +263 -0
- package/README.md +38 -0
- package/dist/index.cjs +392 -48
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +395 -51
- package/dist/index.js.map +1 -1
- package/package.json +7 -6
package/dist/index.cjs
CHANGED
|
@@ -8319,6 +8319,36 @@ function MainGridVirtualizeFooter({
|
|
|
8319
8319
|
] });
|
|
8320
8320
|
}
|
|
8321
8321
|
|
|
8322
|
+
// src/grids/virtualize-paging.ts
|
|
8323
|
+
function hasMoreRowsToLoad(state) {
|
|
8324
|
+
if (state.moreRecords === false) return false;
|
|
8325
|
+
if (state.moreRecords === true) return true;
|
|
8326
|
+
if (state.lastPageEmpty) return false;
|
|
8327
|
+
if (state.totalCount === void 0) return true;
|
|
8328
|
+
return state.accumulatedCount < state.totalCount;
|
|
8329
|
+
}
|
|
8330
|
+
var VirtualizePagingCookies = class {
|
|
8331
|
+
key;
|
|
8332
|
+
byPage = /* @__PURE__ */ new Map();
|
|
8333
|
+
/** Store the cookie the response for `page` came back with (no-op for a null / empty cookie). */
|
|
8334
|
+
record(key, page, cookie) {
|
|
8335
|
+
if (this.key !== key) {
|
|
8336
|
+
this.key = key;
|
|
8337
|
+
this.byPage = /* @__PURE__ */ new Map();
|
|
8338
|
+
}
|
|
8339
|
+
if (cookie) this.byPage.set(page, cookie);
|
|
8340
|
+
}
|
|
8341
|
+
/**
|
|
8342
|
+
* The cookie to send when requesting `page` under `key`: the one page
|
|
8343
|
+
* `page - 1` returned, or `undefined` when it isn't known (page 1, a
|
|
8344
|
+
* non-sequential jump, or a key the store has no cookies for).
|
|
8345
|
+
*/
|
|
8346
|
+
cookieFor(key, page) {
|
|
8347
|
+
if (this.key !== key || page <= 1) return void 0;
|
|
8348
|
+
return this.byPage.get(page - 1);
|
|
8349
|
+
}
|
|
8350
|
+
};
|
|
8351
|
+
|
|
8322
8352
|
// src/grids/persisted-grid-state.ts
|
|
8323
8353
|
function readPersistedGridStateFromUrl(queryParameterName) {
|
|
8324
8354
|
if (!queryParameterName) return null;
|
|
@@ -8850,8 +8880,22 @@ function MainGridImpl({
|
|
|
8850
8880
|
`[MainGrid] Declared <GridColumn> children override the column projection of the supplied fetchXml (columns: ${columnsKey.split(",").join(", ")}). Drop the children \u2014 or remove the conflicting <attribute> elements from the fetchXml \u2014 to silence this message.`
|
|
8851
8881
|
);
|
|
8852
8882
|
}, [columnsKey, useInlineFetchXml]);
|
|
8883
|
+
const accumulatorResetKey = react.useMemo(
|
|
8884
|
+
() => [
|
|
8885
|
+
resolvedViewId ?? "",
|
|
8886
|
+
useInlineFetchXml ? inlineFetchXml ?? "" : "",
|
|
8887
|
+
debouncedSearchText,
|
|
8888
|
+
sort.map((s) => `${s.columnName}:${s.descending ? "d" : "a"}`).join("|"),
|
|
8889
|
+
filtersKey,
|
|
8890
|
+
pageSize,
|
|
8891
|
+
tableName ?? ""
|
|
8892
|
+
].join("|"),
|
|
8893
|
+
[resolvedViewId, useInlineFetchXml, inlineFetchXml, debouncedSearchText, sort, filtersKey, pageSize, tableName]
|
|
8894
|
+
);
|
|
8895
|
+
const [virtualizeCookies] = react.useState(() => new VirtualizePagingCookies());
|
|
8853
8896
|
const searchRequest = react.useMemo(() => {
|
|
8854
8897
|
const trimmed = debouncedSearchText.trim();
|
|
8898
|
+
const pagingCookie = isVirtualize ? virtualizeCookies.cookieFor(accumulatorResetKey, page) : void 0;
|
|
8855
8899
|
return {
|
|
8856
8900
|
...useInlineFetchXml && inlineFetchXml ? { fetchXml: inlineFetchXml } : resolvedViewId !== void 0 && { viewId: resolvedViewId },
|
|
8857
8901
|
...trimmed && { searchText: trimmed },
|
|
@@ -8871,7 +8915,8 @@ function MainGridImpl({
|
|
|
8871
8915
|
},
|
|
8872
8916
|
...columnsOverride && { columns: [...columnsOverride] },
|
|
8873
8917
|
pageNumber: page,
|
|
8874
|
-
pageSize
|
|
8918
|
+
pageSize,
|
|
8919
|
+
...pagingCookie && { pagingCookie }
|
|
8875
8920
|
};
|
|
8876
8921
|
}, [
|
|
8877
8922
|
resolvedViewId,
|
|
@@ -8882,7 +8927,9 @@ function MainGridImpl({
|
|
|
8882
8927
|
page,
|
|
8883
8928
|
pageSize,
|
|
8884
8929
|
filtersKey,
|
|
8885
|
-
columnsKey
|
|
8930
|
+
columnsKey,
|
|
8931
|
+
isVirtualize,
|
|
8932
|
+
accumulatorResetKey
|
|
8886
8933
|
]);
|
|
8887
8934
|
const transformPending = !!transformViewAsync || isCustomActiveView;
|
|
8888
8935
|
const recordsResult = react$1.useGridData(searchRequest, {
|
|
@@ -9030,7 +9077,13 @@ function MainGridImpl({
|
|
|
9030
9077
|
(row) => row.properties?.[columnName],
|
|
9031
9078
|
{
|
|
9032
9079
|
id: columnName,
|
|
9033
|
-
|
|
9080
|
+
// Headers normally show the server-resolved Dataverse display name, which bypasses the
|
|
9081
|
+
// client `t()` — so the ?ppp-show-keys aid couldn't reach them. Consult key-mode here too:
|
|
9082
|
+
// when it's on, show the column's localization key (the one a consumer would override),
|
|
9083
|
+
// keyed by the column's OWN owning table (`resolvedColumn.tableName` — the related table
|
|
9084
|
+
// for a linked-entity column, not the grid's primary table). For linked columns the name
|
|
9085
|
+
// is a dotted alias path, so take the segment after the last dot as the real column name.
|
|
9086
|
+
header: () => react$1.isKeyModeEnabled() ? `tables.${resolvedColumn.tableName}.columns.${columnName.split(".").pop() ?? columnName}.label` : resolvedColumn.displayName,
|
|
9034
9087
|
cell: (cellCtx) => {
|
|
9035
9088
|
const rowRecord = cellCtx.row.original;
|
|
9036
9089
|
const rowId = rowRecord.id;
|
|
@@ -9173,18 +9226,6 @@ function MainGridImpl({
|
|
|
9173
9226
|
]);
|
|
9174
9227
|
const pageRecords = dataSource ? dataSource.rows : recordsResult.data?.tableRecords ?? EMPTY_RECORDS;
|
|
9175
9228
|
const [accumulatedRows, setAccumulatedRows] = react.useState(null);
|
|
9176
|
-
const accumulatorResetKey = react.useMemo(
|
|
9177
|
-
() => [
|
|
9178
|
-
resolvedViewId ?? "",
|
|
9179
|
-
useInlineFetchXml ? inlineFetchXml ?? "" : "",
|
|
9180
|
-
debouncedSearchText,
|
|
9181
|
-
sort.map((s) => `${s.columnName}:${s.descending ? "d" : "a"}`).join("|"),
|
|
9182
|
-
filtersKey,
|
|
9183
|
-
pageSize,
|
|
9184
|
-
tableName ?? ""
|
|
9185
|
-
].join("|"),
|
|
9186
|
-
[resolvedViewId, useInlineFetchXml, inlineFetchXml, debouncedSearchText, sort, filtersKey, pageSize, tableName]
|
|
9187
|
-
);
|
|
9188
9229
|
const resetVirtualizedView = react.useCallback(() => {
|
|
9189
9230
|
setAccumulatedRows([]);
|
|
9190
9231
|
setPage(1);
|
|
@@ -9210,6 +9251,12 @@ function MainGridImpl({
|
|
|
9210
9251
|
return [...base, ...additions];
|
|
9211
9252
|
});
|
|
9212
9253
|
}, [isVirtualize, recordsResult.status, pageRecords]);
|
|
9254
|
+
react.useEffect(() => {
|
|
9255
|
+
if (!isVirtualize) return;
|
|
9256
|
+
const landed = recordsResult.data;
|
|
9257
|
+
if (!landed) return;
|
|
9258
|
+
virtualizeCookies.record(accumulatorResetKey, page, landed.pagingInfo?.pagingCookie);
|
|
9259
|
+
}, [isVirtualize, recordsResult.data]);
|
|
9213
9260
|
const data = isVirtualize ? accumulatedRows ?? EMPTY_RECORDS : pageRecords;
|
|
9214
9261
|
const [pendingRowUpdates, setPendingRowUpdates] = react.useState(() => /* @__PURE__ */ new Map());
|
|
9215
9262
|
const [pendingRowCreates, setPendingRowCreates] = react.useState(() => []);
|
|
@@ -9690,7 +9737,15 @@ function MainGridImpl({
|
|
|
9690
9737
|
if (raw == null) return void 0;
|
|
9691
9738
|
return typeof raw === "number" ? raw : Number(raw);
|
|
9692
9739
|
})();
|
|
9693
|
-
const
|
|
9740
|
+
const hasMoreRowsToLoad2 = isVirtualize && hasMoreRowsToLoad({
|
|
9741
|
+
accumulatedCount: accumulatedRows?.length ?? 0,
|
|
9742
|
+
totalCount,
|
|
9743
|
+
moreRecords: dataSource ? dataSource.moreRecords : recordsResult.data?.pagingInfo?.moreRecords,
|
|
9744
|
+
// "A page has landed and it carried no rows." In standalone mode
|
|
9745
|
+
// `data` is only ever defined for a landed response (errors wipe it);
|
|
9746
|
+
// the datasource has no data handle, so idle-after-success stands in.
|
|
9747
|
+
lastPageEmpty: dataSource ? dataSource.status === react$1.QueryStatus.Success && !dataSource.isFetching && dataSource.rows.length === 0 : recordsResult.data !== void 0 && pageRecords.length === 0
|
|
9748
|
+
});
|
|
9694
9749
|
react.useEffect(() => {
|
|
9695
9750
|
if (!isVirtualize) return;
|
|
9696
9751
|
const sentinel = loadMoreSentinelRef.current;
|
|
@@ -9701,7 +9756,7 @@ function MainGridImpl({
|
|
|
9701
9756
|
const entry = entries[0];
|
|
9702
9757
|
if (!entry?.isIntersecting) return;
|
|
9703
9758
|
if (recordsResult.isFetching) return;
|
|
9704
|
-
if (!
|
|
9759
|
+
if (!hasMoreRowsToLoad2) return;
|
|
9705
9760
|
setPage(page + 1);
|
|
9706
9761
|
},
|
|
9707
9762
|
{
|
|
@@ -9714,7 +9769,7 @@ function MainGridImpl({
|
|
|
9714
9769
|
);
|
|
9715
9770
|
observer.observe(sentinel);
|
|
9716
9771
|
return () => observer.disconnect();
|
|
9717
|
-
}, [isVirtualize,
|
|
9772
|
+
}, [isVirtualize, hasMoreRowsToLoad2, recordsResult.isFetching, page, setPage]);
|
|
9718
9773
|
const handlePageChange = (nextPage) => {
|
|
9719
9774
|
if (nextPage === page) return;
|
|
9720
9775
|
setPage(nextPage);
|
|
@@ -10334,7 +10389,7 @@ function MainGridImpl({
|
|
|
10334
10389
|
}
|
|
10335
10390
|
),
|
|
10336
10391
|
displayedData.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.emptyStateRow, children: t("app.components.grid.empty-content") }),
|
|
10337
|
-
isVirtualize &&
|
|
10392
|
+
isVirtualize && hasMoreRowsToLoad2 && // No spinner here: the full-body `refetchScrim` overlay below is the
|
|
10338
10393
|
// single load indicator. Rendering a `size="tiny"` spinner in the
|
|
10339
10394
|
// sentinel as well meant a deps-change refetch (refresh / sort /
|
|
10340
10395
|
// search / view switch) — which resets the accumulator to empty and
|
|
@@ -13573,27 +13628,57 @@ function NavMenu({ children, width, className, style }) {
|
|
|
13573
13628
|
useNavSelectedStyles();
|
|
13574
13629
|
const location = reactRouterDom.useLocation();
|
|
13575
13630
|
const navigate = reactRouterDom.useNavigate();
|
|
13576
|
-
const { allRoutes,
|
|
13631
|
+
const { allRoutes, groups, defaultOpenIndexes } = react.useMemo(
|
|
13577
13632
|
() => collectNavTree(children),
|
|
13578
13633
|
[children]
|
|
13579
13634
|
);
|
|
13580
|
-
const
|
|
13635
|
+
const selectedRoute = react.useMemo(
|
|
13581
13636
|
() => bestRouteMatch(allRoutes, location.pathname),
|
|
13582
13637
|
[allRoutes, location.pathname]
|
|
13583
13638
|
);
|
|
13639
|
+
const [openIndexes, setOpenIndexes] = react.useState(
|
|
13640
|
+
() => /* @__PURE__ */ new Set([
|
|
13641
|
+
...defaultOpenIndexes,
|
|
13642
|
+
// Seed the active route's group too, so it's open on first paint
|
|
13643
|
+
// (the effect below only fires after mount).
|
|
13644
|
+
...selectedRoute && selectedRoute.groupIndex >= 0 ? [selectedRoute.groupIndex] : []
|
|
13645
|
+
])
|
|
13646
|
+
);
|
|
13647
|
+
const selectedGroupIndex = selectedRoute?.groupIndex ?? -1;
|
|
13648
|
+
react.useEffect(() => {
|
|
13649
|
+
if (selectedGroupIndex < 0) return;
|
|
13650
|
+
setOpenIndexes(
|
|
13651
|
+
(prev) => prev.has(selectedGroupIndex) ? prev : new Set(prev).add(selectedGroupIndex)
|
|
13652
|
+
);
|
|
13653
|
+
}, [selectedGroupIndex]);
|
|
13654
|
+
const openCategories = react.useMemo(
|
|
13655
|
+
() => groups.filter((_, index) => openIndexes.has(index)),
|
|
13656
|
+
[groups, openIndexes]
|
|
13657
|
+
);
|
|
13584
13658
|
const handleSelect = (_, data) => {
|
|
13585
13659
|
const value = data.value;
|
|
13586
13660
|
if (typeof value === "string" && value.startsWith("/")) {
|
|
13587
13661
|
navigate(value);
|
|
13588
13662
|
}
|
|
13589
13663
|
};
|
|
13664
|
+
const handleCategoryToggle = (_, data) => {
|
|
13665
|
+
const index = groups.indexOf(data.value);
|
|
13666
|
+
if (index < 0) return;
|
|
13667
|
+
setOpenIndexes((prev) => {
|
|
13668
|
+
const next = new Set(prev);
|
|
13669
|
+
if (next.has(index)) next.delete(index);
|
|
13670
|
+
else next.add(index);
|
|
13671
|
+
return next;
|
|
13672
|
+
});
|
|
13673
|
+
};
|
|
13590
13674
|
const navStyle = width ? { ...style, minWidth: width, maxWidth: width } : style;
|
|
13591
13675
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
13592
13676
|
reactNav.Nav,
|
|
13593
13677
|
{
|
|
13594
|
-
selectedValue:
|
|
13678
|
+
selectedValue: selectedRoute?.to ?? "",
|
|
13595
13679
|
onNavItemSelect: handleSelect,
|
|
13596
|
-
|
|
13680
|
+
openCategories,
|
|
13681
|
+
onNavCategoryItemToggle: handleCategoryToggle,
|
|
13597
13682
|
className: reactComponents.mergeClasses("ppp-nav-menu", styles.nav, className),
|
|
13598
13683
|
style: navStyle,
|
|
13599
13684
|
children
|
|
@@ -13623,42 +13708,46 @@ function NavItem({ to, icon, children }) {
|
|
|
13623
13708
|
}
|
|
13624
13709
|
function collectNavTree(children) {
|
|
13625
13710
|
const allRoutes = [];
|
|
13626
|
-
const
|
|
13627
|
-
const
|
|
13711
|
+
const groups = [];
|
|
13712
|
+
const defaultOpenIndexes = [];
|
|
13713
|
+
const visit = (nodes, groupIndex) => {
|
|
13628
13714
|
react.Children.forEach(nodes, (child) => {
|
|
13629
13715
|
if (!react.isValidElement(child)) return;
|
|
13630
13716
|
if (child.type === NavGroup) {
|
|
13631
13717
|
const props = child.props;
|
|
13632
13718
|
const value = props.value ?? `group:${props.label}`;
|
|
13633
|
-
|
|
13634
|
-
|
|
13719
|
+
const index = groups.length;
|
|
13720
|
+
groups.push(value);
|
|
13721
|
+
if (props.defaultExpanded) defaultOpenIndexes.push(index);
|
|
13722
|
+
visit(props.children, index);
|
|
13635
13723
|
return;
|
|
13636
13724
|
}
|
|
13637
13725
|
if (child.type === NavItem) {
|
|
13638
13726
|
const props = child.props;
|
|
13639
13727
|
allRoutes.push({
|
|
13640
13728
|
to: props.to,
|
|
13641
|
-
match: props.match ?? (props.to === "/" ? "exact" : "prefix")
|
|
13729
|
+
match: props.match ?? (props.to === "/" ? "exact" : "prefix"),
|
|
13730
|
+
groupIndex
|
|
13642
13731
|
});
|
|
13643
13732
|
return;
|
|
13644
13733
|
}
|
|
13645
13734
|
const wrapperChildren = child.props.children;
|
|
13646
|
-
if (wrapperChildren !== void 0) visit(wrapperChildren);
|
|
13735
|
+
if (wrapperChildren !== void 0) visit(wrapperChildren, groupIndex);
|
|
13647
13736
|
});
|
|
13648
13737
|
};
|
|
13649
|
-
visit(children);
|
|
13650
|
-
return { allRoutes,
|
|
13738
|
+
visit(children, -1);
|
|
13739
|
+
return { allRoutes, groups, defaultOpenIndexes };
|
|
13651
13740
|
}
|
|
13652
13741
|
function bestRouteMatch(routes, pathname) {
|
|
13653
13742
|
const normalize = (p) => p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
|
|
13654
13743
|
const target = normalize(pathname);
|
|
13655
13744
|
let best;
|
|
13656
13745
|
let bestLen = -1;
|
|
13657
|
-
for (const
|
|
13658
|
-
const route = normalize(to);
|
|
13659
|
-
const matches = route === target || match === "prefix" && (route === "/" || target.startsWith(route + "/"));
|
|
13746
|
+
for (const candidate of routes) {
|
|
13747
|
+
const route = normalize(candidate.to);
|
|
13748
|
+
const matches = route === target || candidate.match === "prefix" && (route === "/" || target.startsWith(route + "/"));
|
|
13660
13749
|
if (matches && route.length > bestLen) {
|
|
13661
|
-
best =
|
|
13750
|
+
best = candidate;
|
|
13662
13751
|
bestLen = route.length;
|
|
13663
13752
|
}
|
|
13664
13753
|
}
|
|
@@ -14401,8 +14490,40 @@ var useStyles12 = reactComponents.makeStyles({
|
|
|
14401
14490
|
mergedButtons: {
|
|
14402
14491
|
display: "flex",
|
|
14403
14492
|
flexWrap: "wrap",
|
|
14404
|
-
gap: reactComponents.tokens.
|
|
14493
|
+
gap: reactComponents.tokens.spacingHorizontalL
|
|
14494
|
+
},
|
|
14495
|
+
// One culture group: language label + inspect button + download button, kept together.
|
|
14496
|
+
mergedCulture: {
|
|
14497
|
+
display: "flex",
|
|
14498
|
+
alignItems: "center",
|
|
14499
|
+
gap: reactComponents.tokens.spacingHorizontalXS
|
|
14500
|
+
},
|
|
14501
|
+
mergedCultureLabel: { fontSize: reactComponents.tokens.fontSizeBase200 },
|
|
14502
|
+
dialogSurface: { maxWidth: "1100px", width: "90vw" },
|
|
14503
|
+
dialogToolbar: {
|
|
14504
|
+
display: "flex",
|
|
14505
|
+
alignItems: "center",
|
|
14506
|
+
gap: reactComponents.tokens.spacingHorizontalM,
|
|
14507
|
+
flexWrap: "wrap",
|
|
14508
|
+
marginBottom: reactComponents.tokens.spacingVerticalS
|
|
14509
|
+
},
|
|
14510
|
+
// Scroll viewport for the merged grid so a tall result set scrolls inside the dialog.
|
|
14511
|
+
dialogGrid: { maxHeight: "60vh", overflowY: "auto", overflowX: "auto" },
|
|
14512
|
+
// Merged-snapshot cells. Keys and sources are long single tokens (dotted keys,
|
|
14513
|
+
// backslash-only absolute paths) with no natural break opportunity, so without
|
|
14514
|
+
// this the text paints straight over the neighbouring column. `overflow-wrap:
|
|
14515
|
+
// anywhere` (not just `break-word`) is what makes the flex cell honour its
|
|
14516
|
+
// column width — it counts the soft breaks toward min-content — so the token
|
|
14517
|
+
// wraps inside the column instead of stretching it. Same recipe as
|
|
14518
|
+
// `sourceCell` in the per-source loads table above.
|
|
14519
|
+
mergedCell: {
|
|
14520
|
+
whiteSpace: "normal",
|
|
14521
|
+
overflowWrap: "anywhere",
|
|
14522
|
+
wordBreak: "break-word"
|
|
14405
14523
|
},
|
|
14524
|
+
filterInput: { minWidth: "260px" },
|
|
14525
|
+
pager: { display: "flex", alignItems: "center", gap: reactComponents.tokens.spacingHorizontalS },
|
|
14526
|
+
grow: { flex: 1 },
|
|
14406
14527
|
sectionBody: {
|
|
14407
14528
|
display: "flex",
|
|
14408
14529
|
flexDirection: "column",
|
|
@@ -14421,6 +14542,15 @@ function downloadBytes(fileName, data, contentType = "application/json") {
|
|
|
14421
14542
|
anchor.click();
|
|
14422
14543
|
URL.revokeObjectURL(url);
|
|
14423
14544
|
}
|
|
14545
|
+
var kindKeyOf = (kind) => KIND_KEY[Number(kind)] ?? "tablemetadata";
|
|
14546
|
+
function cultureLabel(culture) {
|
|
14547
|
+
try {
|
|
14548
|
+
const name = new Intl.DisplayNames(void 0, { type: "language" }).of(culture);
|
|
14549
|
+
return name && name.toLowerCase() !== culture.toLowerCase() ? `${name} (${culture})` : culture;
|
|
14550
|
+
} catch {
|
|
14551
|
+
return culture;
|
|
14552
|
+
}
|
|
14553
|
+
}
|
|
14424
14554
|
function LocalizationAdmin({ className }) {
|
|
14425
14555
|
const styles = useStyles12();
|
|
14426
14556
|
const t = react$1.useT();
|
|
@@ -14430,6 +14560,7 @@ function LocalizationAdmin({ className }) {
|
|
|
14430
14560
|
const [isReloading, setIsReloading] = react.useState(false);
|
|
14431
14561
|
const [isDownloading, setIsDownloading] = react.useState(false);
|
|
14432
14562
|
const [message, setMessage] = react.useState();
|
|
14563
|
+
const [mergedDialogCulture, setMergedDialogCulture] = react.useState(null);
|
|
14433
14564
|
const loadOverview = react.useCallback(async () => {
|
|
14434
14565
|
try {
|
|
14435
14566
|
const value = await ppp.getLocalizationOverviewAsync();
|
|
@@ -14587,17 +14718,44 @@ function LocalizationAdmin({ className }) {
|
|
|
14587
14718
|
/* @__PURE__ */ jsxRuntime.jsx(LocalizationTranslator, {}),
|
|
14588
14719
|
availableCultures.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(Section, { headerText: t("app.localization-admin.download-merged-title"), borderVisible: true, children: /* @__PURE__ */ jsxRuntime.jsx(SectionColumn, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.sectionBody, children: [
|
|
14589
14720
|
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { children: t("app.localization-admin.download-merged-description") }),
|
|
14590
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.mergedButtons, children: availableCultures.map((culture) => /* @__PURE__ */ jsxRuntime.
|
|
14591
|
-
|
|
14592
|
-
|
|
14593
|
-
|
|
14594
|
-
|
|
14595
|
-
|
|
14596
|
-
|
|
14597
|
-
|
|
14598
|
-
|
|
14599
|
-
|
|
14600
|
-
|
|
14721
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.mergedButtons, children: availableCultures.map((culture) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.mergedCulture, children: [
|
|
14722
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.mergedCultureLabel, children: cultureLabel(culture) }),
|
|
14723
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14724
|
+
reactComponents.Tooltip,
|
|
14725
|
+
{
|
|
14726
|
+
content: t("app.localization-admin.view-merged-tooltip", [
|
|
14727
|
+
cultureLabel(culture)
|
|
14728
|
+
]),
|
|
14729
|
+
relationship: "label",
|
|
14730
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14731
|
+
reactComponents.Button,
|
|
14732
|
+
{
|
|
14733
|
+
appearance: "outline",
|
|
14734
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.EyeRegular, {}),
|
|
14735
|
+
onClick: () => setMergedDialogCulture(culture)
|
|
14736
|
+
}
|
|
14737
|
+
)
|
|
14738
|
+
}
|
|
14739
|
+
),
|
|
14740
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14741
|
+
reactComponents.Tooltip,
|
|
14742
|
+
{
|
|
14743
|
+
content: t("app.localization-admin.download-merged-tooltip", [
|
|
14744
|
+
cultureLabel(culture)
|
|
14745
|
+
]),
|
|
14746
|
+
relationship: "label",
|
|
14747
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
14748
|
+
reactComponents.Button,
|
|
14749
|
+
{
|
|
14750
|
+
appearance: "subtle",
|
|
14751
|
+
disabled: isDownloading,
|
|
14752
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowDownloadRegular, {}),
|
|
14753
|
+
onClick: () => onDownloadMerged(culture)
|
|
14754
|
+
}
|
|
14755
|
+
)
|
|
14756
|
+
}
|
|
14757
|
+
)
|
|
14758
|
+
] }, culture)) })
|
|
14601
14759
|
] }) }) }),
|
|
14602
14760
|
/* @__PURE__ */ jsxRuntime.jsx(Section, { headerText: t("app.localization-admin.sources-title"), borderVisible: true, children: /* @__PURE__ */ jsxRuntime.jsx(SectionColumn, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.sectionBody, children: [
|
|
14603
14761
|
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Text, { children: t("app.localization-admin.sources-description") }),
|
|
@@ -14628,7 +14786,22 @@ function LocalizationAdmin({ className }) {
|
|
|
14628
14786
|
] }) }) })
|
|
14629
14787
|
] });
|
|
14630
14788
|
}
|
|
14631
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
14789
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: reactComponents.mergeClasses("ppp-localization-admin", styles.root, className), children: [
|
|
14790
|
+
content,
|
|
14791
|
+
mergedDialogCulture !== null && /* @__PURE__ */ jsxRuntime.jsx(
|
|
14792
|
+
MergedSourcesDialog,
|
|
14793
|
+
{
|
|
14794
|
+
culture: mergedDialogCulture,
|
|
14795
|
+
label: cultureLabel(mergedDialogCulture),
|
|
14796
|
+
onClose: () => setMergedDialogCulture(null),
|
|
14797
|
+
onDownload: () => onDownloadMerged(mergedDialogCulture),
|
|
14798
|
+
isDownloading,
|
|
14799
|
+
ppp,
|
|
14800
|
+
t,
|
|
14801
|
+
styles
|
|
14802
|
+
}
|
|
14803
|
+
)
|
|
14804
|
+
] });
|
|
14632
14805
|
}
|
|
14633
14806
|
function ConfigRow({ styles, label, value }) {
|
|
14634
14807
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
@@ -14678,6 +14851,177 @@ function SourceRow({
|
|
|
14678
14851
|
)) }) })
|
|
14679
14852
|
] });
|
|
14680
14853
|
}
|
|
14854
|
+
var MERGED_PAGE_SIZE = 100;
|
|
14855
|
+
function MergedSourcesDialog({
|
|
14856
|
+
culture,
|
|
14857
|
+
label,
|
|
14858
|
+
onClose,
|
|
14859
|
+
onDownload,
|
|
14860
|
+
isDownloading,
|
|
14861
|
+
ppp,
|
|
14862
|
+
t,
|
|
14863
|
+
styles
|
|
14864
|
+
}) {
|
|
14865
|
+
const [entries, setEntries] = react.useState(null);
|
|
14866
|
+
const [loadError, setLoadError] = react.useState();
|
|
14867
|
+
const [filter, setFilter] = react.useState("");
|
|
14868
|
+
const [page, setPage] = react.useState(0);
|
|
14869
|
+
const [sortState, setSortState] = react.useState({
|
|
14870
|
+
sortColumn: "key",
|
|
14871
|
+
sortDirection: "ascending"
|
|
14872
|
+
});
|
|
14873
|
+
react.useEffect(() => {
|
|
14874
|
+
let cancelled = false;
|
|
14875
|
+
void (async () => {
|
|
14876
|
+
try {
|
|
14877
|
+
const value = await ppp.getMergedLocalizationEntriesAsync(culture);
|
|
14878
|
+
if (!cancelled) {
|
|
14879
|
+
setEntries(value);
|
|
14880
|
+
setLoadError(void 0);
|
|
14881
|
+
}
|
|
14882
|
+
} catch (reason) {
|
|
14883
|
+
if (!cancelled) {
|
|
14884
|
+
setEntries(null);
|
|
14885
|
+
setLoadError(reason instanceof Error ? reason.message : String(reason));
|
|
14886
|
+
}
|
|
14887
|
+
}
|
|
14888
|
+
})();
|
|
14889
|
+
return () => {
|
|
14890
|
+
cancelled = true;
|
|
14891
|
+
};
|
|
14892
|
+
}, [ppp, culture]);
|
|
14893
|
+
const columns = react.useMemo(
|
|
14894
|
+
() => [
|
|
14895
|
+
reactComponents.createTableColumn({
|
|
14896
|
+
columnId: "key",
|
|
14897
|
+
compare: (a, b) => a.key.localeCompare(b.key),
|
|
14898
|
+
renderHeaderCell: () => t("app.localization-admin.merged-column-key"),
|
|
14899
|
+
renderCell: (item) => item.key
|
|
14900
|
+
}),
|
|
14901
|
+
reactComponents.createTableColumn({
|
|
14902
|
+
columnId: "kind",
|
|
14903
|
+
compare: (a, b) => Number(a.sourceKind) - Number(b.sourceKind),
|
|
14904
|
+
renderHeaderCell: () => t("app.localization-admin.merged-column-kind"),
|
|
14905
|
+
renderCell: (item) => t(`app.localization-admin.kind-${kindKeyOf(item.sourceKind)}`)
|
|
14906
|
+
}),
|
|
14907
|
+
reactComponents.createTableColumn({
|
|
14908
|
+
columnId: "source",
|
|
14909
|
+
compare: (a, b) => a.sourceDisplayName.localeCompare(b.sourceDisplayName),
|
|
14910
|
+
renderHeaderCell: () => t("app.localization-admin.merged-column-source"),
|
|
14911
|
+
renderCell: (item) => item.sourceDisplayName
|
|
14912
|
+
}),
|
|
14913
|
+
reactComponents.createTableColumn({
|
|
14914
|
+
columnId: "value",
|
|
14915
|
+
compare: (a, b) => a.value.localeCompare(b.value),
|
|
14916
|
+
renderHeaderCell: () => t("app.localization-admin.merged-column-value"),
|
|
14917
|
+
renderCell: (item) => item.value
|
|
14918
|
+
})
|
|
14919
|
+
],
|
|
14920
|
+
[t]
|
|
14921
|
+
);
|
|
14922
|
+
const filtered = react.useMemo(() => {
|
|
14923
|
+
if (!entries) return [];
|
|
14924
|
+
const q = filter.trim().toLowerCase();
|
|
14925
|
+
const base = q ? entries.filter(
|
|
14926
|
+
(e) => e.key.toLowerCase().includes(q) || e.value.toLowerCase().includes(q) || e.sourceDisplayName.toLowerCase().includes(q)
|
|
14927
|
+
) : entries;
|
|
14928
|
+
return [...base];
|
|
14929
|
+
}, [entries, filter]);
|
|
14930
|
+
const sorted = react.useMemo(() => {
|
|
14931
|
+
const column = columns.find((c) => c.columnId === sortState.sortColumn);
|
|
14932
|
+
if (!column?.compare) return filtered;
|
|
14933
|
+
const sign = sortState.sortDirection === "ascending" ? 1 : -1;
|
|
14934
|
+
return [...filtered].sort((a, b) => sign * column.compare(a, b));
|
|
14935
|
+
}, [filtered, columns, sortState]);
|
|
14936
|
+
react.useEffect(() => {
|
|
14937
|
+
setPage(0);
|
|
14938
|
+
}, [filter, sortState]);
|
|
14939
|
+
const pageCount = Math.max(1, Math.ceil(sorted.length / MERGED_PAGE_SIZE));
|
|
14940
|
+
const currentPage = Math.min(page, pageCount - 1);
|
|
14941
|
+
const paged = sorted.slice(currentPage * MERGED_PAGE_SIZE, currentPage * MERGED_PAGE_SIZE + MERGED_PAGE_SIZE);
|
|
14942
|
+
const columnSizingOptions = react.useMemo(
|
|
14943
|
+
() => ({
|
|
14944
|
+
key: { minWidth: 200, defaultWidth: 320 },
|
|
14945
|
+
kind: { minWidth: 110, defaultWidth: 140 },
|
|
14946
|
+
source: { minWidth: 200, defaultWidth: 300 },
|
|
14947
|
+
value: { minWidth: 200, defaultWidth: 360 }
|
|
14948
|
+
}),
|
|
14949
|
+
[]
|
|
14950
|
+
);
|
|
14951
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Dialog, { open: true, onOpenChange: (_event, data) => !data.open && onClose(), children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogSurface, { className: styles.dialogSurface, children: /* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogBody, { children: [
|
|
14952
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogTitle, { children: t("app.localization-admin.merged-dialog-title", [label]) }),
|
|
14953
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.DialogContent, { children: loadError !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBar, { intent: "error", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarBody, { children: t("app.localization-admin.merged-load-failed", [loadError]) }) }) : entries === null ? /* @__PURE__ */ jsxRuntime.jsx(reactComponents.Spinner, {}) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
14954
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: styles.dialogToolbar, children: [
|
|
14955
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14956
|
+
reactComponents.Input,
|
|
14957
|
+
{
|
|
14958
|
+
className: styles.filterInput,
|
|
14959
|
+
value: filter,
|
|
14960
|
+
onChange: (_event, data) => setFilter(data.value),
|
|
14961
|
+
contentBefore: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.SearchRegular, {}),
|
|
14962
|
+
placeholder: t("app.localization-admin.merged-filter-placeholder")
|
|
14963
|
+
}
|
|
14964
|
+
),
|
|
14965
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.lastReloaded, children: t("app.localization-admin.merged-count", [sorted.length, entries.length]) }),
|
|
14966
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.grow }),
|
|
14967
|
+
pageCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: styles.pager, children: [
|
|
14968
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14969
|
+
reactComponents.Button,
|
|
14970
|
+
{
|
|
14971
|
+
size: "small",
|
|
14972
|
+
appearance: "subtle",
|
|
14973
|
+
disabled: currentPage === 0,
|
|
14974
|
+
onClick: () => setPage(currentPage - 1),
|
|
14975
|
+
children: t("app.localization-admin.merged-prev")
|
|
14976
|
+
}
|
|
14977
|
+
),
|
|
14978
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: styles.lastReloaded, children: t("app.localization-admin.merged-page", [currentPage + 1, pageCount]) }),
|
|
14979
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
14980
|
+
reactComponents.Button,
|
|
14981
|
+
{
|
|
14982
|
+
size: "small",
|
|
14983
|
+
appearance: "subtle",
|
|
14984
|
+
disabled: currentPage >= pageCount - 1,
|
|
14985
|
+
onClick: () => setPage(currentPage + 1),
|
|
14986
|
+
children: t("app.localization-admin.merged-next")
|
|
14987
|
+
}
|
|
14988
|
+
)
|
|
14989
|
+
] })
|
|
14990
|
+
] }),
|
|
14991
|
+
sorted.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBar, { intent: "info", children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.MessageBarBody, { children: t("app.localization-admin.merged-empty") }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.dialogGrid, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
14992
|
+
reactComponents.DataGrid,
|
|
14993
|
+
{
|
|
14994
|
+
items: paged,
|
|
14995
|
+
columns,
|
|
14996
|
+
sortable: true,
|
|
14997
|
+
resizableColumns: true,
|
|
14998
|
+
sortState,
|
|
14999
|
+
onSortChange: (_event, next) => setSortState(next),
|
|
15000
|
+
columnSizingOptions,
|
|
15001
|
+
getRowId: (item) => item.key,
|
|
15002
|
+
size: "small",
|
|
15003
|
+
children: [
|
|
15004
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.DataGridHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DataGridRow, { children: ({ renderHeaderCell }) => /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DataGridHeaderCell, { children: renderHeaderCell() }) }) }),
|
|
15005
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.DataGridBody, { children: ({ item, rowId }) => /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DataGridRow, { children: ({ renderCell }) => /* @__PURE__ */ jsxRuntime.jsx(reactComponents.DataGridCell, { className: styles.mergedCell, children: renderCell(item) }) }, rowId) })
|
|
15006
|
+
]
|
|
15007
|
+
}
|
|
15008
|
+
) })
|
|
15009
|
+
] }) }),
|
|
15010
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactComponents.DialogActions, { children: [
|
|
15011
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
15012
|
+
reactComponents.Button,
|
|
15013
|
+
{
|
|
15014
|
+
appearance: "primary",
|
|
15015
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.ArrowDownloadRegular, {}),
|
|
15016
|
+
disabled: isDownloading,
|
|
15017
|
+
onClick: onDownload,
|
|
15018
|
+
children: t("app.localization-admin.merged-download-button")
|
|
15019
|
+
}
|
|
15020
|
+
),
|
|
15021
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactComponents.Button, { appearance: "secondary", icon: /* @__PURE__ */ jsxRuntime.jsx(reactIcons.DismissRegular, {}), onClick: onClose, children: t("app.buttons.close.label") })
|
|
15022
|
+
] })
|
|
15023
|
+
] }) }) });
|
|
15024
|
+
}
|
|
14681
15025
|
var useStyles13 = reactComponents.makeStyles({
|
|
14682
15026
|
root: {
|
|
14683
15027
|
display: "flex",
|