@ai-matrx/records-ui 0.110.4 → 0.110.6
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/CHANGELOG.md +12 -0
- package/dist/first-page.cjs +290 -35
- package/dist/first-page.cjs.map +1 -1
- package/dist/first-page.d.cts +27 -3
- package/dist/first-page.d.ts +27 -3
- package/dist/first-page.js +291 -37
- package/dist/first-page.js.map +1 -1
- package/dist/index.cjs +59 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +60 -32
- package/dist/index.js.map +1 -1
- package/dist/pickers.cjs +23 -3
- package/dist/pickers.cjs.map +1 -1
- package/dist/pickers.js +23 -3
- package/dist/pickers.js.map +1 -1
- package/package.json +3 -3
package/dist/first-page.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Uuid, RecordFilter, RuleExpression, RecordsConfig, RecordPageSort, Table, Field } from '@ai-matrx/records';
|
|
1
|
+
import { Uuid, RecordFilter, RuleExpression, RecordsConfig, StoreFilter, RecordPageSort, Table, Field } from '@ai-matrx/records';
|
|
2
2
|
import { RecordsClient, RecordsSeed } from '@ai-matrx/records/core';
|
|
3
3
|
import { TableStyle, GroupAggregateSpec, GroupOrder } from '@ai-matrx/design-system/data-table';
|
|
4
4
|
import { FieldFormatConfig } from '@ai-matrx/design-system/field-formats';
|
|
@@ -324,13 +324,18 @@ declare const TABLE_PAGE_SIZE = 100;
|
|
|
324
324
|
declare const EMBEDDED_PAGE_SIZE = 50;
|
|
325
325
|
interface FirstGridPageArgs {
|
|
326
326
|
p_table_id: Uuid;
|
|
327
|
-
p_filter: Record<string, never>;
|
|
327
|
+
p_filter: StoreFilter | Record<string, never>;
|
|
328
328
|
p_search: null;
|
|
329
329
|
p_sort: RecordPageSort[];
|
|
330
330
|
p_view_id: null;
|
|
331
331
|
p_limit: number;
|
|
332
332
|
p_offset: 0;
|
|
333
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* THE VIEW'S OWN QUESTION, as the grid asks it (`TablePage` `shownFilter`): its Rule, else its flat filter,
|
|
336
|
+
* through the one question builder. Undefined when it asks none.
|
|
337
|
+
*/
|
|
338
|
+
declare function viewFilterOf(shown: Pick<SavedViewSpec, "where" | "filters"> | null, fields: readonly Field[]): StoreFilter | undefined;
|
|
334
339
|
/** The request for the plain opening, from what the screen itself reads — or null. Pure. */
|
|
335
340
|
declare function firstGridPageArgs(input: {
|
|
336
341
|
tableId: Uuid;
|
|
@@ -339,6 +344,12 @@ declare function firstGridPageArgs(input: {
|
|
|
339
344
|
/** The view as THIS person sees it (the saved view with her look over it), or null for none. */
|
|
340
345
|
shown: SavedView | null;
|
|
341
346
|
pageSize?: number;
|
|
347
|
+
/**
|
|
348
|
+
* The host draws the MERGED grid (`RecordsUiProvider grid="merged"`), which keeps a plain grouping as rows
|
|
349
|
+
* under group headers: the page is then read in group order. The classic grid opens the drill for a
|
|
350
|
+
* grouped view, so a grouped view stays unseeded there.
|
|
351
|
+
*/
|
|
352
|
+
merged?: boolean;
|
|
342
353
|
}): FirstGridPageArgs | null;
|
|
343
354
|
/**
|
|
344
355
|
* Read what the screen reads (through `client`, e.g. one served from `custom.table_page_bundle`)
|
|
@@ -346,6 +357,7 @@ declare function firstGridPageArgs(input: {
|
|
|
346
357
|
*/
|
|
347
358
|
declare function firstGridPageRequest(client: Pick<RecordsClient, "tableRead" | "fields" | "views" | "viewLookRead">, tableId: Uuid, options?: {
|
|
348
359
|
pageSize?: number;
|
|
360
|
+
merged?: boolean;
|
|
349
361
|
}): Promise<FirstGridPageArgs | null>;
|
|
350
362
|
/**
|
|
351
363
|
* A TABLE PAGE'S SEED, ASKED BY THE SERVER AS THE PERSON (lane SSR-ROWS).
|
|
@@ -389,6 +401,18 @@ declare function askTablePageSeed(input: {
|
|
|
389
401
|
/** The table is drawn EMBEDDED (`TablePage presentation="embedded"`, a Space's inline table): its page size. */
|
|
390
402
|
embedded?: boolean;
|
|
391
403
|
pageSize?: number;
|
|
404
|
+
/**
|
|
405
|
+
* The host draws the merged grid. A view grouped by one field (no sub-levels, pivot or drill trail) is then
|
|
406
|
+
* seeded too: the page in group order, the group counts, the summary bar, and the words of the links on the
|
|
407
|
+
* page — each asked with the arguments the grid sends, so the browser's own call finds its answer.
|
|
408
|
+
*/
|
|
409
|
+
merged?: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* THE VIEW THE HOST HANDS THE GRID (a Space's own spec: sorts, filters, `where`, grouping), for a table with
|
|
412
|
+
* no saved view behind it. The first page is built from it exactly as the grid reads it; the table's saved
|
|
413
|
+
* views and looks are not asked.
|
|
414
|
+
*/
|
|
415
|
+
view?: SavedViewSpec | undefined;
|
|
392
416
|
}): Promise<RecordsSeed>;
|
|
393
417
|
|
|
394
|
-
export { EMBEDDED_PAGE_SIZE, type FirstGridPageArgs, TABLE_PAGE_SIZE, type TileSpec, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, firstGridPageArgs, firstGridPageRequest, mergeSeeds, serverRowsOf };
|
|
418
|
+
export { EMBEDDED_PAGE_SIZE, type FirstGridPageArgs, TABLE_PAGE_SIZE, type TileSpec, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, firstGridPageArgs, firstGridPageRequest, mergeSeeds, serverRowsOf, viewFilterOf };
|
package/dist/first-page.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Uuid, RecordFilter, RuleExpression, RecordsConfig, RecordPageSort, Table, Field } from '@ai-matrx/records';
|
|
1
|
+
import { Uuid, RecordFilter, RuleExpression, RecordsConfig, StoreFilter, RecordPageSort, Table, Field } from '@ai-matrx/records';
|
|
2
2
|
import { RecordsClient, RecordsSeed } from '@ai-matrx/records/core';
|
|
3
3
|
import { TableStyle, GroupAggregateSpec, GroupOrder } from '@ai-matrx/design-system/data-table';
|
|
4
4
|
import { FieldFormatConfig } from '@ai-matrx/design-system/field-formats';
|
|
@@ -324,13 +324,18 @@ declare const TABLE_PAGE_SIZE = 100;
|
|
|
324
324
|
declare const EMBEDDED_PAGE_SIZE = 50;
|
|
325
325
|
interface FirstGridPageArgs {
|
|
326
326
|
p_table_id: Uuid;
|
|
327
|
-
p_filter: Record<string, never>;
|
|
327
|
+
p_filter: StoreFilter | Record<string, never>;
|
|
328
328
|
p_search: null;
|
|
329
329
|
p_sort: RecordPageSort[];
|
|
330
330
|
p_view_id: null;
|
|
331
331
|
p_limit: number;
|
|
332
332
|
p_offset: 0;
|
|
333
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* THE VIEW'S OWN QUESTION, as the grid asks it (`TablePage` `shownFilter`): its Rule, else its flat filter,
|
|
336
|
+
* through the one question builder. Undefined when it asks none.
|
|
337
|
+
*/
|
|
338
|
+
declare function viewFilterOf(shown: Pick<SavedViewSpec, "where" | "filters"> | null, fields: readonly Field[]): StoreFilter | undefined;
|
|
334
339
|
/** The request for the plain opening, from what the screen itself reads — or null. Pure. */
|
|
335
340
|
declare function firstGridPageArgs(input: {
|
|
336
341
|
tableId: Uuid;
|
|
@@ -339,6 +344,12 @@ declare function firstGridPageArgs(input: {
|
|
|
339
344
|
/** The view as THIS person sees it (the saved view with her look over it), or null for none. */
|
|
340
345
|
shown: SavedView | null;
|
|
341
346
|
pageSize?: number;
|
|
347
|
+
/**
|
|
348
|
+
* The host draws the MERGED grid (`RecordsUiProvider grid="merged"`), which keeps a plain grouping as rows
|
|
349
|
+
* under group headers: the page is then read in group order. The classic grid opens the drill for a
|
|
350
|
+
* grouped view, so a grouped view stays unseeded there.
|
|
351
|
+
*/
|
|
352
|
+
merged?: boolean;
|
|
342
353
|
}): FirstGridPageArgs | null;
|
|
343
354
|
/**
|
|
344
355
|
* Read what the screen reads (through `client`, e.g. one served from `custom.table_page_bundle`)
|
|
@@ -346,6 +357,7 @@ declare function firstGridPageArgs(input: {
|
|
|
346
357
|
*/
|
|
347
358
|
declare function firstGridPageRequest(client: Pick<RecordsClient, "tableRead" | "fields" | "views" | "viewLookRead">, tableId: Uuid, options?: {
|
|
348
359
|
pageSize?: number;
|
|
360
|
+
merged?: boolean;
|
|
349
361
|
}): Promise<FirstGridPageArgs | null>;
|
|
350
362
|
/**
|
|
351
363
|
* A TABLE PAGE'S SEED, ASKED BY THE SERVER AS THE PERSON (lane SSR-ROWS).
|
|
@@ -389,6 +401,18 @@ declare function askTablePageSeed(input: {
|
|
|
389
401
|
/** The table is drawn EMBEDDED (`TablePage presentation="embedded"`, a Space's inline table): its page size. */
|
|
390
402
|
embedded?: boolean;
|
|
391
403
|
pageSize?: number;
|
|
404
|
+
/**
|
|
405
|
+
* The host draws the merged grid. A view grouped by one field (no sub-levels, pivot or drill trail) is then
|
|
406
|
+
* seeded too: the page in group order, the group counts, the summary bar, and the words of the links on the
|
|
407
|
+
* page — each asked with the arguments the grid sends, so the browser's own call finds its answer.
|
|
408
|
+
*/
|
|
409
|
+
merged?: boolean;
|
|
410
|
+
/**
|
|
411
|
+
* THE VIEW THE HOST HANDS THE GRID (a Space's own spec: sorts, filters, `where`, grouping), for a table with
|
|
412
|
+
* no saved view behind it. The first page is built from it exactly as the grid reads it; the table's saved
|
|
413
|
+
* views and looks are not asked.
|
|
414
|
+
*/
|
|
415
|
+
view?: SavedViewSpec | undefined;
|
|
392
416
|
}): Promise<RecordsSeed>;
|
|
393
417
|
|
|
394
|
-
export { EMBEDDED_PAGE_SIZE, type FirstGridPageArgs, TABLE_PAGE_SIZE, type TileSpec, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, firstGridPageArgs, firstGridPageRequest, mergeSeeds, serverRowsOf };
|
|
418
|
+
export { EMBEDDED_PAGE_SIZE, type FirstGridPageArgs, TABLE_PAGE_SIZE, type TileSpec, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, firstGridPageArgs, firstGridPageRequest, mergeSeeds, serverRowsOf, viewFilterOf };
|
package/dist/first-page.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { recordingDataSource, asRecordsDataSource, createRecordsClient, mapPgError } from '@ai-matrx/records/core';
|
|
2
|
-
import {
|
|
3
|
-
import '@ai-matrx/kit/text-case';
|
|
4
|
-
import '@ai-matrx/
|
|
2
|
+
import { isUuidShape } from '@ai-matrx/kit/uuid';
|
|
3
|
+
import { humanizeIdentifier } from '@ai-matrx/kit/text-case';
|
|
4
|
+
import { PARITY_FIELD_TYPES, KERNEL_TABLES, isRuleFilter, mintOpId, fieldKindFor } from '@ai-matrx/records';
|
|
5
|
+
import { dateFilterBounds, dateFilterDayBounds, dayInstantWindow } from '@ai-matrx/design-system/data-table';
|
|
6
|
+
import 'react';
|
|
5
7
|
import { parseTableStyle, tableStyleIsEmpty } from '@ai-matrx/design-system/data-table/table-style';
|
|
6
8
|
import '@ai-matrx/design-system/field-formats';
|
|
7
|
-
import { isUuidShape } from '@ai-matrx/kit/uuid';
|
|
8
9
|
import { defineChannelNamespace } from '@ai-matrx/realtime';
|
|
9
10
|
|
|
10
11
|
// src/firstPage.ts
|
|
12
|
+
function fieldName(field2) {
|
|
13
|
+
const label = (field2.label ?? "").trim();
|
|
14
|
+
if (label !== "" && label === field2.key && /^[a-z0-9]+(?:[_.-][a-z0-9]+)+$/.test(label)) return humanizeIdentifier(label);
|
|
15
|
+
if (label !== "") return label;
|
|
16
|
+
const name = (field2.name ?? "").trim();
|
|
17
|
+
if (name !== "") return name;
|
|
18
|
+
return humanizeIdentifier(field2.key);
|
|
19
|
+
}
|
|
11
20
|
var kernelId = (name) => KERNEL_TABLES.find((t) => t.name === name)?.id ?? "";
|
|
12
21
|
var KERNEL_FILE_TABLE = kernelId("File");
|
|
13
22
|
var KERNEL_PERSON_TABLE = kernelId("Person");
|
|
@@ -15,8 +24,8 @@ Object.fromEntries(
|
|
|
15
24
|
PARITY_FIELD_TYPES.map((p) => [p.parity_type, p.made_of])
|
|
16
25
|
);
|
|
17
26
|
var KERNEL = { file: KERNEL_FILE_TABLE, person: KERNEL_PERSON_TABLE };
|
|
18
|
-
function editorKindFor(
|
|
19
|
-
return fieldKindFor(
|
|
27
|
+
function editorKindFor(field2) {
|
|
28
|
+
return fieldKindFor(field2, KERNEL);
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
// src/gridLook.ts
|
|
@@ -43,25 +52,212 @@ function defaultSortOf(table, fields) {
|
|
|
43
52
|
if (String(entry["direction"] ?? "").toLowerCase() === "desc") direction = "desc";
|
|
44
53
|
}
|
|
45
54
|
if (!name) return null;
|
|
46
|
-
const
|
|
47
|
-
return
|
|
55
|
+
const field2 = fields.find((f) => f.key === name) ?? fields.find((f) => f.id === name);
|
|
56
|
+
return field2 ? { id: field2.key, direction } : null;
|
|
57
|
+
}
|
|
58
|
+
var MEASURE_LABEL = {
|
|
59
|
+
count: "Count",
|
|
60
|
+
sum: "Sum",
|
|
61
|
+
avg: "Average",
|
|
62
|
+
min: "Smallest",
|
|
63
|
+
max: "Largest",
|
|
64
|
+
median: "Median",
|
|
65
|
+
filled: "Filled",
|
|
66
|
+
empty: "Empty",
|
|
67
|
+
unique: "Unique",
|
|
68
|
+
percent_empty: "Percent empty",
|
|
69
|
+
percent_filled: "Percent filled",
|
|
70
|
+
range: "Range"
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/gridPage.ts
|
|
74
|
+
function filterRefusal(f) {
|
|
75
|
+
if (!computedOnRead(f)) return null;
|
|
76
|
+
return `${fieldName(f)} is worked out each time it is read, so the store keeps no value to filter the whole table by. Filter by a column it is worked out from.`;
|
|
77
|
+
}
|
|
78
|
+
function computedOnRead(f) {
|
|
79
|
+
const kind = String(editorKindFor(f));
|
|
80
|
+
return (f.type === "formula" || kind === "formula" || kind === "lookup" || kind === "rollup") && String(f.compute_on ?? "read") === "read";
|
|
48
81
|
}
|
|
49
|
-
function holdsNumbers(
|
|
50
|
-
const kind = String(editorKindFor(
|
|
82
|
+
function holdsNumbers(field2) {
|
|
83
|
+
const kind = String(editorKindFor(field2));
|
|
51
84
|
if (kind === "number" || kind === "currency" || kind === "percent") return true;
|
|
52
|
-
return String((
|
|
85
|
+
return String((field2.config ?? {})["system"] ?? "") === "autonumber";
|
|
53
86
|
}
|
|
54
|
-
function sortAsFor(
|
|
55
|
-
const kind = String(editorKindFor(
|
|
56
|
-
if (holdsNumbers(
|
|
87
|
+
function sortAsFor(field2) {
|
|
88
|
+
const kind = String(editorKindFor(field2));
|
|
89
|
+
if (holdsNumbers(field2)) return "number";
|
|
57
90
|
if (kind === "datetime") return "date";
|
|
58
91
|
return "text";
|
|
59
92
|
}
|
|
60
93
|
function pageSortFor(fields, sort) {
|
|
61
94
|
if (!sort) return [];
|
|
62
|
-
const
|
|
63
|
-
if (!
|
|
64
|
-
return [{ field:
|
|
95
|
+
const field2 = fields.find((f) => f.key === sort.id);
|
|
96
|
+
if (!field2) return [];
|
|
97
|
+
return [{ field: field2.key, direction: sort.direction === "desc" ? "desc" : "asc", as: sortAsFor(field2) }];
|
|
98
|
+
}
|
|
99
|
+
function containsPattern(text) {
|
|
100
|
+
return `(?i)${text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`;
|
|
101
|
+
}
|
|
102
|
+
var BLANK_VALUE = "__empty__";
|
|
103
|
+
var field = (f) => ({ field: f.id });
|
|
104
|
+
var constant = (value) => ({ const: value });
|
|
105
|
+
var withinWindow = (f, window) => ({ op: "within", args: [field(f), constant(window)] });
|
|
106
|
+
var op = (name, ...args) => ({ op: name, args });
|
|
107
|
+
function clauseFor(f, value, now = /* @__PURE__ */ new Date()) {
|
|
108
|
+
const name = fieldName(f);
|
|
109
|
+
const refused = filterRefusal(f);
|
|
110
|
+
if (refused) return refused;
|
|
111
|
+
const not = (rule) => op("not", rule);
|
|
112
|
+
switch (value.kind) {
|
|
113
|
+
case "text": {
|
|
114
|
+
const mode = value.mode ?? "contains";
|
|
115
|
+
if (mode === "empty" || mode === "null") return value.negated ? { rule: op("present", field(f)) } : { rule: not(op("present", field(f))) };
|
|
116
|
+
if (mode === "not_empty" || mode === "not_null") return value.negated ? { rule: not(op("present", field(f))) } : { rule: op("present", field(f)) };
|
|
117
|
+
const text = value.value.trim();
|
|
118
|
+
if (text === "") return null;
|
|
119
|
+
const kind = String(editorKindFor(f));
|
|
120
|
+
if (kind === "number" || kind === "currency" || kind === "percent" || kind === "checkbox") {
|
|
121
|
+
return `${name} holds numbers, so it is filtered by a range, not by words.`;
|
|
122
|
+
}
|
|
123
|
+
const matches = op("matches", field(f), constant(containsPattern(text)));
|
|
124
|
+
return { rule: value.negated ? not(matches) : matches };
|
|
125
|
+
}
|
|
126
|
+
case "select": {
|
|
127
|
+
const ticked = value.values ?? (value.value === "" ? [] : [value.value]);
|
|
128
|
+
const blank = ticked.includes(BLANK_VALUE);
|
|
129
|
+
const words = ticked.filter((w) => w !== BLANK_VALUE);
|
|
130
|
+
if (!blank && words.length === 0) return value.negated ? null : { rule: constant(false) };
|
|
131
|
+
const present = op("present", field(f));
|
|
132
|
+
const kind = String(editorKindFor(f));
|
|
133
|
+
if (kind === "datetime" || kind === "date") {
|
|
134
|
+
if (words.length === 0) return { rule: value.negated ? present : not(present) };
|
|
135
|
+
const holdsDays = String((f.config ?? {})["kind"] ?? "") === "date";
|
|
136
|
+
if (words.length === 1 && !blank && !value.negated) return { flat: { [f.key]: dayWindow(words[0], holdsDays) } };
|
|
137
|
+
const leaves = words.map((day) => withinWindow(f, dayWindow(day, holdsDays)));
|
|
138
|
+
const anyDay = leaves.length === 1 ? leaves[0] : op("or", ...leaves);
|
|
139
|
+
if (!value.negated) return { rule: blank ? op("or", not(present), anyDay) : anyDay };
|
|
140
|
+
return { rule: blank ? op("and", present, not(anyDay)) : not(anyDay) };
|
|
141
|
+
}
|
|
142
|
+
const asStored = (w) => holdsNumbers(f) || kind === "rating" ? Number.isFinite(Number(w)) ? Number(w) : w : kind === "checkbox" && (w === "true" || w === "false") ? w === "true" : w;
|
|
143
|
+
const stored = words.map(asStored);
|
|
144
|
+
const anyWord = stored.length === 0 ? null : stored.length === 1 ? op("eq", field(f), constant(stored[0])) : op("or", ...stored.map((w) => op("eq", field(f), constant(w))));
|
|
145
|
+
if (!value.negated) {
|
|
146
|
+
if (!blank && stored.length === 1) return { flat: { [f.key]: stored[0] } };
|
|
147
|
+
if (!blank) return { rule: anyWord };
|
|
148
|
+
return { rule: anyWord ? op("or", not(present), anyWord) : not(present) };
|
|
149
|
+
}
|
|
150
|
+
if (blank) return { rule: anyWord ? op("and", present, not(anyWord)) : present };
|
|
151
|
+
return { rule: op("or", not(present), not(anyWord)) };
|
|
152
|
+
}
|
|
153
|
+
case "boolean":
|
|
154
|
+
return value.negated ? { rule: op("ne", field(f), constant(value.value)) } : { flat: { [f.key]: value.value } };
|
|
155
|
+
case "number": {
|
|
156
|
+
const turned = (rule) => ({ rule: value.negated ? not(rule) : rule });
|
|
157
|
+
if (value.op === "empty") return turned(not(op("present", field(f))));
|
|
158
|
+
if (value.op === "not_empty") return turned(op("present", field(f)));
|
|
159
|
+
if (value.op === "eq" || value.op === "ne") {
|
|
160
|
+
if (typeof value.min !== "number") return null;
|
|
161
|
+
return turned(op(value.op, field(f), constant(value.min)));
|
|
162
|
+
}
|
|
163
|
+
if (value.op === "lt") return typeof value.max === "number" ? turned(op("lt", field(f), constant(value.max))) : null;
|
|
164
|
+
if (value.op === "gt") return typeof value.min === "number" ? turned(op("gt", field(f), constant(value.min))) : null;
|
|
165
|
+
const parts = [];
|
|
166
|
+
if (typeof value.min === "number") parts.push(op("gte", field(f), constant(value.min)));
|
|
167
|
+
if (typeof value.max === "number") parts.push(op("lte", field(f), constant(value.max)));
|
|
168
|
+
if (parts.length === 0) return null;
|
|
169
|
+
const range = parts.length === 1 ? parts[0] : op("and", ...parts);
|
|
170
|
+
return { rule: value.negated ? not(range) : range };
|
|
171
|
+
}
|
|
172
|
+
case "date": {
|
|
173
|
+
const zone2 = void 0;
|
|
174
|
+
const moments = dateFilterBounds(value.preset ? { ...value, anchor: now.toISOString() } : value, now, zone2);
|
|
175
|
+
const holdsDays = String((f.config ?? {})["kind"] ?? "") === "date";
|
|
176
|
+
const bounds = holdsDays ? dateFilterDayBounds(moments, zone2) : moments;
|
|
177
|
+
if (bounds.since === void 0 && bounds.until === void 0) return null;
|
|
178
|
+
return {
|
|
179
|
+
flat: {
|
|
180
|
+
[f.key]: {
|
|
181
|
+
...bounds.since === void 0 ? {} : { from: bounds.since },
|
|
182
|
+
...bounds.until === void 0 ? {} : { to: bounds.until }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
default:
|
|
188
|
+
return `${name} cannot be filtered that way by the store.`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function dayWindow(day, holdsDays) {
|
|
192
|
+
if (!holdsDays) return dayInstantWindow(day, void 0);
|
|
193
|
+
const [y, m, d] = day.split("-").map(Number);
|
|
194
|
+
const ymd = (t) => `${t.getUTCFullYear()}-${String(t.getUTCMonth() + 1).padStart(2, "0")}-${String(t.getUTCDate()).padStart(2, "0")}`;
|
|
195
|
+
return { from: ymd(new Date(Date.UTC(y, (m ?? 1) - 1, d ?? 1))), to: ymd(new Date(Date.UTC(y, (m ?? 1) - 1, (d ?? 1) + 1))) };
|
|
196
|
+
}
|
|
197
|
+
function storeQuestion(fields, base, columnFilters, now = /* @__PURE__ */ new Date()) {
|
|
198
|
+
const refused = [];
|
|
199
|
+
const flat = {};
|
|
200
|
+
const rules = [];
|
|
201
|
+
if (base && isRuleFilter(base)) rules.push(base);
|
|
202
|
+
else if (base) Object.assign(flat, base);
|
|
203
|
+
for (const [key, value] of Object.entries(columnFilters)) {
|
|
204
|
+
if (!value) continue;
|
|
205
|
+
const f = fields.find((candidate) => candidate.key === key);
|
|
206
|
+
if (!f) continue;
|
|
207
|
+
const clause = clauseFor(f, value, now);
|
|
208
|
+
if (clause === null) continue;
|
|
209
|
+
if (typeof clause === "string") {
|
|
210
|
+
refused.push(clause);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if ("flat" in clause) Object.assign(flat, clause.flat);
|
|
214
|
+
else rules.push(clause.rule);
|
|
215
|
+
}
|
|
216
|
+
const flatKeys = Object.keys(flat);
|
|
217
|
+
if (rules.length === 0) return { filter: flatKeys.length > 0 ? flat : void 0, refused };
|
|
218
|
+
for (const key of flatKeys) {
|
|
219
|
+
const f = fields.find((candidate) => candidate.key === key);
|
|
220
|
+
const value = flat[key];
|
|
221
|
+
if (!f) continue;
|
|
222
|
+
if (value !== null && typeof value === "object") {
|
|
223
|
+
rules.push(withinWindow(f, value));
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
rules.push(value === null ? op("not", op("present", field(f))) : op("eq", field(f), constant(value)));
|
|
227
|
+
}
|
|
228
|
+
return { filter: rules.length === 1 ? rules[0] : op("and", ...rules), refused };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/rowVersion.ts
|
|
232
|
+
function ownVersion(row) {
|
|
233
|
+
const v = row.version;
|
|
234
|
+
return typeof v === "number" ? v : null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/groupReads.ts
|
|
238
|
+
function isPlainGrouping(grouping) {
|
|
239
|
+
return Boolean(grouping) && !(grouping.then && grouping.then.length > 0) && !grouping.across && !(grouping.trail && grouping.trail.length > 0);
|
|
240
|
+
}
|
|
241
|
+
function groupedPageSort(fields, groupKey, sort) {
|
|
242
|
+
if (!groupKey) return sort;
|
|
243
|
+
const field2 = fields.find((f) => f.key === groupKey);
|
|
244
|
+
if (!field2) return sort;
|
|
245
|
+
return [{ field: groupKey, direction: "asc", as: sortAsFor(field2) }, ...sort.filter((s) => s.field !== groupKey)];
|
|
246
|
+
}
|
|
247
|
+
function chosenEntries(chosen) {
|
|
248
|
+
return JSON.parse(JSON.stringify(Object.entries(chosen).sort())).filter(([, op2]) => op2 in MEASURE_LABEL);
|
|
249
|
+
}
|
|
250
|
+
function groupFactMeasures(chosen) {
|
|
251
|
+
return [{ op: "count" }, ...chosenEntries(chosen).filter(([, op2]) => op2 !== "count").map(([key, op2]) => ({ op: op2, key }))];
|
|
252
|
+
}
|
|
253
|
+
function summaryBarMeasures(chosen) {
|
|
254
|
+
return chosenEntries(chosen).map(([key, op2]) => op2 === "count" ? { op: "count" } : { op: op2, key });
|
|
255
|
+
}
|
|
256
|
+
function choiceFieldKeys(fields) {
|
|
257
|
+
return fields.filter((f) => {
|
|
258
|
+
const kind = String(editorKindFor(f));
|
|
259
|
+
return kind === "select" || kind === "multi_select";
|
|
260
|
+
}).map((f) => f.key);
|
|
65
261
|
}
|
|
66
262
|
var MIN_ROW_HEIGHT = 24;
|
|
67
263
|
var MAX_ROW_HEIGHT = 96;
|
|
@@ -161,8 +357,8 @@ function parseGridPresentation(raw) {
|
|
|
161
357
|
if (footer === "sticky" || footer === "inline") out.footer = footer;
|
|
162
358
|
if (isRecord(value["summaries"])) {
|
|
163
359
|
const summaries = {};
|
|
164
|
-
for (const [key,
|
|
165
|
-
if (typeof
|
|
360
|
+
for (const [key, op2] of Object.entries(value["summaries"])) {
|
|
361
|
+
if (typeof op2 === "string" && op2.trim() !== "") summaries[key] = op2;
|
|
166
362
|
}
|
|
167
363
|
if (Object.keys(summaries).length > 0) out.summaries = summaries;
|
|
168
364
|
}
|
|
@@ -170,9 +366,9 @@ function parseGridPresentation(raw) {
|
|
|
170
366
|
}
|
|
171
367
|
function parseGrouping(raw) {
|
|
172
368
|
if (!isRecord(raw)) return void 0;
|
|
173
|
-
const
|
|
174
|
-
if (typeof
|
|
175
|
-
const out = { field };
|
|
369
|
+
const field2 = raw["field"];
|
|
370
|
+
if (typeof field2 !== "string" || field2.trim() === "") return void 0;
|
|
371
|
+
const out = { field: field2 };
|
|
176
372
|
if (isRecord(raw["aggregates"])) {
|
|
177
373
|
const aggregates = {};
|
|
178
374
|
for (const [key, spec] of Object.entries(raw["aggregates"])) {
|
|
@@ -952,39 +1148,68 @@ async function askTileSeed(input) {
|
|
|
952
1148
|
// src/firstPage.ts
|
|
953
1149
|
var TABLE_PAGE_SIZE = 100;
|
|
954
1150
|
var EMBEDDED_PAGE_SIZE = 50;
|
|
1151
|
+
function viewFilterOf(shown, fields) {
|
|
1152
|
+
if (!shown) return void 0;
|
|
1153
|
+
const base = shown.where ?? (shown.filters && Object.keys(shown.filters).length > 0 ? shown.filters : void 0);
|
|
1154
|
+
return storeQuestion(fields, base, {}).filter;
|
|
1155
|
+
}
|
|
955
1156
|
function firstGridPageArgs(input) {
|
|
956
1157
|
const { shown, fields } = input;
|
|
957
1158
|
if (!shown) return null;
|
|
958
1159
|
if ((shown.layout ?? "grid") !== "grid") return null;
|
|
959
1160
|
if (shown.ruleId) return null;
|
|
960
1161
|
if (shown.order === "manual") return null;
|
|
961
|
-
|
|
962
|
-
if (
|
|
1162
|
+
const grouping = shown.presentation?.grouping;
|
|
1163
|
+
if (grouping && !(input.merged && isPlainGrouping(grouping))) return null;
|
|
963
1164
|
const first = (shown.sorts ?? [])[0];
|
|
964
1165
|
const viewSort = first && fields.some((f) => f.key === first.field) ? { id: first.field, direction: first.direction } : null;
|
|
965
1166
|
const opening = viewSort ?? defaultSortOf(input.table, fields);
|
|
966
1167
|
return {
|
|
967
1168
|
p_table_id: input.tableId,
|
|
968
|
-
p_filter: {},
|
|
1169
|
+
p_filter: viewFilterOf(shown, fields) ?? {},
|
|
969
1170
|
p_search: null,
|
|
970
|
-
p_sort: pageSortFor(fields, opening),
|
|
1171
|
+
p_sort: groupedPageSort(fields, grouping?.field ?? null, pageSortFor(fields, opening)),
|
|
971
1172
|
p_view_id: null,
|
|
972
1173
|
p_limit: input.pageSize ?? TABLE_PAGE_SIZE,
|
|
973
1174
|
p_offset: 0
|
|
974
1175
|
};
|
|
975
1176
|
}
|
|
976
1177
|
async function firstGridPageRequest(client, tableId, options = {}) {
|
|
1178
|
+
return (await firstGridOpening(client, tableId, options))?.args ?? null;
|
|
1179
|
+
}
|
|
1180
|
+
function defaultSpec(tableId) {
|
|
1181
|
+
return { name: "All", subject: tableId, layout: "grid", isDefault: true };
|
|
1182
|
+
}
|
|
1183
|
+
function shownFromSpec(spec, tableId) {
|
|
1184
|
+
return { ...spec, subject: spec.subject ?? tableId, id: "", movedFrom: null };
|
|
1185
|
+
}
|
|
1186
|
+
async function firstGridOpening(client, tableId, options) {
|
|
1187
|
+
const explicit = options.view;
|
|
977
1188
|
const [table, fields, views, looks] = await Promise.all([
|
|
978
1189
|
client.tableRead({ table_id: tableId }),
|
|
979
1190
|
client.fields({ table_id: tableId }),
|
|
980
|
-
client.views({ table_id: tableId }),
|
|
981
|
-
readLooks(client, tableId)
|
|
1191
|
+
explicit ? Promise.resolve(null) : client.views({ table_id: tableId }),
|
|
1192
|
+
explicit ? Promise.resolve(null) : readLooks(client, tableId)
|
|
982
1193
|
]);
|
|
983
|
-
if (!table.ok || !fields.ok
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1194
|
+
if (!table.ok || !fields.ok) return null;
|
|
1195
|
+
let shown;
|
|
1196
|
+
if (explicit) {
|
|
1197
|
+
shown = shownFromSpec(explicit, tableId);
|
|
1198
|
+
} else {
|
|
1199
|
+
if (!views?.ok || !looks?.ok) return null;
|
|
1200
|
+
const mine = views.data.map(viewFromRow).filter((v) => v.subject === tableId);
|
|
1201
|
+
const view = defaultViewOf(mine) ?? mine[0] ?? null;
|
|
1202
|
+
shown = view ? applyLook(view, looks.data[view.id]) : shownFromSpec(defaultSpec(tableId), tableId);
|
|
1203
|
+
}
|
|
1204
|
+
const args = firstGridPageArgs({
|
|
1205
|
+
tableId,
|
|
1206
|
+
table: table.data,
|
|
1207
|
+
fields: fields.data,
|
|
1208
|
+
shown,
|
|
1209
|
+
...options.pageSize ? { pageSize: options.pageSize } : {},
|
|
1210
|
+
...options.merged ? { merged: true } : {}
|
|
1211
|
+
});
|
|
1212
|
+
return args ? { args, shown, fields: fields.data } : null;
|
|
988
1213
|
}
|
|
989
1214
|
function serverRowsOf(seed) {
|
|
990
1215
|
const found = seed?.answers.find((a) => a.door === "server_rows");
|
|
@@ -1006,10 +1231,11 @@ async function askTablePageSeed(input) {
|
|
|
1006
1231
|
).catch(() => null) : null;
|
|
1007
1232
|
try {
|
|
1008
1233
|
const pageSize = input.pageSize ?? (input.embedded ? EMBEDDED_PAGE_SIZE : TABLE_PAGE_SIZE);
|
|
1009
|
-
const
|
|
1234
|
+
const opening = await firstGridOpening(client, input.tableId, { pageSize, ...input.merged ? { merged: true } : {}, ...input.view ? { view: input.view } : {} });
|
|
1010
1235
|
const wantRows = typeof input.rows === "function" ? input.rows(recording.seed()) : input.rows !== false;
|
|
1011
|
-
if (
|
|
1012
|
-
|
|
1236
|
+
if (opening && wantRows) {
|
|
1237
|
+
const { args, shown, fields } = opening;
|
|
1238
|
+
const page = await client.listPage({
|
|
1013
1239
|
table_id: args.p_table_id,
|
|
1014
1240
|
filter: args.p_filter,
|
|
1015
1241
|
search: args.p_search,
|
|
@@ -1018,6 +1244,34 @@ async function askTablePageSeed(input) {
|
|
|
1018
1244
|
limit: args.p_limit,
|
|
1019
1245
|
offset: args.p_offset
|
|
1020
1246
|
});
|
|
1247
|
+
if (page.ok) {
|
|
1248
|
+
const chosen = shown?.presentation?.summaries ?? {};
|
|
1249
|
+
const groupKey = shown?.presentation?.grouping?.field ?? null;
|
|
1250
|
+
const reads = [];
|
|
1251
|
+
const filter = args.p_filter && Object.keys(args.p_filter).length > 0 ? args.p_filter : void 0;
|
|
1252
|
+
if (groupKey) {
|
|
1253
|
+
reads.push(client.recordAggregate({ table_id: input.tableId, groupBy: [groupKey], measures: groupFactMeasures(chosen), limit: 1e3, ...filter ? { filter } : {} }));
|
|
1254
|
+
}
|
|
1255
|
+
const bar = summaryBarMeasures(chosen);
|
|
1256
|
+
if (bar.length > 0) reads.push(client.recordAggregate({ table_id: input.tableId, measures: bar, ...filter ? { filter } : {} }));
|
|
1257
|
+
for (const field2 of fields) {
|
|
1258
|
+
if (field2.type !== "list" && field2.type !== "relation") continue;
|
|
1259
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1260
|
+
for (const row of page.data.rows) {
|
|
1261
|
+
const value = row.document?.[field2.key];
|
|
1262
|
+
for (const id of Array.isArray(value) ? value.map(String) : typeof value === "string" ? [value] : []) if (isUuidShape(id)) ids.add(id);
|
|
1263
|
+
}
|
|
1264
|
+
if (ids.size > 0) reads.push(client.relationWordsMany({ field_id: field2.id, record_ids: [...ids] }));
|
|
1265
|
+
}
|
|
1266
|
+
if (input.merged) {
|
|
1267
|
+
for (const key of choiceFieldKeys(fields)) {
|
|
1268
|
+
reads.push(client.recordAggregate({ table_id: input.tableId, groupBy: [key], measures: [{ op: "count" }], limit: 500, ...filter ? { filter } : {} }));
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
const unversioned = page.data.rows.filter((row) => ownVersion(row) === null).map((row) => row.id);
|
|
1272
|
+
if (unversioned.length > 0) reads.push(client.recordHeaders({ ids: unversioned }));
|
|
1273
|
+
await Promise.all(reads.map((read) => read.catch(() => null)));
|
|
1274
|
+
}
|
|
1021
1275
|
}
|
|
1022
1276
|
} catch {
|
|
1023
1277
|
}
|
|
@@ -1025,6 +1279,6 @@ async function askTablePageSeed(input) {
|
|
|
1025
1279
|
return recording.seed();
|
|
1026
1280
|
}
|
|
1027
1281
|
|
|
1028
|
-
export { EMBEDDED_PAGE_SIZE, TABLE_PAGE_SIZE, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, firstGridPageArgs, firstGridPageRequest, mergeSeeds, serverRowsOf };
|
|
1282
|
+
export { EMBEDDED_PAGE_SIZE, TABLE_PAGE_SIZE, askChartSeed, askEntityBlockSeed, askTablePageSeed, askTileSeed, firstGridPageArgs, firstGridPageRequest, mergeSeeds, serverRowsOf, viewFilterOf };
|
|
1029
1283
|
//# sourceMappingURL=first-page.js.map
|
|
1030
1284
|
//# sourceMappingURL=first-page.js.map
|