@rebasepro/cms 0.22.0 → 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.
- package/dist/{CollectionEditorDialog-CUtfS_49.js → CollectionEditorDialog-DLHCka00.js} +103 -58
- package/dist/CollectionEditorDialog-DLHCka00.js.map +1 -0
- package/dist/{PropertyEditView-BpEbC0Q2.js → PropertyEditView-_pu5hzZn.js} +53 -21
- package/dist/PropertyEditView-_pu5hzZn.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-EK8Knq5I.js → RouterCollectionsStudioView-ChPNl_uU.js} +34 -10
- package/dist/RouterCollectionsStudioView-ChPNl_uU.js.map +1 -0
- package/dist/collection_editor/_cms_internals.d.ts +1 -0
- package/dist/collection_editor/relation_kinds.d.ts +11 -0
- package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +8 -1
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/CollectionTableBinding/fields/CompactEntityCellField.d.ts +12 -13
- package/dist/components/CollectionTableBinding/fields/TableMultipleRelationField.d.ts +6 -3
- package/dist/components/CollectionTableBinding/fields/TableReferenceField.d.ts +8 -1
- package/dist/components/CollectionTableBinding/fields/TableRelationField.d.ts +8 -1
- package/dist/components/CollectionTableBinding/fields/TableRelationSelectorField.d.ts +13 -3
- package/dist/components/CollectionTableBinding/fields/VirtualTableSelect.d.ts +10 -0
- package/dist/components/CollectionTableBinding/fields/VirtualTableUserSelect.d.ts +10 -0
- package/dist/components/CollectionTableBinding/internal/EntityTableCell.d.ts +21 -0
- package/dist/components/CollectionTableBinding/internal/TableCellOpener.d.ts +45 -0
- package/dist/components/CollectionTableBinding/table_bindings.d.ts +24 -0
- package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +7 -1
- package/dist/components/EntityPreviewBinding.d.ts +8 -1
- package/dist/components/EntityPreviewNesting.d.ts +15 -0
- package/dist/components/PickerTrigger.d.ts +17 -0
- package/dist/components/ReferenceTable/SelectionTableBinding.d.ts +12 -0
- package/dist/components/RelationSelector.d.ts +24 -7
- package/dist/components/UserSelector.d.ts +8 -1
- package/dist/data_export/export/additional_fields.d.ts +13 -0
- package/dist/data_export/export/export.d.ts +6 -0
- package/dist/data_import/utils/csv.d.ts +3 -2
- package/dist/data_import/utils/id_column.d.ts +16 -0
- package/dist/editor.js +1 -1
- package/dist/{export-LvIpE7sE.js → export-4bJWdlDK.js} +63 -209
- package/dist/export-4bJWdlDK.js.map +1 -0
- package/dist/form/form_utils.d.ts +13 -0
- package/dist/form/validation.d.ts +13 -0
- package/dist/{history-DG6Ln7CV.js → history-BElrZ5Rw.js} +2 -2
- package/dist/{history-DG6Ln7CV.js.map → history-BElrZ5Rw.js.map} +1 -1
- package/dist/hooks/useEntityDisplay.d.ts +4 -5
- package/dist/{import-Cxnmpyjl.js → import-TCgkTVlS.js} +3 -4
- package/dist/import-TCgkTVlS.js.map +1 -0
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/preview/util.d.ts +25 -0
- package/dist/util/navigation_utils.d.ts +14 -2
- package/dist/util/view_mode.d.ts +12 -18
- package/dist/{util-DB31IoJE.js → util-CC0ozHoD.js} +35 -2
- package/dist/util-CC0ozHoD.js.map +1 -0
- package/dist/{util-CyIcgnv2.js → util-DybQBkug.js} +1363 -636
- package/dist/util-DybQBkug.js.map +1 -0
- package/package.json +9 -9
- package/dist/CollectionEditorDialog-CUtfS_49.js.map +0 -1
- package/dist/PropertyEditView-BpEbC0Q2.js.map +0 -1
- package/dist/RouterCollectionsStudioView-EK8Knq5I.js.map +0 -1
- package/dist/export-LvIpE7sE.js.map +0 -1
- package/dist/import-Cxnmpyjl.js.map +0 -1
- package/dist/util-CyIcgnv2.js.map +0 -1
- package/dist/util-DB31IoJE.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
|
-
* `
|
|
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?: "
|
|
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<
|
|
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<
|
|
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
|
|
6
|
-
*
|
|
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 {
|
|
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 {
|
|
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
|
|
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/
|
|
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(
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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,
|
|
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-
|
|
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 {
|
|
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-
|
|
313
|
+
//# sourceMappingURL=history-BElrZ5Rw.js.map
|