@rebasepro/cms 0.21.2-canary.g1ea48be → 0.23.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.
Files changed (58) hide show
  1. package/dist/{CollectionEditorDialog-B1mG1cfx.js → CollectionEditorDialog-DLHCka00.js} +103 -58
  2. package/dist/CollectionEditorDialog-DLHCka00.js.map +1 -0
  3. package/dist/{PropertyEditView-gKNUHxLY.js → PropertyEditView-_pu5hzZn.js} +53 -21
  4. package/dist/PropertyEditView-_pu5hzZn.js.map +1 -0
  5. package/dist/{RouterCollectionsStudioView-qgDWC6E2.js → RouterCollectionsStudioView-ChPNl_uU.js} +34 -10
  6. package/dist/RouterCollectionsStudioView-ChPNl_uU.js.map +1 -0
  7. package/dist/collection_editor/_cms_internals.d.ts +1 -0
  8. package/dist/collection_editor/relation_kinds.d.ts +11 -0
  9. package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +8 -1
  10. package/dist/collection_editor_ui.js +4 -4
  11. package/dist/components/CollectionTableBinding/fields/CompactEntityCellField.d.ts +12 -13
  12. package/dist/components/CollectionTableBinding/fields/TableMultipleRelationField.d.ts +6 -3
  13. package/dist/components/CollectionTableBinding/fields/TableReferenceField.d.ts +8 -1
  14. package/dist/components/CollectionTableBinding/fields/TableRelationField.d.ts +8 -1
  15. package/dist/components/CollectionTableBinding/fields/TableRelationSelectorField.d.ts +13 -3
  16. package/dist/components/CollectionTableBinding/fields/VirtualTableSelect.d.ts +10 -0
  17. package/dist/components/CollectionTableBinding/fields/VirtualTableUserSelect.d.ts +10 -0
  18. package/dist/components/CollectionTableBinding/internal/EntityTableCell.d.ts +21 -0
  19. package/dist/components/CollectionTableBinding/internal/TableCellOpener.d.ts +45 -0
  20. package/dist/components/CollectionTableBinding/table_bindings.d.ts +24 -0
  21. package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +7 -1
  22. package/dist/components/EntityPreviewBinding.d.ts +8 -1
  23. package/dist/components/EntityPreviewNesting.d.ts +15 -0
  24. package/dist/components/PickerTrigger.d.ts +17 -0
  25. package/dist/components/ReferenceTable/SelectionTableBinding.d.ts +12 -0
  26. package/dist/components/RelationSelector.d.ts +24 -7
  27. package/dist/components/UserSelector.d.ts +8 -1
  28. package/dist/data_export/export/additional_fields.d.ts +13 -0
  29. package/dist/data_export/export/export.d.ts +6 -0
  30. package/dist/data_import/utils/csv.d.ts +3 -2
  31. package/dist/data_import/utils/id_column.d.ts +16 -0
  32. package/dist/editor.js +1 -1
  33. package/dist/{export-DIyk1720.js → export-4bJWdlDK.js} +63 -209
  34. package/dist/export-4bJWdlDK.js.map +1 -0
  35. package/dist/form/form_utils.d.ts +13 -0
  36. package/dist/form/validation.d.ts +13 -0
  37. package/dist/{history-CrZEzQ5b.js → history-BElrZ5Rw.js} +2 -2
  38. package/dist/{history-CrZEzQ5b.js.map → history-BElrZ5Rw.js.map} +1 -1
  39. package/dist/hooks/useEntityDisplay.d.ts +4 -5
  40. package/dist/{import-ByaJMZuT.js → import-TCgkTVlS.js} +3 -4
  41. package/dist/import-TCgkTVlS.js.map +1 -0
  42. package/dist/index.js +16 -10
  43. package/dist/index.js.map +1 -1
  44. package/dist/preview/util.d.ts +25 -0
  45. package/dist/util/navigation_utils.d.ts +14 -2
  46. package/dist/util/view_mode.d.ts +12 -18
  47. package/dist/{util-DB31IoJE.js → util-CC0ozHoD.js} +35 -2
  48. package/dist/util-CC0ozHoD.js.map +1 -0
  49. package/dist/{util-qfCPxJzB.js → util-DybQBkug.js} +1482 -717
  50. package/dist/util-DybQBkug.js.map +1 -0
  51. package/package.json +9 -9
  52. package/dist/CollectionEditorDialog-B1mG1cfx.js.map +0 -1
  53. package/dist/PropertyEditView-gKNUHxLY.js.map +0 -1
  54. package/dist/RouterCollectionsStudioView-qgDWC6E2.js.map +0 -1
  55. package/dist/export-DIyk1720.js.map +0 -1
  56. package/dist/import-ByaJMZuT.js.map +0 -1
  57. package/dist/util-DB31IoJE.js.map +0 -1
  58. package/dist/util-qfCPxJzB.js.map +0 -1
@@ -20,13 +20,10 @@ export interface RelationSelectorProps {
20
20
  * scale, so a relation picker lines up with the text field beside it.
21
21
  *
22
22
  * `small` and `medium` predate that scale (42 and 56) and are kept as they
23
- *
24
- * `compact` is the 32px trigger for a selected table cell in a text row:
25
- * the chosen records as inline lines (each opens its record), a chevron,
26
- * nothing else.
27
- * are because the table cells and the filter row are built around them.
23
+ * are because the filter row is built around them. A table cell passes
24
+ * `triggerContent` instead, and has no size of its own.
28
25
  */
29
- size?: "compact" | "small" | "medium" | "large";
26
+ size?: "small" | "medium" | "large";
30
27
  useChips?: boolean;
31
28
  disabled?: boolean;
32
29
  invisible?: boolean;
@@ -64,6 +61,26 @@ export interface RelationSelectorProps {
64
61
  */
65
62
  emptyCollectionText?: string;
66
63
  loadingText?: string;
64
+ /**
65
+ * Whether the list is open, for a caller that opens it from outside the
66
+ * trigger — a table cell's opener. Every open and close the selector
67
+ * makes itself is reported through `onOpenChange`, controlled or not.
68
+ */
69
+ open?: boolean;
70
+ onOpenChange?: (open: boolean) => void;
71
+ /**
72
+ * Replaces everything the trigger draws — the value, the box, its padding
73
+ * and the chevron — with this node. For a caller that frames the picker
74
+ * itself: a table cell passes the preview it shows at rest, so the cell
75
+ * looks the same whether the picker is mounted, closed or open.
76
+ */
77
+ triggerContent?: React.ReactNode;
78
+ /**
79
+ * Positions the list against this element instead of the trigger. A
80
+ * table cell passes itself: the trigger inside it can be taller than the
81
+ * cell that clips it, and the list belongs under what can be seen.
82
+ */
83
+ anchorRef?: React.RefObject<HTMLElement | null>;
67
84
  /**
68
85
  * Whether the open list offers to create a row in the target collection.
69
86
  * Defaults to `true`. The offer is shown only when the user could actually
@@ -73,4 +90,4 @@ export interface RelationSelectorProps {
73
90
  */
74
91
  allowCreate?: boolean;
75
92
  }
76
- export declare const RelationSelector: React.ForwardRefExoticComponent<RelationSelectorProps & React.RefAttributes<HTMLButtonElement>>;
93
+ export declare const RelationSelector: React.ForwardRefExoticComponent<RelationSelectorProps & React.RefAttributes<HTMLElement>>;
@@ -21,5 +21,12 @@ export interface UserSelectorProps {
21
21
  searchPlaceholder?: string;
22
22
  noResultsText?: string;
23
23
  loadingText?: string;
24
+ /** See `RelationSelector`'s `open`. */
25
+ open?: boolean;
26
+ onOpenChange?: (open: boolean) => void;
27
+ /** See `RelationSelector`'s `triggerContent`. */
28
+ triggerContent?: React.ReactNode;
29
+ /** See `RelationSelector`'s `anchorRef`. */
30
+ anchorRef?: React.RefObject<HTMLElement | null>;
24
31
  }
25
- export declare const UserSelector: React.ForwardRefExoticComponent<UserSelectorProps & React.RefAttributes<HTMLButtonElement>>;
32
+ export declare const UserSelector: React.ForwardRefExoticComponent<UserSelectorProps & React.RefAttributes<HTMLElement>>;
@@ -0,0 +1,13 @@
1
+ import { Entity } from "@rebasepro/types";
2
+ import { AdditionalFieldDelegate, ExportMappingFunction, RebaseContext } from "@rebasepro/cms-types";
3
+ /**
4
+ * The values of the export's extra columns, one record per entity: the
5
+ * collection's `exportable.additionalFields` and its `additionalFields` that
6
+ * declare a `value`, both in the same record.
7
+ */
8
+ export declare function resolveAdditionalExportValues<M extends Record<string, unknown>>({ entities, exportFields, additionalFields, context }: {
9
+ entities: Entity<M>[];
10
+ exportFields: ExportMappingFunction[] | undefined;
11
+ additionalFields: AdditionalFieldDelegate<M>[] | undefined;
12
+ context: RebaseContext;
13
+ }): Promise<Record<string, unknown>[]>;
@@ -23,6 +23,12 @@ export declare function getEntityJsonExportableData(data: Entity<Record<string,
23
23
  * the spreadsheet as "treat the rest as text", so the value reads back intact.
24
24
  */
25
25
  export declare function escapeCsvFormula(value: string): string;
26
+ /**
27
+ * The inverse of {@link escapeCsvFormula}, for a file read back in: a cell that
28
+ * is an apostrophe followed by a formula trigger loses the apostrophe, as it
29
+ * does when a spreadsheet reads it. Any other apostrophe is text.
30
+ */
31
+ export declare function unescapeCsvFormula(value: string): string;
26
32
  export declare function entryToCSVRow(entry: unknown[]): string;
27
33
  export declare function downloadBlob(content: BlobPart[], filename: string, contentType: string): void;
28
34
  export declare function downloadDataAsCsv(data: object[], name: string): void;
@@ -2,8 +2,9 @@
2
2
  * Split CSV text into rows of raw cells, RFC 4180 style.
3
3
  *
4
4
  * Quoted fields may contain the delimiter, newlines and doubled quotes; this is
5
- * the exact inverse of what the export's `entryToCSVRow` writes, so a file this
6
- * admin produced reads back into the cells it was built from.
5
+ * the exact inverse of the quoting the export's `entryToCSVRow` writes, and
6
+ * `parseCsvToObjects` undoes its formula escape, so a file this admin produced
7
+ * reads back into the cells it was built from.
7
8
  */
8
9
  export declare function parseCsvRows(text: string, delimiter?: string): string[][];
9
10
  /**
@@ -0,0 +1,16 @@
1
+ import { Properties } from "@rebasepro/types";
2
+ /**
3
+ * The column an import uses as the row id before the user touches the mapping.
4
+ *
5
+ * The guess decides something destructive: the chosen column is taken out of
6
+ * the values and sent as the id of an upsert, so every row overwrites the record
7
+ * that already has that id. A header that merely *contained* "id" or "key" used
8
+ * to qualify, so a file starting with `width`, `video` or `provider` wrote its
9
+ * widths over the records whose ids they happened to equal.
10
+ *
11
+ * Only the first column is a candidate — the one the export writes the id to —
12
+ * and only when its name says it is the id: the header is `id` (in any case),
13
+ * or it maps onto a property the collection declares with `isId`. Anything else
14
+ * is left for the user to choose in the mapping step.
15
+ */
16
+ export declare function guessIdColumn(headers: string[], headersMapping: Record<string, string | null>, properties?: Properties): string | undefined;
package/dist/editor.js CHANGED
@@ -1,4 +1,4 @@
1
- import { r as sanitizeUrl } from "./util-DB31IoJE.js";
1
+ import { o as sanitizeUrl } from "./util-CC0ozHoD.js";
2
2
  import { i as schema, n as parser, r as serializer } from "./markdown-CCqp8ibf.js";
3
3
  import React, { createContext, forwardRef, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { BoldIcon, Button, CheckIcon, CheckSquareIcon, Checkbox, ChevronDownIcon, CodeIcon, Heading1Icon, Heading2Icon, Heading3Icon, IconButton, ImageIcon, ItalicIcon, Label, ListIcon, ListOrderedIcon, Popover, QuoteIcon, Separator, Slot, StrikethroughIcon, TableIcon, TextField, TextareaAutosize, Tooltip, Trash2Icon, TypeIcon, UnderlineIcon, Wand2Icon, cls, defaultBorderMixin, focusedDisabled, iconSize, useInjectStyles } from "@rebasepro/ui";
@@ -1,183 +1,10 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
- import { bn as useAdminContext, dn as resolveSelection, fn as selectionQueryToFindParams, mn as walkEntityPages } from "./util-qfCPxJzB.js";
2
+ import { An as useAdminContext, Gt as downloadDataAsCsv, Jt as escapeCsvFormula, Kt as downloadEntitiesExport, Sn as selectionQueryToFindParams, Wt as downloadBlob, Xt as getEntityJsonExportableData, Yt as getEntityCSVExportableData, Zt as unescapeCsvFormula, qt as entryToCSVRow, wn as walkEntityPages, xn as resolveSelection } from "./util-DybQBkug.js";
3
3
  import React, { useCallback, useMemo } from "react";
4
4
  import { Alert, BooleanSwitchWithLabel, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, DownloadIcon, IconButton, Label, RadioGroup, RadioGroupItem, Tooltip, iconSize } from "@rebasepro/ui";
5
5
  import { getDefaultValuesFor } from "@rebasepro/common";
6
6
  import { useData, useTranslation } from "@rebasepro/app";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
- import { getArrayValuesCount, getValueInPath } from "@rebasepro/utils";
9
- //#region src/data_export/export/export.ts
10
- function downloadEntitiesExport({ data, additionalData, properties, propertiesOrder, name, flattenArrays, additionalHeaders, exportType, dateExportType }) {
11
- console.debug("Downloading export", {
12
- dataLength: data.length,
13
- properties,
14
- exportType,
15
- dateExportType
16
- });
17
- if (exportType === "csv") {
18
- const headers = getExportHeaders(properties, propertiesOrder, additionalHeaders, flattenArrays ? getArrayValuesCount(data.map((d) => d.values)) : {});
19
- const exportableData = getEntityCSVExportableData(data, additionalData, properties, headers, dateExportType);
20
- downloadBlob([entryToCSVRow(headers.map((h) => h.label)), ...exportableData.map((entry) => entryToCSVRow(entry))], `${name}.csv`, "text/csv");
21
- } else {
22
- const exportableData = getEntityJsonExportableData(data, additionalData, properties, dateExportType);
23
- downloadBlob([JSON.stringify(exportableData, null, 2)], `${name}.json`, "application/json");
24
- }
25
- }
26
- function getEntityCSVExportableData(data, additionalData, properties, headers, dateExportType) {
27
- const mergedData = data.map((e) => ({
28
- id: e.id,
29
- ...processValuesForExport(e.values, properties, "csv", dateExportType)
30
- }));
31
- if (additionalData) additionalData.forEach((additional, index) => {
32
- mergedData[index] = {
33
- ...mergedData[index],
34
- ...additional
35
- };
36
- });
37
- return mergedData && mergedData.map((entry) => {
38
- return headers.map((header) => getValueInPath(entry, header.key));
39
- });
40
- }
41
- function getEntityJsonExportableData(data, additionalData, properties, dateExportType) {
42
- const mergedData = data.map((e) => ({
43
- id: e.id,
44
- ...processValuesForExport(e.values, properties, "json", dateExportType)
45
- }));
46
- if (additionalData) additionalData.forEach((additional, index) => {
47
- mergedData[index] = {
48
- ...mergedData[index],
49
- ...additional
50
- };
51
- });
52
- return mergedData;
53
- }
54
- function getExportHeaders(properties, propertiesOrder, additionalHeaders, arrayValuesCount) {
55
- const headers = [{
56
- label: "id",
57
- key: "id"
58
- }, ...(propertiesOrder ?? Object.keys(properties)).flatMap((childKey) => {
59
- const property = properties[childKey];
60
- if (!property) {
61
- console.warn("Property not found", childKey, properties);
62
- return [];
63
- }
64
- if (arrayValuesCount && arrayValuesCount[childKey] > 1) return Array.from({ length: arrayValuesCount[childKey] }, (_, i) => getHeaders(property, `${childKey}[${i}]`, "")).flat();
65
- else return getHeaders(property, childKey, "");
66
- })];
67
- if (additionalHeaders) headers.push(...additionalHeaders.map((h) => ({
68
- label: h,
69
- key: h
70
- })));
71
- return headers;
72
- }
73
- /**
74
- * Get headers for property. There could be more than one header per property
75
- * @param property
76
- * @param propertyKey
77
- * @param prefix
78
- */
79
- function getHeaders(property, propertyKey, prefix = "") {
80
- const currentKey = prefix ? `${prefix}.${propertyKey}` : propertyKey;
81
- if (property.type === "map" && property.properties) return Object.entries(property.properties).map(([childKey, p]) => getHeaders(p, childKey, currentKey)).flat();
82
- else return [{
83
- label: currentKey,
84
- key: currentKey
85
- }];
86
- }
87
- /**
88
- * Read whatever a `date` column actually arrived as into a `Date`.
89
- *
90
- * The driver/WebSocket path revives `{ __type: "date" }` into a `Date`; the REST
91
- * path — which is the one the admin is on — leaves an ISO string, and an epoch
92
- * number is what a re-imported export carries. Returns `undefined` for anything
93
- * that is not a date, so the caller can pass the raw value through.
94
- */
95
- function toExportableDate(inputValue) {
96
- if (inputValue instanceof Date) return Number.isNaN(inputValue.getTime()) ? void 0 : inputValue;
97
- if (typeof inputValue === "number" && Number.isFinite(inputValue)) return new Date(inputValue);
98
- if (typeof inputValue === "string" && inputValue.trim() !== "") {
99
- const parsed = new Date(inputValue);
100
- return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
101
- }
102
- }
103
- function processValueForExport(inputValue, property, exportType, dateExportType) {
104
- let value;
105
- if (property.type === "map" && property.properties) value = processValuesForExport(inputValue, property.properties, exportType, dateExportType);
106
- else if (property.type === "array") if (property.of && Array.isArray(inputValue)) if (Array.isArray(property.of)) value = property.of.map((p, i) => processValueForExport(inputValue[i], p, exportType, dateExportType));
107
- else if (property.of.type === "map") value = exportType === "csv" ? inputValue.map((e) => JSON.stringify(e)) : inputValue.map((e) => processValueForExport(e, property.of, exportType, dateExportType));
108
- else value = inputValue.map((e) => processValueForExport(e, property.of, exportType, dateExportType));
109
- else value = inputValue;
110
- else if (property.type === "reference" && inputValue && typeof inputValue === "object" && "isEntityReference" in inputValue && typeof inputValue.isEntityReference === "function" && inputValue.isEntityReference()) {
111
- const ref = inputValue ? inputValue : void 0;
112
- value = ref ? ref.fullPath : null;
113
- } else if (property.type === "date") {
114
- const date = toExportableDate(inputValue);
115
- value = date ? dateExportType === "timestamp" ? date.getTime() : date.toISOString() : inputValue;
116
- } else value = inputValue;
117
- return value;
118
- }
119
- function processValuesForExport(inputValues, properties, exportType, dateExportType) {
120
- const updatedValues = Object.entries(properties).map(([key, property]) => {
121
- const updatedValue = processValueForExport(inputValues && inputValues[key], property, exportType, dateExportType);
122
- if (updatedValue === void 0) return {};
123
- return { [key]: updatedValue };
124
- }).reduce((a, b) => ({
125
- ...a,
126
- ...b
127
- }), {});
128
- return {
129
- ...inputValues,
130
- ...updatedValues
131
- };
132
- }
133
- /**
134
- * Characters that make a spreadsheet read a cell as a formula rather than text.
135
- *
136
- * Quoting is CSV *escaping* and is not enough: Excel, LibreOffice Calc and
137
- * Sheets strip the CSV quoting at parse time and then evaluate a cell whose
138
- * first character is one of these — `=HYPERLINK(…)` exfiltrates neighbouring
139
- * cells on click, `=cmd|'…'!A0` is worse.
140
- */
141
- var FORMULA_TRIGGERS = [
142
- "=",
143
- "+",
144
- "-",
145
- "@",
146
- " ",
147
- "\r"
148
- ];
149
- /**
150
- * Neutralise a cell that a spreadsheet would otherwise evaluate, by prefixing a
151
- * single quote — the standard OWASP mitigation. The apostrophe is consumed by
152
- * the spreadsheet as "treat the rest as text", so the value reads back intact.
153
- */
154
- function escapeCsvFormula(value) {
155
- const first = value.charAt(0);
156
- if (!FORMULA_TRIGGERS.includes(first)) return value;
157
- if ((first === "-" || first === "+") && value.trim() !== "" && Number.isFinite(Number(value))) return value;
158
- return "'" + value;
159
- }
160
- function toCSVCell(v) {
161
- if (v === null || v === void 0) return "";
162
- return "\"" + escapeCsvFormula(Array.isArray(v) ? JSON.stringify(v) : String(v)).replaceAll("\"", "\"\"") + "\"";
163
- }
164
- function entryToCSVRow(entry) {
165
- return entry.map(toCSVCell).join(",") + "\r\n";
166
- }
167
- function downloadBlob(content, filename, contentType) {
168
- const blob = new Blob(content, { type: contentType });
169
- const url = URL.createObjectURL(blob);
170
- const pom = document.createElement("a");
171
- pom.href = url;
172
- pom.setAttribute("download", filename);
173
- pom.click();
174
- }
175
- function downloadDataAsCsv(data, name) {
176
- if (data.length === 0) return;
177
- const headers = Object.keys(data[0]);
178
- downloadBlob([[headers.join(","), ...data.map((row) => headers.map((header) => toCSVCell(row[header])).join(","))].join("\r\n")], `${name}.csv`, "text/csv");
179
- }
180
- //#endregion
181
8
  //#region src/data_export/export/fetch_export_data.ts
182
9
  /**
183
10
  * Rows asked for per request.
@@ -239,14 +66,22 @@ function BasicExportAction({ data, properties, propertiesOrder }) {
239
66
  additionalData: [],
240
67
  properties,
241
68
  propertiesOrder,
242
- name: "export.csv",
69
+ name: "export",
243
70
  flattenArrays,
244
71
  additionalHeaders: [],
245
72
  exportType,
246
73
  dateExportType
247
74
  });
248
75
  handleClose();
249
- }, []);
76
+ }, [
77
+ data,
78
+ properties,
79
+ propertiesOrder,
80
+ flattenArrays,
81
+ exportType,
82
+ dateExportType,
83
+ handleClose
84
+ ]);
250
85
  return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Tooltip, {
251
86
  title: "Export",
252
87
  asChild: true,
@@ -354,8 +189,7 @@ function BasicExportAction({ data, properties, propertiesOrder }) {
354
189
  })] });
355
190
  }
356
191
  //#endregion
357
- //#region src/data_export/export/ExportCollectionAction.tsx
358
- var DOCS_LIMIT = 500;
192
+ //#region src/data_export/export/additional_fields.ts
359
193
  /**
360
194
  * Additional-field builders run per row and may do I/O, so a `Promise.all` over
361
195
  * the whole export is one request per row all at once. That was bounded only by
@@ -368,6 +202,43 @@ async function mapInChunks(items, fn, chunkSize = ADDITIONAL_FIELDS_CONCURRENCY)
368
202
  for (let i = 0; i < items.length; i += chunkSize) results.push(...await Promise.all(items.slice(i, i + chunkSize).map(fn)));
369
203
  return results;
370
204
  }
205
+ /**
206
+ * The values of the export's extra columns, one record per entity: the
207
+ * collection's `exportable.additionalFields` and its `additionalFields` that
208
+ * declare a `value`, both in the same record.
209
+ */
210
+ async function resolveAdditionalExportValues({ entities, exportFields, additionalFields, context }) {
211
+ const resolvedExportColumnsValues = exportFields ? await mapInChunks(entities, async (entity) => {
212
+ return (await Promise.all(exportFields.map(async (column) => {
213
+ return { [column.key]: await column.builder({
214
+ entity,
215
+ context
216
+ }) };
217
+ }))).reduce((a, b) => ({
218
+ ...a,
219
+ ...b
220
+ }), {});
221
+ }) : [];
222
+ const resolvedColumnsValues = additionalFields ? await mapInChunks(entities, async (entity) => {
223
+ return (await Promise.all(additionalFields.map(async (field) => {
224
+ if (!field.value) return {};
225
+ return { [field.key]: await field.value({
226
+ entity,
227
+ context
228
+ }) };
229
+ }))).reduce((a, b) => ({
230
+ ...a,
231
+ ...b
232
+ }), {});
233
+ }) : [];
234
+ return entities.map((_, i) => ({
235
+ ...resolvedExportColumnsValues[i],
236
+ ...resolvedColumnsValues[i]
237
+ }));
238
+ }
239
+ //#endregion
240
+ //#region src/data_export/export/ExportCollectionAction.tsx
241
+ var DOCS_LIMIT = 500;
371
242
  function ExportCollectionAction({ collection, path, collectionEntitiesCount, selectionController, tableController, onAnalyticsEvent, exportAllowed, notAllowedView }) {
372
243
  const { t } = useTranslation();
373
244
  const exportConfig = typeof collection.exportable === "object" ? collection.exportable : void 0;
@@ -407,34 +278,16 @@ function ExportCollectionAction({ collection, path, collectionEntitiesCount, sel
407
278
  const handleClose = useCallback(() => {
408
279
  setOpen(false);
409
280
  }, [setOpen]);
410
- const fetchAdditionalFields = useCallback(async (entities) => {
411
- const additionalExportFields = exportConfig?.additionalFields;
412
- const additionalFields = collection.additionalFields;
413
- const resolvedExportColumnsValues = additionalExportFields ? await mapInChunks(entities, async (entity) => {
414
- return (await Promise.all(additionalExportFields.map(async (column) => {
415
- return { [column.key]: await column.builder({
416
- entity,
417
- context
418
- }) };
419
- }))).reduce((a, b) => ({
420
- ...a,
421
- ...b
422
- }), {});
423
- }) : [];
424
- const resolvedColumnsValues = additionalFields ? await mapInChunks(entities, async (entity) => {
425
- return (await Promise.all(additionalFields.map(async (field) => {
426
- if (!field.value) return {};
427
- return { [field.key]: await field.value({
428
- entity,
429
- context
430
- }) };
431
- }))).reduce((a, b) => ({
432
- ...a,
433
- ...b
434
- }), {});
435
- }) : [];
436
- return [...resolvedExportColumnsValues, ...resolvedColumnsValues];
437
- }, [exportConfig?.additionalFields]);
281
+ const fetchAdditionalFields = useCallback((entities) => resolveAdditionalExportValues({
282
+ entities,
283
+ exportFields: exportConfig?.additionalFields,
284
+ additionalFields: collection.additionalFields,
285
+ context
286
+ }), [
287
+ exportConfig?.additionalFields,
288
+ collection.additionalFields,
289
+ context
290
+ ]);
438
291
  const doDownload = useCallback(async (collection, exportConfig) => {
439
292
  onAnalyticsEvent?.("export_collection", { collection: collection.slug });
440
293
  setDataLoading(true);
@@ -658,9 +511,10 @@ var export_exports = /* @__PURE__ */ __exportAll({
658
511
  escapeCsvFormula: () => escapeCsvFormula,
659
512
  fetchAllEntitiesForExport: () => fetchAllEntitiesForExport,
660
513
  getEntityCSVExportableData: () => getEntityCSVExportableData,
661
- getEntityJsonExportableData: () => getEntityJsonExportableData
514
+ getEntityJsonExportableData: () => getEntityJsonExportableData,
515
+ unescapeCsvFormula: () => unescapeCsvFormula
662
516
  });
663
517
  //#endregion
664
- export { MAX_EXPORT_ROWS as a, downloadDataAsCsv as c, escapeCsvFormula as d, getEntityCSVExportableData as f, EXPORT_PAGE_SIZE as i, downloadEntitiesExport as l, ExportCollectionAction as n, fetchAllEntitiesForExport as o, getEntityJsonExportableData as p, BasicExportAction as r, downloadBlob as s, export_exports as t, entryToCSVRow as u };
518
+ export { MAX_EXPORT_ROWS as a, EXPORT_PAGE_SIZE as i, ExportCollectionAction as n, fetchAllEntitiesForExport as o, BasicExportAction as r, export_exports as t };
665
519
 
666
- //# sourceMappingURL=export-DIyk1720.js.map
520
+ //# sourceMappingURL=export-4bJWdlDK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-4bJWdlDK.js","names":[],"sources":["../src/data_export/export/fetch_export_data.ts","../src/data_export/export/BasicExportAction.tsx","../src/data_export/export/additional_fields.ts","../src/data_export/export/ExportCollectionAction.tsx","../src/data_export/export/index.ts"],"sourcesContent":["import { Entity, FindParams } from \"@rebasepro/types\";\nimport { EntityPageAccessor, walkEntityPages } from \"../../util/entity_pages\";\n\n/**\n * Rows asked for per request.\n *\n * Kept at or below the server's `MAX_LIST_LIMIT`, which is now the largest page\n * a read will serve *and refuses* anything larger — it used to clamp silently,\n * and asking for 100 000 while receiving 1 000 looked exactly like a collection\n * with 1 000 rows in it. The walk still never trusts the page size it asked for:\n * it advances by the rows it was actually handed.\n */\nexport const EXPORT_PAGE_SIZE = 500;\n\n/**\n * Most rows one browser-side export will materialise.\n *\n * Everything here is built in memory and handed to a Blob, so there is a\n * ceiling whether or not it is named. Naming it means going over it **throws**\n * instead of writing a short file that reads like the whole collection.\n */\nexport const MAX_EXPORT_ROWS = 100_000;\n\nexport interface FetchAllForExportParams<M extends Record<string, unknown>> {\n /** The collection accessor to read through — `dataClient.collection(path)`. */\n accessor: EntityPageAccessor<M>;\n /** Query to export. The window (`limit` / `offset` / `page`) is owned by the walk. */\n params?: Omit<FindParams<M>, \"limit\" | \"offset\" | \"page\">;\n pageSize?: number;\n maxRows?: number;\n /** Called after every page with the rows so far, and the total when the server reports one. */\n onProgress?: (loaded: number, total: number | undefined) => void;\n}\n\n/**\n * Read **every** row matching a query, for an export file.\n *\n * The export used to hand back the 50 rows an unwindowed `find({})` resolves to\n * as `<collection>.csv`, with nothing saying it was a sample, next to a dialog\n * warning that the collection had 100 000 documents. The walk is in\n * {@link walkEntityPages}; what belongs to export is the ceiling — an export is\n * built in memory and handed to a Blob — and what to say on hitting it.\n *\n * @throws When more than `maxRows` rows match.\n */\nexport async function fetchAllEntitiesForExport<M extends Record<string, unknown>>({\n accessor,\n params,\n pageSize = EXPORT_PAGE_SIZE,\n maxRows = MAX_EXPORT_ROWS,\n onProgress\n}: FetchAllForExportParams<M>): Promise<Entity<M>[]> {\n return walkEntityPages<M>({\n accessor,\n params,\n pageSize,\n maxRows,\n onProgress,\n onCeiling: (max) =>\n `This export would contain more than ${max.toLocaleString()} rows, which is more than the ` +\n \"browser can build in one file. Filter the collection down, or export it from the server.\"\n });\n}\n","\nimport React, { useCallback } from \"react\";\n\nimport { useTranslation } from \"@rebasepro/app\";\nimport { Entity, Properties } from \"@rebasepro/types\";\nimport {\n BooleanSwitchWithLabel,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n DownloadIcon,\n IconButton,\n iconSize,\n Label,\n RadioGroup,\n RadioGroupItem,\n Tooltip,\n Typography\n} from \"@rebasepro/ui\";\nimport { downloadEntitiesExport } from \"./export\";\n\nexport type BasicExportActionProps = {\n data: Entity<any>[];\n properties: Properties;\n propertiesOrder?: string[];\n}\n\nexport function BasicExportAction({\n data,\n properties,\n propertiesOrder\n}: BasicExportActionProps) {\n\n const { t } = useTranslation();\n const dateRef = React.useRef<Date>(new Date());\n const [flattenArrays, setFlattenArrays] = React.useState<boolean>(true);\n const [exportType, setExportType] = React.useState<\"csv\" | \"json\">(\"csv\");\n const [dateExportType, setDateExportType] = React.useState<\"timestamp\" | \"string\">(\"string\");\n\n const [open, setOpen] = React.useState(false);\n\n const handleClickOpen = useCallback(() => {\n setOpen(true);\n }, [setOpen]);\n\n const handleClose = useCallback(() => {\n setOpen(false);\n }, [setOpen]);\n\n const onOkClicked = useCallback(() => {\n downloadEntitiesExport({\n data,\n additionalData: [],\n properties,\n propertiesOrder,\n // The writer appends the extension for the chosen format.\n name: \"export\",\n flattenArrays,\n additionalHeaders: [],\n exportType,\n dateExportType\n });\n handleClose();\n }, [data, properties, propertiesOrder, flattenArrays, exportType, dateExportType, handleClose]);\n\n return <>\n\n <Tooltip title={\"Export\"}\n asChild={true}>\n <IconButton\n size={\"small\"}\n color={\"primary\"} onClick={handleClickOpen}>\n <DownloadIcon\n size={iconSize.small}/>\n </IconButton>\n </Tooltip>\n\n <Dialog\n open={open}\n onOpenChange={setOpen}\n maxWidth={\"xl\"}>\n\n <DialogTitle variant={\"h6\"}>{t(\"export_data\")}</DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-4 my-4\"}>\n\n <div>{t(\"download_table_csv\")}</div>\n\n <div className={\"flex flex-row gap-4\"}>\n <div className={\"p-4 flex flex-col\"}>\n <RadioGroup value={exportType} onValueChange={(v) => setExportType(v as \"csv\" | \"json\")}>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"csv\" id=\"radio-csv\"/>\n <Label htmlFor=\"radio-csv\">{t(\"csv\")}</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"json\" id=\"radio-json\"/>\n <Label htmlFor=\"radio-json\">{t(\"json\")}</Label>\n </div>\n </RadioGroup>\n </div>\n\n <div className={\"p-4 flex flex-col\"}>\n <RadioGroup value={dateExportType} onValueChange={(v) => setDateExportType(v as \"timestamp\" | \"string\")}>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"timestamp\" id=\"radio-timestamp\"/>\n <Label htmlFor=\"radio-timestamp\">{t(\"dates_as_timestamps\")} ({dateRef.current.getTime()})</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"string\" id=\"radio-string\"/>\n <Label htmlFor=\"radio-string\">{t(\"dates_as_strings\")} ({dateRef.current.toISOString()})</Label>\n </div>\n </RadioGroup>\n </div>\n </div>\n\n <BooleanSwitchWithLabel\n size={\"small\"}\n disabled={exportType !== \"csv\"}\n value={flattenArrays}\n onValueChange={setFlattenArrays}\n label={t(\"flatten_arrays\")}/>\n\n </DialogContent>\n\n <DialogActions>\n\n <Button onClick={handleClose}\n variant={\"text\"}>\n {t(\"cancel\")}\n </Button>\n\n <Button onClick={onOkClicked}>\n {t(\"download\")}\n </Button>\n\n </DialogActions>\n\n </Dialog>\n\n </>;\n}\n","import { Entity } from \"@rebasepro/types\";\nimport { AdditionalFieldDelegate, ExportMappingFunction, RebaseContext } from \"@rebasepro/cms-types\";\n\n/**\n * Additional-field builders run per row and may do I/O, so a `Promise.all` over\n * the whole export is one request per row all at once. That was bounded only by\n * the 50-row cap the export used to have; now that the read is paginated, it is\n * bounded here instead.\n */\nconst ADDITIONAL_FIELDS_CONCURRENCY = 50;\n\nasync function mapInChunks<T, R>(items: T[], fn: (item: T) => Promise<R>, chunkSize = ADDITIONAL_FIELDS_CONCURRENCY): Promise<R[]> {\n const results: R[] = [];\n for (let i = 0; i < items.length; i += chunkSize) {\n results.push(...await Promise.all(items.slice(i, i + chunkSize).map(fn)));\n }\n return results;\n}\n\n/**\n * The values of the export's extra columns, one record per entity: the\n * collection's `exportable.additionalFields` and its `additionalFields` that\n * declare a `value`, both in the same record.\n */\nexport async function resolveAdditionalExportValues<M extends Record<string, unknown>>({\n entities,\n exportFields,\n additionalFields,\n context\n}: {\n entities: Entity<M>[];\n exportFields: ExportMappingFunction[] | undefined;\n additionalFields: AdditionalFieldDelegate<M>[] | undefined;\n context: RebaseContext;\n}): Promise<Record<string, unknown>[]> {\n\n const resolvedExportColumnsValues: Record<string, unknown>[] = exportFields\n ? await mapInChunks(entities, async (entity) => {\n return (await Promise.all(exportFields.map(async (column) => {\n return {\n [column.key]: await column.builder({\n entity,\n context\n })\n };\n }))).reduce((a, b) => ({ ...a,\n...b }), {});\n })\n : [];\n\n const resolvedColumnsValues: Record<string, unknown>[] = additionalFields\n ? await mapInChunks(entities, async (entity) => {\n return (await Promise.all(additionalFields\n .map(async (field) => {\n if (!field.value)\n return {};\n return {\n [field.key]: await field.value({\n entity,\n context\n })\n };\n }))).reduce((a, b) => ({ ...a,\n...b }), {});\n })\n : [];\n // One record per entity, index for index — the export merges\n // `additionalData[i]` into row `i`.\n return entities.map((_, i) => ({\n ...resolvedExportColumnsValues[i],\n ...resolvedColumnsValues[i]\n }));\n}\n","\nimport React, { useCallback, useMemo } from \"react\";\n\nimport {\n useAuthController,\n useCustomizationController,\n useData,\n useTranslation\n} from \"@rebasepro/app\";\nimport { useAdminContext } from \"../../hooks\";\nimport { Entity, User } from \"@rebasepro/types\";\nimport { CollectionActionsProps, ExportConfig, RebaseContext, AdminCollection, SelectionQuery } from \"@rebasepro/cms-types\";\nimport { getDefaultValuesFor } from \"@rebasepro/common\";\nimport {\n Alert,\n BooleanSwitchWithLabel,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n DownloadIcon,\n IconButton,\n iconSize,\n Label,\n RadioGroup,\n RadioGroupItem,\n Tooltip\n} from \"@rebasepro/ui\";\nimport { downloadEntitiesExport } from \"./export\";\nimport { fetchAllEntitiesForExport, MAX_EXPORT_ROWS } from \"./fetch_export_data\";\nimport { resolveAdditionalExportValues } from \"./additional_fields\";\nimport { resolveSelection, selectionQueryToFindParams } from \"../../selection\";\n\nconst DOCS_LIMIT = 500;\n\nexport function ExportCollectionAction<M extends Record<string, unknown>, USER extends User>({\n collection,\n path,\n collectionEntitiesCount,\n selectionController,\n tableController,\n onAnalyticsEvent,\n exportAllowed,\n notAllowedView\n}: CollectionActionsProps<M, USER, AdminCollection<M>> & {\n exportAllowed?: (props: { collectionEntitiesCount: number, path: string, collection: AdminCollection }) => boolean;\n notAllowedView?: React.ReactNode;\n onAnalyticsEvent?: (event: string, params?: any) => void;\n}) {\n\n const { t } = useTranslation();\n\n const exportConfig = typeof collection.exportable === \"object\" ? collection.exportable : undefined;\n\n const dateRef = React.useRef<Date>(new Date());\n\n const [includeUndefinedValues, setIncludeUndefinedValues] = React.useState<boolean>(false);\n const [flattenArrays, setFlattenArrays] = React.useState<boolean>(true);\n const [exportType, setExportType] = React.useState<\"csv\" | \"json\">(\"csv\");\n const [dateExportType, setDateExportType] = React.useState<\"timestamp\" | \"string\">(\"string\");\n\n const context = useAdminContext<USER>();\n const dataClient = useData();\n\n // A selection narrows the export to it; with none, the export is the view\n // — filter, search and sort included.\n const exportSelection = selectionController?.hasSelection ? selectionController.selection : undefined;\n\n const liveQuery: SelectionQuery<M> = useMemo(() => ({\n path,\n filterValues: tableController?.filterValues,\n searchString: tableController?.searchString,\n sortBy: tableController?.sortBy\n }), [path, tableController?.filterValues, tableController?.searchString, tableController?.sortBy]);\n\n // Said before the download starts, not discovered halfway through it: the\n // walk refuses rather than writing a short file, so the dialog has to name\n // the ceiling it is about to hit.\n const rowsToExport = exportSelection ? selectionController?.selectedCount : collectionEntitiesCount;\n const tooManyToExport = rowsToExport !== undefined && rowsToExport > MAX_EXPORT_ROWS;\n\n const canExport = !exportAllowed || exportAllowed({\n collectionEntitiesCount: collectionEntitiesCount ?? 0,\n path,\n collection\n });\n\n const [dataLoading, setDataLoading] = React.useState<boolean>(false);\n const [dataLoadingError, setDataLoadingError] = React.useState<Error | undefined>();\n const [progress, setProgress] = React.useState<{ loaded: number, total?: number }>({ loaded: 0 });\n\n const [open, setOpen] = React.useState(false);\n\n const handleClickOpen = useCallback(() => {\n setOpen(true);\n }, [setOpen]);\n\n const handleClose = useCallback(() => {\n setOpen(false);\n }, [setOpen]);\n\n const fetchAdditionalFields = useCallback((entities: Entity<M>[]) => resolveAdditionalExportValues({\n entities,\n exportFields: exportConfig?.additionalFields,\n additionalFields: collection.additionalFields,\n context: context as RebaseContext\n }), [exportConfig?.additionalFields, collection.additionalFields, context]);\n\n const doDownload = useCallback(async (collection: AdminCollection<M>,\n exportConfig: ExportConfig<any> | undefined): Promise<boolean> => {\n\n onAnalyticsEvent?.(\"export_collection\", {\n collection: collection.slug\n });\n setDataLoading(true);\n setDataLoadingError(undefined);\n setProgress({ loaded: 0 });\n try {\n const onProgress = (loaded: number, total: number | undefined) => setProgress({ loaded, total });\n const accessor = dataClient.collection(path) as { find: (params?: any) => Promise<any> };\n\n // What the user is looking at, or what they picked out of it.\n //\n // Neither used to be true: the export read the collection with no\n // params at all, so a filtered, searched table of 40 rows exported\n // every row in the table — and ticking rows first changed nothing.\n const data = exportSelection\n ? await resolveSelection<M>({\n selection: exportSelection,\n accessor,\n maxRows: MAX_EXPORT_ROWS,\n onProgress\n })\n // Paginated, not `find({})`: an absent limit resolves to 50 rows\n // server-side, so the export used to be the first page of the\n // collection under a filename that read like all of it.\n : await fetchAllEntitiesForExport<M>({\n accessor,\n params: selectionQueryToFindParams(liveQuery),\n onProgress\n });\n const additionalData = await fetchAdditionalFields(data);\n const additionalHeaders = [\n ...exportConfig?.additionalFields?.map(column => column.key) ?? [],\n ...collection.additionalFields?.map(field => field.key) ?? []\n ];\n\n const defaultValues = includeUndefinedValues ? getDefaultValuesFor(collection.properties) : undefined;\n const dataWithDefaults = defaultValues\n ? data.map(entity => ({\n ...entity,\n values: { ...defaultValues,\n...entity.values }\n }))\n : data;\n downloadEntitiesExport({\n data: dataWithDefaults,\n additionalData,\n properties: collection.properties,\n propertiesOrder: collection.propertiesOrder,\n name: collection.name,\n flattenArrays,\n additionalHeaders,\n exportType,\n dateExportType\n });\n onAnalyticsEvent?.(\"export_collection_success\", {\n collection: collection.slug\n });\n return true;\n } catch (e) {\n console.error(\"Error loading export data\", e);\n setDataLoadingError(e as Error);\n return false;\n } finally {\n setDataLoading(false);\n }\n\n }, [onAnalyticsEvent, dataClient, path, exportSelection, liveQuery, fetchAdditionalFields, includeUndefinedValues, flattenArrays, exportType, dateExportType]);\n\n const onOkClicked = useCallback(() => {\n // The dialog stays open until the walk finishes: it is the only place a\n // multi-page export reports progress, or reports that it failed.\n doDownload(collection, exportConfig).then((downloaded) => {\n if (downloaded) handleClose();\n });\n }, [doDownload, collection, exportConfig, handleClose]);\n\n return <>\n\n <Tooltip title={\"Export\"}\n asChild={true}>\n <IconButton\n size={\"small\"}\n color={\"primary\"}\n onClick={handleClickOpen}>\n <DownloadIcon size={iconSize.small}/>\n </IconButton>\n </Tooltip>\n\n <Dialog\n open={open}\n onOpenChange={setOpen}\n maxWidth={\"xl\"}>\n\n <DialogTitle variant={\"h6\"}>{t(\"export_data\")}</DialogTitle>\n\n <DialogContent className={\"flex flex-col gap-4 my-4\"}>\n\n {/* What is about to be downloaded, in as many words. The export\n now follows the selection, or the view's filter and search —\n so \"the content of this table\" would be describing something\n it no longer does. */}\n <div>{exportSelection\n ? (rowsToExport === undefined\n ? (t(\"export_selection_all_matching\", { collection: collection.name })\n ?? `Download every ${collection.name} matching the current filter as a ${exportType.toUpperCase()}`)\n : (t(\"export_selection_count\", {\n total: rowsToExport.toLocaleString(),\n format: exportType.toUpperCase()\n }) ?? `Download the ${rowsToExport.toLocaleString()} selected rows as a ${exportType.toUpperCase()}`))\n : t(\"download_table_csv\")}</div>\n\n {!exportSelection && collectionEntitiesCount !== undefined && collectionEntitiesCount > DOCS_LIMIT && !tooManyToExport &&\n <Alert color={\"warning\"}>\n <div>\n {t(\"large_number_of_documents\", { count: collectionEntitiesCount.toString() })}\n </div>\n </Alert>}\n\n {tooManyToExport &&\n <Alert color={\"error\"}>\n <div>\n {t(\"too_many_documents_to_export\", {\n count: (collectionEntitiesCount ?? 0).toString(),\n limit: MAX_EXPORT_ROWS.toString()\n })}\n </div>\n </Alert>}\n\n <div className={\"flex flex-row gap-4\"}>\n <div className={\"p-4 flex flex-col\"}>\n <RadioGroup value={exportType} onValueChange={(v) => setExportType(v as \"csv\" | \"json\")}>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"csv\" id=\"radio-csv\"/>\n <Label htmlFor=\"radio-csv\">{t(\"csv\")}</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"json\" id=\"radio-json\"/>\n <Label htmlFor=\"radio-json\">{t(\"json\")}</Label>\n </div>\n </RadioGroup>\n </div>\n\n <div className={\"p-4 flex flex-col\"}>\n <RadioGroup value={dateExportType} onValueChange={(v) => setDateExportType(v as \"timestamp\" | \"string\")}>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"timestamp\" id=\"radio-timestamp\"/>\n <Label htmlFor=\"radio-timestamp\">{t(\"dates_as_timestamps\")} ({dateRef.current.getTime()})</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <RadioGroupItem value=\"string\" id=\"radio-string\"/>\n <Label htmlFor=\"radio-string\">{t(\"dates_as_strings\")} ({dateRef.current.toISOString()})</Label>\n </div>\n </RadioGroup>\n </div>\n </div>\n\n <BooleanSwitchWithLabel\n size={\"small\"}\n disabled={exportType !== \"csv\"}\n value={flattenArrays}\n onValueChange={setFlattenArrays}\n label={t(\"flatten_arrays\")}/>\n\n <BooleanSwitchWithLabel\n size={\"small\"}\n value={includeUndefinedValues}\n onValueChange={setIncludeUndefinedValues}\n label={t(\"include_undefined_values\")}/>\n\n {dataLoadingError && <Alert color={\"error\"}>\n <div>{dataLoadingError.message}</div>\n </Alert>}\n\n {!canExport && notAllowedView}\n\n </DialogContent>\n\n <DialogActions>\n\n {dataLoading && <CircularProgress size={\"smallest\"}/>}\n\n {dataLoading && <Label>\n {progress.total !== undefined\n ? `${progress.loaded} / ${progress.total}`\n : `${progress.loaded}`}\n </Label>}\n\n <Button onClick={handleClose}\n variant={\"text\"}>\n {t(\"cancel\")}\n </Button>\n\n <Button onClick={onOkClicked}\n disabled={dataLoading || !canExport || tooManyToExport}>\n {t(\"download\")}\n </Button>\n\n </DialogActions>\n\n </Dialog>\n\n </>;\n}\n","export * from \"./export\";\nexport * from \"./fetch_export_data\";\nexport * from \"./BasicExportAction\";\nexport * from \"./ExportCollectionAction\";\n"],"mappings":"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"}
@@ -5,6 +5,19 @@ import { z } from "zod";
5
5
  export declare function extractTouchedValues(values: unknown, touched: Record<string, boolean>): Record<string, unknown>;
6
6
  export declare function removeEmptyContainers(obj: unknown): unknown;
7
7
  export declare function getChanges<T extends object>(source: Partial<T>, comparison: Partial<T>): Partial<T>;
8
+ /**
9
+ * What an update of a stored record sends: every top-level property whose value
10
+ * differs from the stored one — whole.
11
+ *
12
+ * Whole, because that is the unit an update replaces. It sets each property it
13
+ * carries, so the value sent *is* the property's new value: a map sent as the
14
+ * one key that changed would erase every key left out, a key-value map with a
15
+ * key removed would arrive empty, and a geopoint with one coordinate moved is
16
+ * refused for lacking the other. {@link getChanges} walks into plain objects to
17
+ * find what changed inside them, which is right for comparing a draft with the
18
+ * form and wrong for a write.
19
+ */
20
+ export declare function getChangedProperties<M extends Record<string, unknown>>(values: Partial<M>, storedValues: Partial<M>): Partial<M>;
8
21
  /**
9
22
  * What the local-changes backup still has to offer, given what the form is
10
23
  * already showing — the answer the "unsaved local changes" banner is asking for.
@@ -18,4 +18,17 @@ interface PropertyContext<P extends Property> {
18
18
  export declare function getEntitySchema<M extends Record<string, unknown>>(entityId: string | number | undefined, properties: Properties, customFieldValidator?: CustomFieldValidator): z.ZodObject<Record<string, ZodTypeAny>>;
19
19
  export declare function mapPropertyToZod(propertyContext: PropertyContext<Property>): ZodTypeAny;
20
20
  export declare function getZodMapObjectSchema({ property, entityId, customFieldValidator, name }: PropertyContext<MapProperty>): ZodTypeAny;
21
+ /**
22
+ * The values as they are to be written: every `trim`, `lowercase` and
23
+ * `uppercase` a string property declares applied to its value, inside maps and
24
+ * arrays too.
25
+ *
26
+ * They are documented as changing the value that is written, and only the
27
+ * panel applies them — the server checks what it receives. Validation judges
28
+ * the transformed value, so without this the form passed "my-slug" and sent
29
+ * " My-Slug ", which a `matches` on the server then refused.
30
+ *
31
+ * Returns `values` itself when nothing changes.
32
+ */
33
+ export declare function applyValueTransforms<M extends Record<string, unknown>>(values: M, properties: Properties): M;
21
34
  export {};
@@ -1,4 +1,4 @@
1
- import { Mr as getPropertyInPath$1, Pn as PropertyPreview, tt as useHistory } from "./util-qfCPxJzB.js";
1
+ import { Hr as getPropertyInPath$1, Wn as PropertyPreview, tt as useHistory } from "./util-DybQBkug.js";
2
2
  import * as React$1 from "react";
3
3
  import { useEffect, useRef, useState } from "react";
4
4
  import { ArrowLeftIcon, Chip, ErrorBoundary, HistoryIcon, IconButton, Label, Tooltip, Typography, cls, defaultBorderMixin, iconSize } from "@rebasepro/ui";
@@ -310,4 +310,4 @@ function EntityHistoryView({ entity, collection, formContext, refreshToken }) {
310
310
  //#endregion
311
311
  export { EntityHistoryView };
312
312
 
313
- //# sourceMappingURL=history-CrZEzQ5b.js.map
313
+ //# sourceMappingURL=history-BElrZ5Rw.js.map