@cosmicdrift/kumiko-renderer 0.388.0 → 0.389.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/app/base-path-context.d.ts +9 -0
- package/dist/app/base-path-context.js +13 -0
- package/dist/app/base-path.d.ts +3 -0
- package/dist/app/base-path.js +27 -0
- package/dist/app/config-edit-body.d.ts +1 -1
- package/dist/app/config-edit-body.js +15 -6
- package/dist/app/config-edit-without-secret-fields.d.ts +2 -0
- package/dist/app/config-edit-without-secret-fields.js +28 -0
- package/dist/app/entity-list-facet-specs.d.ts +3 -0
- package/dist/app/entity-list-facet-specs.js +65 -0
- package/dist/app/entity-list-screen.js +74 -206
- package/dist/app/kumiko-screen.js +6 -14
- package/dist/app/list-bulk-actions.d.ts +18 -0
- package/dist/app/list-bulk-actions.js +54 -0
- package/dist/app/locked-screen-header.d.ts +9 -0
- package/dist/app/locked-screen-header.js +33 -0
- package/dist/app/projection-list-body.js +51 -46
- package/dist/app/row-actions.d.ts +3 -0
- package/dist/app/row-actions.js +10 -5
- package/dist/app/screen-shared.js +9 -2
- package/dist/app/use-entity-range-facets.d.ts +24 -0
- package/dist/app/use-entity-range-facets.js +64 -0
- package/dist/app/use-projection-date-range.d.ts +12 -0
- package/dist/app/use-projection-date-range.js +21 -0
- package/dist/app/use-screen-list-rows.d.ts +37 -0
- package/dist/app/use-screen-list-rows.js +104 -0
- package/dist/app/use-screen-list-table.d.ts +24 -0
- package/dist/app/use-screen-list-table.js +46 -0
- package/dist/components/list-group-page-bridges.d.ts +3 -0
- package/dist/components/list-group-page-bridges.js +31 -0
- package/dist/components/list-state-store.d.ts +16 -0
- package/dist/components/list-state-store.js +31 -0
- package/dist/components/related-list-section.js +37 -89
- package/dist/components/render-field.js +6 -5
- package/dist/components/render-list-toolbar-action.d.ts +27 -0
- package/dist/components/render-list-toolbar-action.js +40 -0
- package/dist/components/render-list.d.ts +13 -29
- package/dist/components/render-list.js +42 -50
- package/dist/current-user.d.ts +2 -0
- package/dist/data-table-aggregate-types.d.ts +47 -0
- package/dist/data-table-aggregate-types.js +1 -0
- package/dist/data-table-bulk-types.d.ts +27 -0
- package/dist/data-table-bulk-types.js +1 -0
- package/dist/data-table-facet-types.d.ts +36 -0
- package/dist/data-table-facet-types.js +1 -0
- package/dist/data-table-view-types.d.ts +73 -0
- package/dist/data-table-view-types.js +1 -0
- package/dist/headless/view-model/edit.js +3 -1
- package/dist/headless/view-model/types.d.ts +4 -2
- package/dist/hooks/list-column-layout.d.ts +19 -0
- package/dist/hooks/list-column-layout.js +36 -0
- package/dist/hooks/list-group-pages.d.ts +26 -0
- package/dist/hooks/list-group-pages.js +37 -0
- package/dist/hooks/list-reset.d.ts +8 -0
- package/dist/hooks/list-reset.js +15 -0
- package/dist/hooks/list-server-groups.d.ts +39 -0
- package/dist/hooks/list-server-groups.js +84 -0
- package/dist/hooks/list-state-schema.d.ts +28 -0
- package/dist/hooks/list-state-schema.js +76 -0
- package/dist/hooks/use-bulk-run.d.ts +41 -0
- package/dist/hooks/use-bulk-run.js +65 -0
- package/dist/hooks/use-group-load-state.d.ts +11 -0
- package/dist/hooks/use-group-load-state.js +31 -0
- package/dist/hooks/use-list-aggregates.d.ts +38 -0
- package/dist/hooks/use-list-aggregates.js +107 -0
- package/dist/hooks/use-list-table.d.ts +38 -0
- package/dist/hooks/use-list-table.js +91 -0
- package/dist/hooks/use-list-view-state.d.ts +43 -0
- package/dist/hooks/use-list-view-state.js +161 -0
- package/dist/hooks/use-related-list-rows.d.ts +27 -0
- package/dist/hooks/use-related-list-rows.js +81 -0
- package/dist/hooks/use-related-list-state.d.ts +15 -0
- package/dist/hooks/use-related-list-state.js +31 -0
- package/dist/hooks/use-remembered-list-params.d.ts +13 -0
- package/dist/hooks/use-remembered-list-params.js +53 -0
- package/dist/hooks/use-remembered-local-list-state.d.ts +26 -0
- package/dist/hooks/use-remembered-local-list-state.js +63 -0
- package/dist/i18n-defaults.js +53 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/primitives.d.ts +11 -47
- package/dist/web/app/create-app.d.ts +5 -0
- package/dist/web/app/create-app.js +7 -4
- package/dist/web/app/history-leave-guard.d.ts +6 -0
- package/dist/web/app/history-leave-guard.js +115 -0
- package/dist/web/app/list-state-store-boot.d.ts +6 -0
- package/dist/web/app/list-state-store-boot.js +15 -0
- package/dist/web/app/nav.js +7 -54
- package/dist/web/app/session-list-state-store.d.ts +15 -0
- package/dist/web/app/session-list-state-store.js +117 -0
- package/dist/web/index.d.ts +1 -0
- package/dist/web/index.js +1 -0
- package/dist/web/lib/export-xlsx-request.d.ts +11 -0
- package/dist/web/lib/export-xlsx-request.js +46 -0
- package/dist/web/primitives/data-table/actions-column.d.ts +16 -0
- package/dist/web/primitives/data-table/actions-column.js +34 -0
- package/dist/web/primitives/data-table/bulk-selection-bar.d.ts +20 -0
- package/dist/web/primitives/data-table/bulk-selection-bar.js +37 -0
- package/dist/web/primitives/data-table/card-sort-select.d.ts +14 -0
- package/dist/web/primitives/data-table/card-sort-select.js +40 -0
- package/dist/web/primitives/data-table/column-layout-mode.d.ts +5 -0
- package/dist/web/primitives/data-table/column-layout-mode.js +10 -0
- package/dist/web/primitives/data-table/column-picker-section.d.ts +7 -0
- package/dist/web/primitives/data-table/column-picker-section.js +35 -0
- package/dist/web/primitives/data-table/column-resize-handle.d.ts +14 -0
- package/dist/web/primitives/data-table/column-resize-handle.js +46 -0
- package/dist/web/primitives/data-table/density.d.ts +14 -0
- package/dist/web/primitives/data-table/density.js +20 -0
- package/dist/web/primitives/data-table/export-button.d.ts +5 -0
- package/dist/web/primitives/data-table/export-button.js +37 -0
- package/dist/web/primitives/data-table/facet-cluster.d.ts +8 -0
- package/dist/web/primitives/data-table/facet-cluster.js +37 -0
- package/dist/web/primitives/data-table/group-band.d.ts +28 -0
- package/dist/web/primitives/data-table/group-band.js +53 -0
- package/dist/web/primitives/data-table/measure-value.d.ts +10 -0
- package/dist/web/primitives/data-table/measure-value.js +28 -0
- package/dist/web/primitives/data-table/pin-section.d.ts +8 -0
- package/dist/web/primitives/data-table/pin-section.js +20 -0
- package/dist/web/primitives/data-table/reset-all-button.d.ts +4 -0
- package/dist/web/primitives/data-table/reset-all-button.js +8 -0
- package/dist/web/primitives/data-table/server-group-header.d.ts +11 -0
- package/dist/web/primitives/data-table/server-group-header.js +51 -0
- package/dist/web/primitives/data-table/sortable-header.d.ts +17 -0
- package/dist/web/primitives/data-table/sortable-header.js +41 -0
- package/dist/web/primitives/data-table/table-chrome.d.ts +13 -0
- package/dist/web/primitives/data-table/table-chrome.js +30 -0
- package/dist/web/primitives/data-table/table-footer.d.ts +7 -0
- package/dist/web/primitives/data-table/table-footer.js +20 -0
- package/dist/web/primitives/data-table/toolbar-end-cluster.d.ts +10 -0
- package/dist/web/primitives/data-table/toolbar-end-cluster.js +13 -0
- package/dist/web/primitives/data-table/totals-footer.d.ts +24 -0
- package/dist/web/primitives/data-table/totals-footer.js +48 -0
- package/dist/web/primitives/data-table/use-bulk-selection.d.ts +21 -0
- package/dist/web/primitives/data-table/use-bulk-selection.js +66 -0
- package/dist/web/primitives/data-table/use-column-pins.d.ts +32 -0
- package/dist/web/primitives/data-table/use-column-pins.js +117 -0
- package/dist/web/primitives/data-table/use-column-sizing.d.ts +22 -0
- package/dist/web/primitives/data-table/use-column-sizing.js +44 -0
- package/dist/web/primitives/data-table/use-measured-column-widths.d.ts +15 -0
- package/dist/web/primitives/data-table/use-measured-column-widths.js +83 -0
- package/dist/web/primitives/data-table/use-row-keyboard-navigation.d.ts +22 -0
- package/dist/web/primitives/data-table/use-row-keyboard-navigation.js +65 -0
- package/dist/web/primitives/data-table/use-row-selection.d.ts +14 -0
- package/dist/web/primitives/data-table/use-row-selection.js +48 -0
- package/dist/web/primitives/data-table/use-view-columns.d.ts +6 -0
- package/dist/web/primitives/data-table/use-view-columns.js +17 -0
- package/dist/web/primitives/data-table/view-menu.d.ts +8 -0
- package/dist/web/primitives/data-table/view-menu.js +45 -0
- package/dist/web/primitives/data-table-cell.d.ts +0 -22
- package/dist/web/primitives/data-table-cell.js +1 -58
- package/dist/web/primitives/data-table.d.ts +1 -1
- package/dist/web/primitives/data-table.js +122 -144
- package/dist/web/primitives/file-value-list.d.ts +1 -1
- package/dist/web/primitives/file-value-list.js +5 -5
- package/dist/web/primitives/number-range-facet.d.ts +7 -0
- package/dist/web/primitives/number-range-facet.js +33 -0
- package/dist/web/widgets/bounded-load-cache.d.ts +5 -1
- package/dist/web/widgets/bounded-load-cache.js +48 -26
- package/dist/web/widgets/pdf-preview.js +9 -4
- package/package.json +5 -5
- package/src/changes.json +109 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ListAggregateGroup } from "@cosmicdrift/kumiko-types/list-aggregate-types";
|
|
2
|
+
import { type ListTableGrouping } from "@cosmicdrift/kumiko-types/screen";
|
|
3
|
+
import type { DataTableMeasure, DataTableServerGroup } from "../data-table-aggregate-types.ts";
|
|
4
|
+
import type { Translate } from "../headless/index.ts";
|
|
5
|
+
import { type ListGroupPageRequest, type ListGroupPageResult, type ListGroupPaging } from "./list-group-pages.ts";
|
|
6
|
+
export type GroupLoadState = {
|
|
7
|
+
readonly resetKey: string;
|
|
8
|
+
readonly toggled: ReadonlySet<string>;
|
|
9
|
+
readonly visible: ReadonlySet<string>;
|
|
10
|
+
readonly requestedPages: ReadonlyMap<string, number>;
|
|
11
|
+
readonly results: ReadonlyMap<string, ListGroupPageResult>;
|
|
12
|
+
};
|
|
13
|
+
export type MeasureSpec = {
|
|
14
|
+
readonly field: string;
|
|
15
|
+
readonly kind: DataTableMeasure["kind"];
|
|
16
|
+
};
|
|
17
|
+
type Row = Readonly<Record<string, unknown>>;
|
|
18
|
+
export declare function freshLoadState(resetKey: string): GroupLoadState;
|
|
19
|
+
export declare function measuresOf(totals: readonly MeasureSpec[], group: ListAggregateGroup | undefined): DataTableMeasure[];
|
|
20
|
+
type ServerGroupsInput = {
|
|
21
|
+
readonly answer: readonly ListAggregateGroup[];
|
|
22
|
+
readonly groupField: string;
|
|
23
|
+
/** The declared grouping, only when the active field is the declared one. */
|
|
24
|
+
readonly declared: ListTableGrouping | undefined;
|
|
25
|
+
readonly state: GroupLoadState;
|
|
26
|
+
readonly base: Readonly<Record<string, unknown>>;
|
|
27
|
+
readonly pageSize: number;
|
|
28
|
+
readonly paging: ListGroupPaging;
|
|
29
|
+
readonly measures: readonly MeasureSpec[];
|
|
30
|
+
readonly translate: Translate;
|
|
31
|
+
};
|
|
32
|
+
export type ServerGroupsBuild = {
|
|
33
|
+
readonly groups: DataTableServerGroup[];
|
|
34
|
+
readonly rows: Row[];
|
|
35
|
+
readonly requests: ListGroupPageRequest[];
|
|
36
|
+
};
|
|
37
|
+
/** Header entries, loaded rows and the page requests of the open, visible groups. */
|
|
38
|
+
export declare function buildServerGroups(input: ServerGroupsInput): ServerGroupsBuild;
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { relatedListGroupHeaderLabel, relatedListGroupKey, } from "@cosmicdrift/kumiko-types/screen";
|
|
2
|
+
import { groupPageKey, groupPagePayload, loadedGroupPages, } from "./list-group-pages.js";
|
|
3
|
+
export function freshLoadState(resetKey) {
|
|
4
|
+
return {
|
|
5
|
+
resetKey,
|
|
6
|
+
toggled: new Set(),
|
|
7
|
+
visible: new Set(),
|
|
8
|
+
requestedPages: new Map(),
|
|
9
|
+
results: new Map(),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function measuresOf(totals, group) {
|
|
13
|
+
return totals.map((total, index) => ({
|
|
14
|
+
field: total.field,
|
|
15
|
+
kind: total.kind,
|
|
16
|
+
value: group?.measures[index] ?? null,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
function startsCollapsed(declared, key) {
|
|
20
|
+
return (declared?.collapsedWhen !== undefined && key === relatedListGroupKey(declared.collapsedWhen));
|
|
21
|
+
}
|
|
22
|
+
function headerLabel(input, key, group) {
|
|
23
|
+
const serverLabel = group.label ?? undefined;
|
|
24
|
+
const labelKey = input.declared === undefined ? undefined : relatedListGroupHeaderLabel(input.declared, key);
|
|
25
|
+
if (labelKey === undefined)
|
|
26
|
+
return serverLabel;
|
|
27
|
+
return input.translate(labelKey, { count: group.count, value: serverLabel ?? key });
|
|
28
|
+
}
|
|
29
|
+
function pageRequests(input, group, key, pages, requestedPages) {
|
|
30
|
+
const requests = [];
|
|
31
|
+
for (let pageIndex = 0; pageIndex < requestedPages; pageIndex++) {
|
|
32
|
+
const cursor = pageIndex === 0 ? undefined : (pages[pageIndex - 1]?.nextCursor ?? undefined);
|
|
33
|
+
// skip: a cursor page waits for the page before it
|
|
34
|
+
if (input.paging === "cursor" && pageIndex > 0 && cursor === undefined)
|
|
35
|
+
break;
|
|
36
|
+
requests.push({
|
|
37
|
+
pageKey: groupPageKey(key, pageIndex),
|
|
38
|
+
payload: groupPagePayload({
|
|
39
|
+
base: input.base,
|
|
40
|
+
groupField: input.groupField,
|
|
41
|
+
groupValue: group.key,
|
|
42
|
+
pageSize: input.pageSize,
|
|
43
|
+
pageIndex,
|
|
44
|
+
paging: input.paging,
|
|
45
|
+
cursor,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return requests;
|
|
50
|
+
}
|
|
51
|
+
function hasMoreRows(paging, loaded, count, lastPage) {
|
|
52
|
+
return loaded < count && (paging === "offset" || lastPage?.nextCursor !== null);
|
|
53
|
+
}
|
|
54
|
+
function buildGroup(input, group, into) {
|
|
55
|
+
const { state } = input;
|
|
56
|
+
const key = relatedListGroupKey(group.key);
|
|
57
|
+
const collapsed = startsCollapsed(input.declared, key) !== state.toggled.has(key);
|
|
58
|
+
const requestedPages = state.requestedPages.get(key) ?? 1;
|
|
59
|
+
const pages = collapsed ? [] : loadedGroupPages(state.results, key, requestedPages);
|
|
60
|
+
const loadsRows = !collapsed && state.visible.has(key);
|
|
61
|
+
if (loadsRows)
|
|
62
|
+
into.requests.push(...pageRequests(input, group, key, pages, requestedPages));
|
|
63
|
+
const groupRows = pages.flatMap((page) => page.rows);
|
|
64
|
+
into.rows.push(...groupRows);
|
|
65
|
+
const label = headerLabel(input, key, group);
|
|
66
|
+
into.groups.push({
|
|
67
|
+
key,
|
|
68
|
+
value: group.key,
|
|
69
|
+
...(label !== undefined && { label }),
|
|
70
|
+
count: group.count,
|
|
71
|
+
measures: measuresOf(input.measures, group),
|
|
72
|
+
collapsed,
|
|
73
|
+
rowCount: groupRows.length,
|
|
74
|
+
hasMore: hasMoreRows(input.paging, groupRows.length, group.count, pages[pages.length - 1]),
|
|
75
|
+
loading: loadsRows && (pages.length < requestedPages || pages.some((page) => page.loading)),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Header entries, loaded rows and the page requests of the open, visible groups. */
|
|
79
|
+
export function buildServerGroups(input) {
|
|
80
|
+
const build = { groups: [], rows: [], requests: [] };
|
|
81
|
+
for (const group of input.answer)
|
|
82
|
+
buildGroup(input, group, build);
|
|
83
|
+
return build;
|
|
84
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ListColumnPin, type ListTableDensity } from "@cosmicdrift/kumiko-types/screen";
|
|
2
|
+
export type StoredListColumn = {
|
|
3
|
+
readonly field: string;
|
|
4
|
+
readonly visible: boolean;
|
|
5
|
+
};
|
|
6
|
+
/** Remembered state of one list. `params` holds the list's URL parameters
|
|
7
|
+
* without the screen-id prefix (`sort`, `dir`, `q`, `page`, `size`, `f.<field>`). */
|
|
8
|
+
export type StoredListState = {
|
|
9
|
+
readonly params?: Readonly<Record<string, string>>;
|
|
10
|
+
readonly view?: {
|
|
11
|
+
readonly columns?: readonly StoredListColumn[];
|
|
12
|
+
readonly density?: ListTableDensity;
|
|
13
|
+
readonly pins?: Readonly<Record<string, ListColumnPin>>;
|
|
14
|
+
readonly widths?: Readonly<Record<string, number>>;
|
|
15
|
+
/** Chosen group field, "" = the user turned grouping off. */
|
|
16
|
+
readonly groupBy?: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/** Storage boundary: anything that is not a well-formed state reads as "nothing remembered". */
|
|
20
|
+
export declare function parseStoredListState(raw: unknown): StoredListState | undefined;
|
|
21
|
+
export declare function parseStoredListStateJson(text: string): StoredListState | undefined;
|
|
22
|
+
export type KnownListFields = {
|
|
23
|
+
readonly columnFields: readonly string[];
|
|
24
|
+
readonly filterFields: readonly string[];
|
|
25
|
+
};
|
|
26
|
+
/** Drops what the screen no longer has (renamed field, removed column) so an old
|
|
27
|
+
* session never feeds dead keys into the URL or the column picker. */
|
|
28
|
+
export declare function sanitizeStoredListState(raw: unknown, known: KnownListFields): StoredListState | undefined;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { LIST_COLUMN_PINS, LIST_TABLE_DENSITIES, } from "@cosmicdrift/kumiko-types/screen";
|
|
2
|
+
import * as z from "zod";
|
|
3
|
+
const storedListStateSchema = z.object({
|
|
4
|
+
params: z.record(z.string(), z.string()).optional(),
|
|
5
|
+
view: z
|
|
6
|
+
.object({
|
|
7
|
+
columns: z.array(z.object({ field: z.string(), visible: z.boolean() })).optional(),
|
|
8
|
+
density: z.enum(LIST_TABLE_DENSITIES).optional(),
|
|
9
|
+
pins: z.record(z.string(), z.enum(LIST_COLUMN_PINS)).optional(),
|
|
10
|
+
widths: z.record(z.string(), z.number().finite().positive()).optional(),
|
|
11
|
+
groupBy: z.string().optional(),
|
|
12
|
+
})
|
|
13
|
+
.optional(),
|
|
14
|
+
});
|
|
15
|
+
/** Storage boundary: anything that is not a well-formed state reads as "nothing remembered". */
|
|
16
|
+
export function parseStoredListState(raw) {
|
|
17
|
+
const parsed = storedListStateSchema.safeParse(raw);
|
|
18
|
+
return parsed.success ? parsed.data : undefined;
|
|
19
|
+
}
|
|
20
|
+
export function parseStoredListStateJson(text) {
|
|
21
|
+
try {
|
|
22
|
+
return parseStoredListState(JSON.parse(text));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const PLAIN_PARAM_SUFFIXES = new Set(["sort", "dir", "q", "page", "size"]);
|
|
29
|
+
const FILTER_PREFIX = "f.";
|
|
30
|
+
const RANGE_BOUND_SUFFIXES = [".from", ".to", ".min", ".max"];
|
|
31
|
+
function isKnownParamSuffix(suffix, filterFields) {
|
|
32
|
+
if (PLAIN_PARAM_SUFFIXES.has(suffix))
|
|
33
|
+
return true;
|
|
34
|
+
if (!suffix.startsWith(FILTER_PREFIX))
|
|
35
|
+
return false;
|
|
36
|
+
const filterKey = suffix.slice(FILTER_PREFIX.length);
|
|
37
|
+
if (filterFields.has(filterKey))
|
|
38
|
+
return true;
|
|
39
|
+
return RANGE_BOUND_SUFFIXES.some((bound) => filterKey.endsWith(bound) && filterFields.has(filterKey.slice(0, -bound.length)));
|
|
40
|
+
}
|
|
41
|
+
function onKnownFields(record, known) {
|
|
42
|
+
if (record === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
return Object.fromEntries(Object.entries(record).filter(([field]) => known.has(field)));
|
|
45
|
+
}
|
|
46
|
+
/** Drops what the screen no longer has (renamed field, removed column) so an old
|
|
47
|
+
* session never feeds dead keys into the URL or the column picker. */
|
|
48
|
+
export function sanitizeStoredListState(raw, known) {
|
|
49
|
+
const state = parseStoredListState(raw);
|
|
50
|
+
if (state === undefined)
|
|
51
|
+
return undefined;
|
|
52
|
+
const filterFields = new Set(known.filterFields);
|
|
53
|
+
const columnFields = new Set(known.columnFields);
|
|
54
|
+
const params = state.params === undefined
|
|
55
|
+
? undefined
|
|
56
|
+
: Object.fromEntries(Object.entries(state.params).filter(([suffix]) => isKnownParamSuffix(suffix, filterFields)));
|
|
57
|
+
const columns = state.view?.columns?.filter((column) => columnFields.has(column.field));
|
|
58
|
+
const density = state.view?.density;
|
|
59
|
+
const pins = onKnownFields(state.view?.pins, columnFields);
|
|
60
|
+
const widths = onKnownFields(state.view?.widths, columnFields);
|
|
61
|
+
const savedGroupBy = state.view?.groupBy;
|
|
62
|
+
const groupBy = savedGroupBy === "" || (savedGroupBy !== undefined && columnFields.has(savedGroupBy))
|
|
63
|
+
? savedGroupBy
|
|
64
|
+
: undefined;
|
|
65
|
+
const view = {
|
|
66
|
+
...(columns !== undefined && { columns }),
|
|
67
|
+
...(density !== undefined && { density }),
|
|
68
|
+
...(pins !== undefined && { pins }),
|
|
69
|
+
...(widths !== undefined && { widths }),
|
|
70
|
+
...(groupBy !== undefined && { groupBy }),
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
...(params !== undefined && { params }),
|
|
74
|
+
...(state.view !== undefined && { view }),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DataTableBulkAction } from "../data-table-bulk-types.ts";
|
|
2
|
+
import type { ListRowViewModel } from "../headless/index.ts";
|
|
3
|
+
/** Parallel row writes of one run; more would crowd out the list's own requests. */
|
|
4
|
+
export declare const BULK_WRITE_CONCURRENCY = 4;
|
|
5
|
+
export type BulkRunResult = {
|
|
6
|
+
readonly succeededIds: readonly string[];
|
|
7
|
+
readonly failedIds: readonly string[];
|
|
8
|
+
readonly skippedIds: readonly string[];
|
|
9
|
+
/** Rows a cancel kept from starting. */
|
|
10
|
+
readonly notStartedIds: readonly string[];
|
|
11
|
+
readonly firstFailureMessage?: string;
|
|
12
|
+
};
|
|
13
|
+
type BulkRunAction = Pick<DataTableBulkAction, "appliesTo" | "writeRow">;
|
|
14
|
+
/** One write per row, at most `concurrency` at a time. Cancel stops new writes; running ones finish. */
|
|
15
|
+
export declare function runBulkWrites({ rows, action, isCancelled, onRowSettled, concurrency, }: {
|
|
16
|
+
readonly rows: readonly ListRowViewModel[];
|
|
17
|
+
readonly action: BulkRunAction;
|
|
18
|
+
readonly isCancelled: () => boolean;
|
|
19
|
+
readonly onRowSettled?: () => void;
|
|
20
|
+
readonly concurrency?: number;
|
|
21
|
+
}): Promise<BulkRunResult>;
|
|
22
|
+
export type BulkRunState = {
|
|
23
|
+
readonly phase: "idle";
|
|
24
|
+
} | {
|
|
25
|
+
readonly phase: "running";
|
|
26
|
+
readonly total: number;
|
|
27
|
+
readonly settled: number;
|
|
28
|
+
readonly cancelRequested: boolean;
|
|
29
|
+
} | {
|
|
30
|
+
readonly phase: "finished";
|
|
31
|
+
readonly result: BulkRunResult;
|
|
32
|
+
};
|
|
33
|
+
export type BulkRun = {
|
|
34
|
+
readonly state: BulkRunState;
|
|
35
|
+
readonly start: (action: BulkRunAction, rows: readonly ListRowViewModel[]) => Promise<BulkRunResult>;
|
|
36
|
+
readonly cancel: () => void;
|
|
37
|
+
readonly dismissResult: () => void;
|
|
38
|
+
};
|
|
39
|
+
/** Execution model of a bulk run: progress counter, cancel and the final tally. */
|
|
40
|
+
export declare function useBulkRun(): BulkRun;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from "react";
|
|
2
|
+
/** Parallel row writes of one run; more would crowd out the list's own requests. */
|
|
3
|
+
export const BULK_WRITE_CONCURRENCY = 4;
|
|
4
|
+
function failureOf(error) {
|
|
5
|
+
return { kind: "failed", message: error instanceof Error ? error.message : String(error) };
|
|
6
|
+
}
|
|
7
|
+
/** One write per row, at most `concurrency` at a time. Cancel stops new writes; running ones finish. */
|
|
8
|
+
export async function runBulkWrites({ rows, action, isCancelled, onRowSettled, concurrency = BULK_WRITE_CONCURRENCY, }) {
|
|
9
|
+
const applicable = rows.filter((row) => action.appliesTo(row));
|
|
10
|
+
const skippedIds = rows.filter((row) => !action.appliesTo(row)).map((row) => row.id);
|
|
11
|
+
const succeededIds = [];
|
|
12
|
+
const failedIds = [];
|
|
13
|
+
let firstFailureMessage;
|
|
14
|
+
let nextIndex = 0;
|
|
15
|
+
async function drainQueue() {
|
|
16
|
+
while (nextIndex < applicable.length && !isCancelled()) {
|
|
17
|
+
const row = applicable[nextIndex];
|
|
18
|
+
nextIndex += 1;
|
|
19
|
+
if (row === undefined)
|
|
20
|
+
continue;
|
|
21
|
+
const outcome = await action.writeRow(row).catch(failureOf);
|
|
22
|
+
if (outcome.kind === "succeeded") {
|
|
23
|
+
succeededIds.push(row.id);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
failedIds.push(row.id);
|
|
27
|
+
firstFailureMessage ??= outcome.message;
|
|
28
|
+
}
|
|
29
|
+
onRowSettled?.();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
const workerCount = Math.min(concurrency, applicable.length);
|
|
33
|
+
await Promise.all(Array.from({ length: workerCount }, drainQueue));
|
|
34
|
+
return {
|
|
35
|
+
succeededIds,
|
|
36
|
+
failedIds,
|
|
37
|
+
skippedIds,
|
|
38
|
+
notStartedIds: applicable.slice(nextIndex).map((row) => row.id),
|
|
39
|
+
...(firstFailureMessage !== undefined && { firstFailureMessage }),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Execution model of a bulk run: progress counter, cancel and the final tally. */
|
|
43
|
+
export function useBulkRun() {
|
|
44
|
+
const [state, setState] = useState({ phase: "idle" });
|
|
45
|
+
const cancelledRef = useRef(false);
|
|
46
|
+
const start = useCallback(async (action, rows) => {
|
|
47
|
+
cancelledRef.current = false;
|
|
48
|
+
const total = rows.filter((row) => action.appliesTo(row)).length;
|
|
49
|
+
setState({ phase: "running", total, settled: 0, cancelRequested: false });
|
|
50
|
+
const result = await runBulkWrites({
|
|
51
|
+
rows,
|
|
52
|
+
action,
|
|
53
|
+
isCancelled: () => cancelledRef.current,
|
|
54
|
+
onRowSettled: () => setState((prev) => prev.phase === "running" ? { ...prev, settled: prev.settled + 1 } : prev),
|
|
55
|
+
});
|
|
56
|
+
setState({ phase: "finished", result });
|
|
57
|
+
return result;
|
|
58
|
+
}, []);
|
|
59
|
+
const cancel = useCallback(() => {
|
|
60
|
+
cancelledRef.current = true;
|
|
61
|
+
setState((prev) => (prev.phase === "running" ? { ...prev, cancelRequested: true } : prev));
|
|
62
|
+
}, []);
|
|
63
|
+
const dismissResult = useCallback(() => setState({ phase: "idle" }), []);
|
|
64
|
+
return { state, start, cancel, dismissResult };
|
|
65
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ListGroupPageResult } from "./list-group-pages.ts";
|
|
2
|
+
import { type GroupLoadState } from "./list-server-groups.ts";
|
|
3
|
+
export type GroupLoadControls = {
|
|
4
|
+
readonly state: GroupLoadState;
|
|
5
|
+
readonly onToggle: (key: string) => void;
|
|
6
|
+
readonly onHeaderVisible: (key: string) => void;
|
|
7
|
+
readonly onLoadMore: (key: string) => void;
|
|
8
|
+
readonly onResult: (pageKey: string, result: ListGroupPageResult) => void;
|
|
9
|
+
};
|
|
10
|
+
/** Open/visible/paged state of the server groups; starts over when `resetKey` changes. */
|
|
11
|
+
export declare function useGroupLoadState(resetKey: string): GroupLoadControls;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import { freshLoadState } from "./list-server-groups.js";
|
|
3
|
+
function withToggled(set, key) {
|
|
4
|
+
const next = new Set(set);
|
|
5
|
+
if (!next.delete(key))
|
|
6
|
+
next.add(key);
|
|
7
|
+
return next;
|
|
8
|
+
}
|
|
9
|
+
/** Open/visible/paged state of the server groups; starts over when `resetKey` changes. */
|
|
10
|
+
export function useGroupLoadState(resetKey) {
|
|
11
|
+
const [loadState, setLoadState] = useState(() => freshLoadState(resetKey));
|
|
12
|
+
const state = loadState.resetKey === resetKey ? loadState : freshLoadState(resetKey);
|
|
13
|
+
if (loadState.resetKey !== resetKey)
|
|
14
|
+
setLoadState(state);
|
|
15
|
+
const update = useCallback((change) => setLoadState((current) => (current.resetKey === resetKey ? change(current) : current)), [resetKey]);
|
|
16
|
+
const onToggle = useCallback((key) => update((current) => ({ ...current, toggled: withToggled(current.toggled, key) })), [update]);
|
|
17
|
+
const onHeaderVisible = useCallback((key) => update((current) => current.visible.has(key)
|
|
18
|
+
? current
|
|
19
|
+
: { ...current, visible: new Set([...current.visible, key]) }), [update]);
|
|
20
|
+
const onLoadMore = useCallback((key) => update((current) => {
|
|
21
|
+
const requestedPages = new Map(current.requestedPages);
|
|
22
|
+
requestedPages.set(key, (current.requestedPages.get(key) ?? 1) + 1);
|
|
23
|
+
return { ...current, requestedPages };
|
|
24
|
+
}), [update]);
|
|
25
|
+
const onResult = useCallback((pageKey, result) => update((current) => {
|
|
26
|
+
const results = new Map(current.results);
|
|
27
|
+
results.set(pageKey, result);
|
|
28
|
+
return { ...current, results };
|
|
29
|
+
}), [update]);
|
|
30
|
+
return { state, onToggle, onHeaderVisible, onLoadMore, onResult };
|
|
31
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ListTableOptions } from "@cosmicdrift/kumiko-types/screen";
|
|
2
|
+
import type { DataTableListNotice, DataTableServerGroups, DataTableTotals } from "../data-table-aggregate-types.ts";
|
|
3
|
+
import type { DispatcherError, Translate } from "../headless/index.ts";
|
|
4
|
+
import { type ListGroupPageRequest, type ListGroupPageResult, type ListGroupPaging } from "./list-group-pages.ts";
|
|
5
|
+
type ListAggregatesArgs = {
|
|
6
|
+
readonly queryType: string;
|
|
7
|
+
/** The list payload as the ungrouped list would send it; paging keys are dropped. */
|
|
8
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
9
|
+
readonly table: ListTableOptions | undefined;
|
|
10
|
+
readonly groupField: string | undefined;
|
|
11
|
+
readonly pageSize: number;
|
|
12
|
+
readonly paging: ListGroupPaging;
|
|
13
|
+
readonly translate: Translate;
|
|
14
|
+
};
|
|
15
|
+
export type ListGroupPagesProps = {
|
|
16
|
+
readonly queryType: string;
|
|
17
|
+
readonly requests: readonly ListGroupPageRequest[];
|
|
18
|
+
readonly onResult: (pageKey: string, result: ListGroupPageResult) => void;
|
|
19
|
+
readonly refreshNonce: number;
|
|
20
|
+
};
|
|
21
|
+
export type ListAggregates = {
|
|
22
|
+
readonly grouped: boolean;
|
|
23
|
+
/** Loaded rows of the open groups, in group order. */
|
|
24
|
+
readonly rows: readonly Readonly<Record<string, unknown>>[];
|
|
25
|
+
readonly serverGroups?: DataTableServerGroups;
|
|
26
|
+
readonly totals?: DataTableTotals;
|
|
27
|
+
readonly notice?: DataTableListNotice;
|
|
28
|
+
readonly groupPages: ListGroupPagesProps;
|
|
29
|
+
/** Reloads groups, totals and the loaded group pages (after a write). */
|
|
30
|
+
readonly refresh: () => void;
|
|
31
|
+
/** True until the first group answer is in. */
|
|
32
|
+
readonly loading: boolean;
|
|
33
|
+
readonly error: DispatcherError | null;
|
|
34
|
+
};
|
|
35
|
+
/** Server-side grouping and totals of a list: groups are the load unit, each open
|
|
36
|
+
* and visible group pages through its own rows; nothing is summed on the client. */
|
|
37
|
+
export declare function useListAggregates({ queryType, payload, table, groupField, pageSize, paging, translate, }: ListAggregatesArgs): ListAggregates;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { isListAggregateResult, } from "@cosmicdrift/kumiko-types/list-aggregate-types";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { withoutListPaging, } from "./list-group-pages.js";
|
|
4
|
+
import { buildServerGroups, measuresOf } from "./list-server-groups.js";
|
|
5
|
+
import { useGroupLoadState } from "./use-group-load-state.js";
|
|
6
|
+
import { useQuery } from "./use-query.js";
|
|
7
|
+
const EMPTY_BUILD = { groups: [], rows: [], requests: [] };
|
|
8
|
+
function aggregateOf(data) {
|
|
9
|
+
return isListAggregateResult(data) ? data : undefined;
|
|
10
|
+
}
|
|
11
|
+
function noticeOf(groups, totals) {
|
|
12
|
+
if (groups?.truncated === true)
|
|
13
|
+
return "truncated";
|
|
14
|
+
if (groups?.searchTruncated === true || totals?.searchTruncated === true) {
|
|
15
|
+
return "searchTruncated";
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
/** The groups and totals aggregate queries plus a refresh that reloads both. */
|
|
20
|
+
function useAggregateQueries(args) {
|
|
21
|
+
const { queryType, base, groupField, measures } = args;
|
|
22
|
+
const grouped = groupField !== undefined;
|
|
23
|
+
const withTotals = measures.length > 0;
|
|
24
|
+
const groupsPayload = useMemo(() => ({ ...base, aggregate: { groupBy: groupField, measures } }), [base, groupField, measures]);
|
|
25
|
+
const totalsPayload = useMemo(() => ({ ...base, aggregate: { measures } }), [base, measures]);
|
|
26
|
+
const groupsQuery = useQuery(queryType, groupsPayload, { live: true, enabled: grouped });
|
|
27
|
+
const totalsQuery = useQuery(queryType, totalsPayload, {
|
|
28
|
+
live: true,
|
|
29
|
+
enabled: withTotals,
|
|
30
|
+
});
|
|
31
|
+
const [refreshNonce, setRefreshNonce] = useState(0);
|
|
32
|
+
const refetchGroups = groupsQuery.refetch;
|
|
33
|
+
const refetchTotals = totalsQuery.refetch;
|
|
34
|
+
const refresh = useCallback(() => setRefreshNonce((n) => n + 1), []);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
// skip: the mount fetch already covers the first nonce
|
|
37
|
+
if (refreshNonce === 0)
|
|
38
|
+
return;
|
|
39
|
+
if (grouped)
|
|
40
|
+
void refetchGroups();
|
|
41
|
+
if (withTotals)
|
|
42
|
+
void refetchTotals();
|
|
43
|
+
}, [refreshNonce, grouped, withTotals, refetchGroups, refetchTotals]);
|
|
44
|
+
return {
|
|
45
|
+
groupsAnswer: grouped ? aggregateOf(groupsQuery.data) : undefined,
|
|
46
|
+
totalsAnswer: withTotals ? aggregateOf(totalsQuery.data) : undefined,
|
|
47
|
+
groupsError: grouped ? groupsQuery.error : null,
|
|
48
|
+
refresh,
|
|
49
|
+
refreshNonce,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Server-side grouping and totals of a list: groups are the load unit, each open
|
|
53
|
+
* and visible group pages through its own rows; nothing is summed on the client. */
|
|
54
|
+
export function useListAggregates({ queryType, payload, table, groupField, pageSize, paging, translate, }) {
|
|
55
|
+
const grouping = table?.grouping;
|
|
56
|
+
const totalSpecs = useMemo(() => table?.totals ?? [], [table?.totals]);
|
|
57
|
+
const base = useMemo(() => withoutListPaging(payload), [payload]);
|
|
58
|
+
const baseKey = JSON.stringify(base);
|
|
59
|
+
const measures = useMemo(() => totalSpecs.map((total) => ({ field: total.field, kind: total.kind })), [totalSpecs]);
|
|
60
|
+
const grouped = groupField !== undefined;
|
|
61
|
+
const { groupsAnswer, totalsAnswer, groupsError, refresh, refreshNonce } = useAggregateQueries({
|
|
62
|
+
queryType,
|
|
63
|
+
base,
|
|
64
|
+
groupField,
|
|
65
|
+
measures,
|
|
66
|
+
});
|
|
67
|
+
const { state, onToggle, onHeaderVisible, onLoadMore, onResult } = useGroupLoadState(`${groupField ?? ""}|${baseKey}`);
|
|
68
|
+
const active = groupField !== undefined && groupsAnswer !== undefined;
|
|
69
|
+
const { groups, rows, requests } = active
|
|
70
|
+
? buildServerGroups({
|
|
71
|
+
answer: groupsAnswer.groups,
|
|
72
|
+
groupField,
|
|
73
|
+
declared: grouping?.field === groupField ? grouping : undefined,
|
|
74
|
+
state,
|
|
75
|
+
base,
|
|
76
|
+
pageSize,
|
|
77
|
+
paging,
|
|
78
|
+
measures: totalSpecs,
|
|
79
|
+
translate,
|
|
80
|
+
})
|
|
81
|
+
: EMPTY_BUILD;
|
|
82
|
+
const serverGroups = active
|
|
83
|
+
? {
|
|
84
|
+
field: groupField,
|
|
85
|
+
groups,
|
|
86
|
+
totalCount: groups.reduce((sum, group) => sum + group.count, 0),
|
|
87
|
+
onToggle,
|
|
88
|
+
onLoadMore,
|
|
89
|
+
onHeaderVisible,
|
|
90
|
+
}
|
|
91
|
+
: undefined;
|
|
92
|
+
const totals = totalsAnswer !== undefined
|
|
93
|
+
? { measures: measuresOf(totalSpecs, totalsAnswer.groups[0]) }
|
|
94
|
+
: undefined;
|
|
95
|
+
const notice = noticeOf(groupsAnswer, totalsAnswer);
|
|
96
|
+
return {
|
|
97
|
+
grouped,
|
|
98
|
+
rows,
|
|
99
|
+
...(serverGroups !== undefined && { serverGroups }),
|
|
100
|
+
...(totals !== undefined && { totals }),
|
|
101
|
+
...(notice !== undefined && { notice }),
|
|
102
|
+
groupPages: { queryType, requests, onResult, refreshNonce },
|
|
103
|
+
refresh,
|
|
104
|
+
loading: grouped && groupsAnswer === undefined && groupsError === null,
|
|
105
|
+
error: groupsError,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ListTableOptions } from "@cosmicdrift/kumiko-types/screen";
|
|
2
|
+
import type { DataTableExport, DataTableResetAll, DataTableViewOptions } from "../data-table-view-types.ts";
|
|
3
|
+
import { type ListViewSchemaColumn } from "./use-list-view-state.ts";
|
|
4
|
+
type ListTableArgs = {
|
|
5
|
+
readonly storeKey: string;
|
|
6
|
+
/** Undefined: the list has no `table` block and nothing below is active. */
|
|
7
|
+
readonly table: ListTableOptions | undefined;
|
|
8
|
+
/** Must be referentially stable across renders. */
|
|
9
|
+
readonly schemaColumns: readonly ListViewSchemaColumn[];
|
|
10
|
+
/** URL-backed list (entityList, projectionList): reset clears the `<screenId>.*` keys. */
|
|
11
|
+
readonly url?: {
|
|
12
|
+
readonly screenId: string;
|
|
13
|
+
};
|
|
14
|
+
/** List with component-local state (relatedList): the owner resets it. */
|
|
15
|
+
readonly local?: {
|
|
16
|
+
readonly atDefault: boolean;
|
|
17
|
+
readonly reset: () => void;
|
|
18
|
+
};
|
|
19
|
+
/** Screen identity and current list payload; used only when `table.export.xlsx` is on. */
|
|
20
|
+
readonly exportSource?: {
|
|
21
|
+
readonly screen: string;
|
|
22
|
+
readonly screenShortId: string;
|
|
23
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export type ListTableProps = {
|
|
27
|
+
readonly view?: DataTableViewOptions;
|
|
28
|
+
readonly resetAll?: DataTableResetAll;
|
|
29
|
+
readonly exportList?: DataTableExport;
|
|
30
|
+
};
|
|
31
|
+
export type ListTableState = {
|
|
32
|
+
readonly props: ListTableProps;
|
|
33
|
+
/** Field the list is grouped by right now; undefined = ungrouped. */
|
|
34
|
+
readonly groupField: string | undefined;
|
|
35
|
+
};
|
|
36
|
+
/** Props for the DataTable's `view` / `resetAll` from a screen's `table` block. */
|
|
37
|
+
export declare function useListTable({ storeKey, table, schemaColumns, url, local, exportSource, }: ListTableArgs): ListTableState;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { useCallback, useMemo } from "react";
|
|
2
|
+
import { useNav } from "../app/nav.js";
|
|
3
|
+
import { useListStateStore } from "../components/list-state-store.js";
|
|
4
|
+
import { isListStateAtDefault, listResetUpdates } from "./list-reset.js";
|
|
5
|
+
import { useListViewState } from "./use-list-view-state.js";
|
|
6
|
+
const NO_TABLE_OPTIONS = {};
|
|
7
|
+
const PAGING_PAYLOAD_KEYS = ["limit", "offset", "cursor", "totalCount", "filter"];
|
|
8
|
+
function withoutPaging(payload) {
|
|
9
|
+
const kept = { ...payload };
|
|
10
|
+
for (const key of PAGING_PAYLOAD_KEYS)
|
|
11
|
+
delete kept[key];
|
|
12
|
+
return kept;
|
|
13
|
+
}
|
|
14
|
+
/** Props for the DataTable's `view` / `resetAll` from a screen's `table` block. */
|
|
15
|
+
export function useListTable({ storeKey, table, schemaColumns, url, local, exportSource, }) {
|
|
16
|
+
const nav = useNav();
|
|
17
|
+
const store = useListStateStore();
|
|
18
|
+
const options = table ?? NO_TABLE_OPTIONS;
|
|
19
|
+
const viewState = useListViewState({ storeKey, store, table: options, schemaColumns });
|
|
20
|
+
const urlParams = nav.searchParams;
|
|
21
|
+
const urlScreenId = url?.screenId;
|
|
22
|
+
const localAtDefault = local?.atDefault;
|
|
23
|
+
const localReset = local?.reset;
|
|
24
|
+
const atDefault = urlScreenId !== undefined
|
|
25
|
+
? isListStateAtDefault(urlParams, urlScreenId)
|
|
26
|
+
: (localAtDefault ?? true);
|
|
27
|
+
// The remembered view (columns, density) survives: "reset view" is its own action.
|
|
28
|
+
const onReset = useCallback(() => {
|
|
29
|
+
if (urlScreenId !== undefined)
|
|
30
|
+
nav.setSearchParams(listResetUpdates(urlParams, urlScreenId));
|
|
31
|
+
localReset?.();
|
|
32
|
+
const { params: _forgotten, ...kept } = store.read(storeKey) ?? {};
|
|
33
|
+
if (kept.view === undefined)
|
|
34
|
+
store.remove(storeKey);
|
|
35
|
+
else
|
|
36
|
+
store.write(storeKey, kept);
|
|
37
|
+
}, [nav, urlParams, urlScreenId, localReset, store, storeKey]);
|
|
38
|
+
const resetAll = useMemo(() => (options.resetAll === true ? { active: !atDefault, onReset } : undefined), [options.resetAll, atDefault, onReset]);
|
|
39
|
+
const hasView = options.columnPicker === true ||
|
|
40
|
+
options.density !== undefined ||
|
|
41
|
+
options.pinning === true ||
|
|
42
|
+
options.resizing === true ||
|
|
43
|
+
options.keyboardNavigation === true ||
|
|
44
|
+
viewState.groupByFields !== undefined;
|
|
45
|
+
const view = useMemo(() => hasView
|
|
46
|
+
? {
|
|
47
|
+
...(viewState.columns !== undefined && {
|
|
48
|
+
columns: viewState.columns,
|
|
49
|
+
onColumnsChange: viewState.setColumns,
|
|
50
|
+
}),
|
|
51
|
+
density: viewState.density,
|
|
52
|
+
...(viewState.userChoosesDensity && { onDensityChange: viewState.setDensity }),
|
|
53
|
+
...(viewState.pins !== undefined && {
|
|
54
|
+
pins: viewState.pins,
|
|
55
|
+
onPinsChange: viewState.setPins,
|
|
56
|
+
}),
|
|
57
|
+
...(viewState.widths !== undefined &&
|
|
58
|
+
viewState.minWidths !== undefined && {
|
|
59
|
+
widths: viewState.widths,
|
|
60
|
+
minWidths: viewState.minWidths,
|
|
61
|
+
onWidthChange: viewState.setWidth,
|
|
62
|
+
}),
|
|
63
|
+
...(options.keyboardNavigation === true && { keyboardNavigation: true }),
|
|
64
|
+
...(viewState.groupByFields !== undefined && {
|
|
65
|
+
groupBy: {
|
|
66
|
+
value: viewState.groupBy,
|
|
67
|
+
fields: viewState.groupByFields,
|
|
68
|
+
onChange: viewState.setGroupBy,
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
viewIsDefault: viewState.viewIsDefault,
|
|
72
|
+
onViewReset: viewState.resetView,
|
|
73
|
+
}
|
|
74
|
+
: undefined, [hasView, viewState, options.keyboardNavigation]);
|
|
75
|
+
const exportEnabled = options.export?.xlsx === true;
|
|
76
|
+
const viewColumns = viewState.columns;
|
|
77
|
+
const exportList = useMemo(() => {
|
|
78
|
+
if (!exportEnabled || exportSource === undefined)
|
|
79
|
+
return undefined;
|
|
80
|
+
const ordered = viewColumns?.filter((column) => column.visible).map((column) => column.field) ??
|
|
81
|
+
schemaColumns.map((column) => column.field);
|
|
82
|
+
return { ...exportSource, payload: withoutPaging(exportSource.payload), columns: ordered };
|
|
83
|
+
}, [exportEnabled, exportSource, viewColumns, schemaColumns]);
|
|
84
|
+
const props = useMemo(() => ({
|
|
85
|
+
...(view !== undefined && { view }),
|
|
86
|
+
...(resetAll !== undefined && { resetAll }),
|
|
87
|
+
...(exportList !== undefined && { exportList }),
|
|
88
|
+
}), [view, resetAll, exportList]);
|
|
89
|
+
const groupField = options.grouping !== undefined && viewState.groupBy !== "" ? viewState.groupBy : undefined;
|
|
90
|
+
return { props, groupField };
|
|
91
|
+
}
|