@firecms/collection_editor 3.0.0-canary.17 → 3.0.0-canary.170
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/LICENSE +114 -21
- package/dist/ConfigControllerProvider.d.ts +11 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +10011 -4759
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10712 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +14 -2
- package/dist/types/collection_inference.d.ts +1 -1
- package/dist/types/config_permissions.d.ts +2 -2
- package/dist/types/persisted_collection.d.ts +1 -1
- package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
- package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
- package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
- package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +4 -3
- package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
- package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
- package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
- package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
- package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
- package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
- package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
- package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
- package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
- package/dist/useCollectionEditorPlugin.d.ts +17 -11
- package/dist/utils/collections.d.ts +6 -0
- package/package.json +24 -35
- package/src/ConfigControllerProvider.tsx +76 -64
- package/src/index.ts +1 -0
- package/src/types/collection_editor_controller.tsx +14 -4
- package/src/types/collection_inference.ts +1 -1
- package/src/types/config_permissions.ts +1 -1
- package/src/types/persisted_collection.ts +2 -3
- package/src/ui/CollectionViewHeaderAction.tsx +10 -5
- package/src/ui/EditorCollectionAction.tsx +10 -63
- package/src/ui/EditorCollectionActionStart.tsx +88 -0
- package/src/ui/HomePageEditorCollectionAction.tsx +19 -13
- package/src/ui/NewCollectionButton.tsx +12 -10
- package/src/ui/NewCollectionCard.tsx +3 -3
- package/src/ui/PropertyAddColumnComponent.tsx +11 -6
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +83 -10
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +66 -37
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +39 -36
- package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
- package/src/ui/collection_editor/EnumForm.tsx +10 -6
- package/src/ui/collection_editor/GetCodeDialog.tsx +55 -25
- package/src/ui/collection_editor/LayoutModeSwitch.tsx +53 -0
- package/src/ui/collection_editor/PropertyEditView.tsx +257 -80
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
- package/src/ui/collection_editor/PropertyTree.tsx +9 -7
- package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
- package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +26 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
- package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
- package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
- package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -0
- package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
- package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
- package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
- package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
- package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
- package/src/ui/collection_editor/templates/pages_template.ts +1 -6
- package/src/useCollectionEditorPlugin.tsx +41 -31
- package/src/utils/collections.ts +34 -0
- package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
- package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
- package/src/ui/RootCollectionSuggestions.tsx +0 -63
- package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
AutocompleteItem,
|
|
6
6
|
BooleanSwitchWithLabel,
|
|
7
7
|
Chip,
|
|
8
|
-
|
|
8
|
+
CloseIcon,
|
|
9
|
+
cls,
|
|
9
10
|
Container,
|
|
10
11
|
DebouncedTextField,
|
|
11
12
|
Dialog,
|
|
@@ -21,6 +22,8 @@ import {
|
|
|
21
22
|
} from "@firecms/ui";
|
|
22
23
|
|
|
23
24
|
import { Field, getIn, useFormex } from "@firecms/formex";
|
|
25
|
+
import { useCollectionEditorController } from "../../useCollectionEditorController";
|
|
26
|
+
import { LayoutModeSwitch } from "./LayoutModeSwitch";
|
|
24
27
|
|
|
25
28
|
export function CollectionDetailsForm({
|
|
26
29
|
isNewCollection,
|
|
@@ -51,9 +54,15 @@ export function CollectionDetailsForm({
|
|
|
51
54
|
submitCount
|
|
52
55
|
} = useFormex<EntityCollection>();
|
|
53
56
|
|
|
57
|
+
const collectionEditor = useCollectionEditorController();
|
|
58
|
+
|
|
54
59
|
const [iconDialogOpen, setIconDialogOpen] = useState(false);
|
|
55
60
|
const [advancedPanelExpanded, setAdvancedPanelExpanded] = useState(false);
|
|
56
61
|
|
|
62
|
+
const updateDatabaseId = (databaseId: string) => {
|
|
63
|
+
setFieldValue("databaseId", databaseId ?? undefined);
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
const updateName = (name: string) => {
|
|
58
67
|
setFieldValue("name", name);
|
|
59
68
|
|
|
@@ -80,6 +89,8 @@ export function CollectionDetailsForm({
|
|
|
80
89
|
}
|
|
81
90
|
}, [errors.id]);
|
|
82
91
|
|
|
92
|
+
const DatabaseField = collectionEditor.components?.DatabaseField ?? DefaultDatabaseField;
|
|
93
|
+
|
|
83
94
|
const collectionIcon = <IconForView collectionOrView={values}/>;
|
|
84
95
|
|
|
85
96
|
const groupOptions = groups?.filter((group) => !reservedGroups?.includes(group));
|
|
@@ -112,11 +123,15 @@ export function CollectionDetailsForm({
|
|
|
112
123
|
|
|
113
124
|
<div>
|
|
114
125
|
<div
|
|
115
|
-
className="flex flex-row py-2 pt-3 items-center">
|
|
126
|
+
className="flex flex-row gap-2 py-2 pt-3 items-center">
|
|
116
127
|
<Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
|
|
117
128
|
{isNewCollection ? "New collection" : `${values?.name} collection`}
|
|
118
129
|
</Typography>
|
|
119
|
-
<
|
|
130
|
+
<DatabaseField databaseId={values.databaseId}
|
|
131
|
+
onDatabaseIdUpdate={updateDatabaseId}/>
|
|
132
|
+
|
|
133
|
+
<Tooltip title={"Change icon"}
|
|
134
|
+
asChild={true}>
|
|
120
135
|
<IconButton
|
|
121
136
|
shape={"square"}
|
|
122
137
|
onClick={() => setIconDialogOpen(true)}>
|
|
@@ -139,14 +154,15 @@ export function CollectionDetailsForm({
|
|
|
139
154
|
value={values.name ?? ""}
|
|
140
155
|
onChange={(e: any) => updateName(e.target.value)}
|
|
141
156
|
label={"Name"}
|
|
157
|
+
autoFocus={true}
|
|
142
158
|
required
|
|
143
159
|
error={showErrors && Boolean(errors.name)}/>
|
|
144
160
|
<FieldCaption error={touched.name && Boolean(errors.name)}>
|
|
145
|
-
{touched.name && Boolean(errors.name) ? errors.name : "Name of
|
|
161
|
+
{touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
|
|
146
162
|
</FieldCaption>
|
|
147
163
|
</div>
|
|
148
164
|
|
|
149
|
-
<div className={
|
|
165
|
+
<div className={cls("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
|
|
150
166
|
<Field name={"path"}
|
|
151
167
|
as={DebouncedTextField}
|
|
152
168
|
label={"Path"}
|
|
@@ -190,16 +206,23 @@ export function CollectionDetailsForm({
|
|
|
190
206
|
})}
|
|
191
207
|
</Autocomplete>
|
|
192
208
|
<FieldCaption>
|
|
193
|
-
{showErrors && Boolean(errors.group) ? errors.group : "Group
|
|
209
|
+
{showErrors && Boolean(errors.group) ? errors.group : "Group in the home page"}
|
|
194
210
|
</FieldCaption>
|
|
211
|
+
|
|
212
|
+
|
|
195
213
|
</div>}
|
|
196
214
|
|
|
197
|
-
<
|
|
215
|
+
<LayoutModeSwitch
|
|
216
|
+
className={"col-span-12"}
|
|
217
|
+
value={values.openEntityMode ?? "side_panel"}
|
|
218
|
+
onChange={(value) => setFieldValue("openEntityMode", value)}/>
|
|
219
|
+
|
|
220
|
+
<div className={"col-span-12 mt-8"}>
|
|
198
221
|
<ExpandablePanel
|
|
199
222
|
expanded={advancedPanelExpanded}
|
|
200
223
|
onExpandedChange={setAdvancedPanelExpanded}
|
|
201
224
|
title={
|
|
202
|
-
<div className="flex flex-row text-
|
|
225
|
+
<div className="flex flex-row text-surface-500">
|
|
203
226
|
<SettingsIcon/>
|
|
204
227
|
<Typography variant={"subtitle2"}
|
|
205
228
|
className="ml-2">
|
|
@@ -216,7 +239,7 @@ export function CollectionDetailsForm({
|
|
|
216
239
|
label={"Collection id"}
|
|
217
240
|
error={showErrors && Boolean(errors.id)}/>
|
|
218
241
|
<FieldCaption error={touched.id && Boolean(errors.id)}>
|
|
219
|
-
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
|
|
242
|
+
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
|
|
220
243
|
</FieldCaption>
|
|
221
244
|
</div>
|
|
222
245
|
|
|
@@ -235,6 +258,35 @@ export function CollectionDetailsForm({
|
|
|
235
258
|
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
|
|
236
259
|
</FieldCaption>
|
|
237
260
|
</div>
|
|
261
|
+
<div className={"col-span-12"}>
|
|
262
|
+
<TextField
|
|
263
|
+
error={showErrors && Boolean(errors.sideDialogWidth)}
|
|
264
|
+
name={"sideDialogWidth"}
|
|
265
|
+
type={"number"}
|
|
266
|
+
aria-describedby={"sideDialogWidth-helper"}
|
|
267
|
+
onChange={(e) => {
|
|
268
|
+
setFieldTouched("sideDialogWidth", true);
|
|
269
|
+
const value = e.target.value;
|
|
270
|
+
if (!value) {
|
|
271
|
+
setFieldValue("sideDialogWidth", null);
|
|
272
|
+
} else if (!isNaN(Number(value))) {
|
|
273
|
+
setFieldValue("sideDialogWidth", Number(value));
|
|
274
|
+
}
|
|
275
|
+
}}
|
|
276
|
+
endAdornment={<IconButton
|
|
277
|
+
size={"small"}
|
|
278
|
+
onClick={() => {
|
|
279
|
+
setFieldValue("sideDialogWidth", null);
|
|
280
|
+
}}
|
|
281
|
+
disabled={!values.sideDialogWidth}>
|
|
282
|
+
<CloseIcon size={"small"}/>
|
|
283
|
+
</IconButton>}
|
|
284
|
+
value={values.sideDialogWidth ?? ""}
|
|
285
|
+
label={"Side dialog width"}/>
|
|
286
|
+
<FieldCaption error={showErrors && Boolean(errors.singularName)}>
|
|
287
|
+
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
|
|
288
|
+
</FieldCaption>
|
|
289
|
+
</div>
|
|
238
290
|
<div className={"col-span-12"}>
|
|
239
291
|
<TextField
|
|
240
292
|
error={showErrors && Boolean(errors.description)}
|
|
@@ -254,6 +306,8 @@ export function CollectionDetailsForm({
|
|
|
254
306
|
<div className={"col-span-12"}>
|
|
255
307
|
<Select
|
|
256
308
|
name="defaultSize"
|
|
309
|
+
size={"large"}
|
|
310
|
+
fullWidth={true}
|
|
257
311
|
label="Default row size"
|
|
258
312
|
position={"item-aligned"}
|
|
259
313
|
onChange={handleChange}
|
|
@@ -272,8 +326,10 @@ export function CollectionDetailsForm({
|
|
|
272
326
|
<div className={"col-span-12"}>
|
|
273
327
|
<Select
|
|
274
328
|
name="customId"
|
|
275
|
-
label="
|
|
329
|
+
label="Document IDs generation"
|
|
276
330
|
position={"item-aligned"}
|
|
331
|
+
size={"large"}
|
|
332
|
+
fullWidth={true}
|
|
277
333
|
disabled={customIdValue === "code_defined"}
|
|
278
334
|
onValueChange={(v) => {
|
|
279
335
|
if (v === "code_defined")
|
|
@@ -363,3 +419,20 @@ export function CollectionDetailsForm({
|
|
|
363
419
|
</div>
|
|
364
420
|
);
|
|
365
421
|
}
|
|
422
|
+
|
|
423
|
+
function DefaultDatabaseField({
|
|
424
|
+
databaseId,
|
|
425
|
+
onDatabaseIdUpdate
|
|
426
|
+
}: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
|
|
427
|
+
|
|
428
|
+
return <Tooltip title={"Database ID"}
|
|
429
|
+
side={"top"}
|
|
430
|
+
align={"start"}>
|
|
431
|
+
<TextField size={"small"}
|
|
432
|
+
invisible={true}
|
|
433
|
+
inputClassName={"text-end"}
|
|
434
|
+
value={databaseId ?? ""}
|
|
435
|
+
onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
|
|
436
|
+
placeholder={"(default)"}></TextField>
|
|
437
|
+
</Tooltip>
|
|
438
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import {
|
|
4
4
|
CircularProgressCenter,
|
|
5
|
+
Entity,
|
|
5
6
|
EntityCollection,
|
|
6
7
|
ErrorView,
|
|
7
8
|
isPropertyBuilder,
|
|
@@ -25,13 +26,14 @@ import {
|
|
|
25
26
|
import {
|
|
26
27
|
ArrowBackIcon,
|
|
27
28
|
Button,
|
|
28
|
-
|
|
29
|
+
CheckIcon,
|
|
30
|
+
cls,
|
|
29
31
|
coolIconKeys,
|
|
30
32
|
defaultBorderMixin,
|
|
31
33
|
Dialog,
|
|
32
34
|
DialogActions,
|
|
33
35
|
DialogContent,
|
|
34
|
-
|
|
36
|
+
DialogTitle,
|
|
35
37
|
IconButton,
|
|
36
38
|
LoadingButton,
|
|
37
39
|
Tab,
|
|
@@ -76,9 +78,10 @@ export interface CollectionEditorDialogProps {
|
|
|
76
78
|
icon: React.ReactNode
|
|
77
79
|
};
|
|
78
80
|
pathSuggestions?: (path?: string) => Promise<string[]>;
|
|
79
|
-
getUser
|
|
81
|
+
getUser?: (uid: string) => User | null;
|
|
80
82
|
getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
|
|
81
83
|
parentCollection?: PersistedCollection;
|
|
84
|
+
existingEntities?: Entity<any>[];
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
@@ -88,13 +91,13 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
88
91
|
const [formDirty, setFormDirty] = React.useState<boolean>(false);
|
|
89
92
|
const [unsavedChangesDialogOpen, setUnsavedChangesDialogOpen] = React.useState<boolean>(false);
|
|
90
93
|
|
|
91
|
-
const handleCancel =
|
|
94
|
+
const handleCancel = () => {
|
|
92
95
|
if (!formDirty) {
|
|
93
96
|
props.handleClose(undefined);
|
|
94
97
|
} else {
|
|
95
98
|
setUnsavedChangesDialogOpen(true);
|
|
96
99
|
}
|
|
97
|
-
}
|
|
100
|
+
};
|
|
98
101
|
|
|
99
102
|
useEffect(() => {
|
|
100
103
|
if (!open) {
|
|
@@ -112,6 +115,7 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
112
115
|
maxWidth={"7xl"}
|
|
113
116
|
onOpenChange={(open) => !open ? handleCancel() : undefined}
|
|
114
117
|
>
|
|
118
|
+
<DialogTitle hidden>Collection editor</DialogTitle>
|
|
115
119
|
{open && <CollectionEditor {...props}
|
|
116
120
|
handleCancel={handleCancel}
|
|
117
121
|
setFormDirty={setFormDirty}/>}
|
|
@@ -136,7 +140,7 @@ type EditorView = "welcome"
|
|
|
136
140
|
| "extra_view"
|
|
137
141
|
| "subcollections";
|
|
138
142
|
|
|
139
|
-
export function CollectionEditor
|
|
143
|
+
export function CollectionEditor(props: CollectionEditorDialogProps & {
|
|
140
144
|
handleCancel: () => void,
|
|
141
145
|
setFormDirty: (dirty: boolean) => void
|
|
142
146
|
}) {
|
|
@@ -154,14 +158,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
154
158
|
const collectionsInThisLevel = (props.parentCollection ? props.parentCollection.subcollections : collections) ?? [];
|
|
155
159
|
const existingPaths = collectionsInThisLevel.map(col => col.path.trim().toLowerCase());
|
|
156
160
|
const existingIds = collectionsInThisLevel.map(col => col.id?.trim().toLowerCase()).filter(Boolean) as string[];
|
|
157
|
-
const [collection, setCollection] = React.useState<PersistedCollection<
|
|
161
|
+
const [collection, setCollection] = React.useState<PersistedCollection<any> | undefined>();
|
|
158
162
|
const [initialLoadingCompleted, setInitialLoadingCompleted] = React.useState(false);
|
|
159
163
|
|
|
160
164
|
useEffect(() => {
|
|
161
165
|
try {
|
|
162
166
|
if (navigation.initialised) {
|
|
163
167
|
if (props.editedCollectionId) {
|
|
164
|
-
setCollection(navigation.getCollectionFromPaths
|
|
168
|
+
setCollection(navigation.getCollectionFromPaths([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
|
|
165
169
|
} else {
|
|
166
170
|
setCollection(undefined);
|
|
167
171
|
}
|
|
@@ -170,7 +174,8 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
170
174
|
} catch (e) {
|
|
171
175
|
console.error(e);
|
|
172
176
|
}
|
|
173
|
-
}, [
|
|
177
|
+
}, [props.editedCollectionId, props.parentCollectionIds, navigation.initialised, navigation.getCollectionFromPaths]);
|
|
178
|
+
|
|
174
179
|
if (!topLevelNavigation) {
|
|
175
180
|
throw Error("Internal: Navigation not ready in collection editor");
|
|
176
181
|
}
|
|
@@ -186,14 +191,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
186
191
|
}
|
|
187
192
|
: undefined;
|
|
188
193
|
|
|
189
|
-
const initialValues: PersistedCollection<
|
|
194
|
+
const initialValues: PersistedCollection<any> = initialCollection
|
|
190
195
|
? applyPropertyConfigs(initialCollection, propertyConfigs)
|
|
191
196
|
: {
|
|
192
197
|
id: initialValuesProp?.path ?? randomString(16),
|
|
193
198
|
path: initialValuesProp?.path ?? "",
|
|
194
199
|
name: initialValuesProp?.name ?? "",
|
|
195
200
|
group: initialValuesProp?.group ?? "",
|
|
196
|
-
properties: {} as PropertiesOrBuilders
|
|
201
|
+
properties: {} as PropertiesOrBuilders,
|
|
197
202
|
propertiesOrder: [],
|
|
198
203
|
icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
|
|
199
204
|
ownerId: authController.user?.uid ?? ""
|
|
@@ -243,7 +248,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
243
248
|
setCollection,
|
|
244
249
|
initialValues,
|
|
245
250
|
propertyConfigs,
|
|
246
|
-
groups
|
|
251
|
+
groups,
|
|
252
|
+
existingEntities
|
|
247
253
|
}: CollectionEditorDialogProps & {
|
|
248
254
|
handleCancel: () => void,
|
|
249
255
|
setFormDirty: (dirty: boolean) => void,
|
|
@@ -272,6 +278,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
272
278
|
|
|
273
279
|
const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
|
|
274
280
|
const id = updatedCollection.id || updatedCollection.path;
|
|
281
|
+
|
|
275
282
|
return configController.saveCollection({
|
|
276
283
|
id,
|
|
277
284
|
collectionData: updatedCollection,
|
|
@@ -293,7 +300,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
293
300
|
});
|
|
294
301
|
};
|
|
295
302
|
|
|
296
|
-
const setNextMode =
|
|
303
|
+
const setNextMode = () => {
|
|
297
304
|
if (currentView === "details") {
|
|
298
305
|
if (importConfig.inUse) {
|
|
299
306
|
setCurrentView("import_data_saving");
|
|
@@ -314,14 +321,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
314
321
|
setCurrentView("details");
|
|
315
322
|
}
|
|
316
323
|
|
|
317
|
-
}
|
|
324
|
+
};
|
|
318
325
|
|
|
319
|
-
const doCollectionInference =
|
|
326
|
+
const doCollectionInference = (collection: PersistedCollection<any>) => {
|
|
320
327
|
if (!collectionInference) return undefined;
|
|
321
|
-
return collectionInference?.(collection.path, collection.collectionGroup ?? false,
|
|
322
|
-
}
|
|
328
|
+
return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? []);
|
|
329
|
+
};
|
|
323
330
|
|
|
324
|
-
const inferCollectionFromData =
|
|
331
|
+
const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
|
|
325
332
|
|
|
326
333
|
try {
|
|
327
334
|
if (!doCollectionInference) {
|
|
@@ -365,15 +372,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
365
372
|
});
|
|
366
373
|
return newCollection;
|
|
367
374
|
}
|
|
368
|
-
}
|
|
375
|
+
};
|
|
369
376
|
|
|
370
377
|
const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
|
|
371
|
-
console.
|
|
378
|
+
console.debug("Submitting collection", newCollectionState);
|
|
372
379
|
try {
|
|
373
380
|
|
|
374
381
|
if (!isNewCollection) {
|
|
375
382
|
saveCollection(newCollectionState).then(() => {
|
|
376
|
-
formexController.resetForm(
|
|
383
|
+
formexController.resetForm();
|
|
377
384
|
handleClose(newCollectionState);
|
|
378
385
|
});
|
|
379
386
|
return;
|
|
@@ -462,7 +469,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
462
469
|
const formController = useCreateFormex<PersistedCollection<M>>({
|
|
463
470
|
initialValues,
|
|
464
471
|
onSubmit,
|
|
465
|
-
validation
|
|
472
|
+
validation,
|
|
473
|
+
debugId: "COLLECTION_EDITOR"
|
|
466
474
|
});
|
|
467
475
|
|
|
468
476
|
const {
|
|
@@ -479,26 +487,37 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
479
487
|
const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
|
|
480
488
|
|
|
481
489
|
const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
|
|
482
|
-
const resolvedPath = !pathError ? navigation.
|
|
483
|
-
const getDataWithPath = resolvedPath && getData ? () =>
|
|
490
|
+
const resolvedPath = !pathError ? navigation.resolveIdsFrom(updatedFullPath) : undefined;
|
|
491
|
+
const getDataWithPath = resolvedPath && getData ? async () => {
|
|
492
|
+
const data = await getData(resolvedPath, parentPaths ?? []);
|
|
493
|
+
if (existingEntities) {
|
|
494
|
+
const existingData = existingEntities.map(e => e.values);
|
|
495
|
+
data.push(...existingData);
|
|
496
|
+
}
|
|
497
|
+
return data;
|
|
498
|
+
} : undefined;
|
|
484
499
|
|
|
485
500
|
useEffect(() => {
|
|
486
501
|
setFormDirty(dirty);
|
|
487
502
|
}, [dirty]);
|
|
488
503
|
|
|
489
|
-
function onImportDataSet(data: object[]) {
|
|
504
|
+
function onImportDataSet(data: object[], propertiesOrder?: string[]) {
|
|
490
505
|
importConfig.setInUse(true);
|
|
491
506
|
buildEntityPropertiesFromData(data, getInferenceType)
|
|
492
507
|
.then((properties) => {
|
|
493
508
|
const res = cleanPropertiesFromImport(properties);
|
|
494
509
|
|
|
495
|
-
setFieldValue("properties", res.properties);
|
|
496
|
-
setFieldValue("propertiesOrder", Object.keys(res.properties));
|
|
497
|
-
|
|
498
510
|
importConfig.setIdColumn(res.idColumn);
|
|
499
511
|
importConfig.setImportData(data);
|
|
500
512
|
importConfig.setHeadersMapping(res.headersMapping);
|
|
513
|
+
const filteredHeadingsOrder = ((propertiesOrder ?? [])
|
|
514
|
+
.filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
|
|
515
|
+
importConfig.setHeadingsOrder(filteredHeadingsOrder);
|
|
501
516
|
importConfig.setOriginProperties(res.properties);
|
|
517
|
+
|
|
518
|
+
const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
|
|
519
|
+
setFieldValue("properties", res.properties);
|
|
520
|
+
setFieldValue("propertiesOrder", mappedHeadings);
|
|
502
521
|
});
|
|
503
522
|
}
|
|
504
523
|
|
|
@@ -521,7 +540,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
521
540
|
|
|
522
541
|
<>
|
|
523
542
|
{!isNewCollection && <Tabs value={currentView}
|
|
524
|
-
|
|
543
|
+
innerClassName={cls(defaultBorderMixin, "px-4 h-14 w-full justify-end bg-surface-50 dark:bg-surface-950 border-b")}
|
|
525
544
|
onValueChange={(v) => setCurrentView(v as EditorView)}>
|
|
526
545
|
<Tab value={"details"}>
|
|
527
546
|
Details
|
|
@@ -536,7 +555,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
536
555
|
|
|
537
556
|
<form noValidate
|
|
538
557
|
onSubmit={formController.handleSubmit}
|
|
539
|
-
className={
|
|
558
|
+
className={cls(
|
|
540
559
|
isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
|
|
541
560
|
"flex-grow flex flex-col relative")}>
|
|
542
561
|
|
|
@@ -551,9 +570,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
551
570
|
{currentView === "welcome" &&
|
|
552
571
|
<CollectionEditorWelcomeView
|
|
553
572
|
path={path}
|
|
554
|
-
onContinue={(importData) => {
|
|
573
|
+
onContinue={(importData, propertiesOrder) => {
|
|
574
|
+
// console.log("Import data", importData, propertiesOrder)
|
|
555
575
|
if (importData) {
|
|
556
|
-
onImportDataSet(importData);
|
|
576
|
+
onImportDataSet(importData, propertiesOrder);
|
|
557
577
|
setCurrentView("import_data_mapping");
|
|
558
578
|
} else {
|
|
559
579
|
setCurrentView("details");
|
|
@@ -706,7 +726,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
706
726
|
loading={isSubmitting}
|
|
707
727
|
disabled={isSubmitting || (currentView === "details" && !validValues)}
|
|
708
728
|
startIcon={currentView === "properties"
|
|
709
|
-
? <
|
|
729
|
+
? <CheckIcon/>
|
|
710
730
|
: undefined}
|
|
711
731
|
>
|
|
712
732
|
{currentView === "details" && "Next"}
|
|
@@ -733,7 +753,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
733
753
|
}
|
|
734
754
|
|
|
735
755
|
function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
|
|
736
|
-
const {
|
|
756
|
+
const {
|
|
757
|
+
properties,
|
|
758
|
+
...rest
|
|
759
|
+
} = collection;
|
|
737
760
|
const propertiesResult: PropertiesOrBuilders<any> = {};
|
|
738
761
|
if (properties) {
|
|
739
762
|
Object.keys(properties).forEach((key) => {
|
|
@@ -741,7 +764,10 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
|
|
|
741
764
|
});
|
|
742
765
|
}
|
|
743
766
|
|
|
744
|
-
return {
|
|
767
|
+
return {
|
|
768
|
+
...rest,
|
|
769
|
+
properties: propertiesResult
|
|
770
|
+
};
|
|
745
771
|
}
|
|
746
772
|
|
|
747
773
|
function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
|
|
@@ -761,7 +787,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
|
|
|
761
787
|
Object.keys(internalProperty.properties).forEach((key) => {
|
|
762
788
|
properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
|
|
763
789
|
});
|
|
764
|
-
internalProperty = {
|
|
790
|
+
internalProperty = {
|
|
791
|
+
...internalProperty,
|
|
792
|
+
properties
|
|
793
|
+
};
|
|
765
794
|
}
|
|
766
795
|
|
|
767
796
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { EntityCollection, unslugify, } from "@firecms/core";
|
|
3
|
-
import { Button, Card, Chip, CircularProgress,
|
|
3
|
+
import { Button, Card, Chip, CircularProgress, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
|
|
4
4
|
|
|
5
5
|
import { productsCollectionTemplate } from "./templates/products_template";
|
|
6
6
|
import { blogCollectionTemplate } from "./templates/blog_template";
|
|
@@ -19,7 +19,7 @@ export function CollectionEditorWelcomeView({
|
|
|
19
19
|
path: string;
|
|
20
20
|
pathSuggestions?: (path: string) => Promise<string[]>;
|
|
21
21
|
parentCollection?: EntityCollection;
|
|
22
|
-
onContinue: (importData?: object[]) => void;
|
|
22
|
+
onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
|
|
23
23
|
existingCollectionPaths?: string[];
|
|
24
24
|
}) {
|
|
25
25
|
|
|
@@ -154,7 +154,7 @@ export function CollectionEditorWelcomeView({
|
|
|
154
154
|
● Create a collection from a file (csv, json, xls, xslx...)
|
|
155
155
|
</Typography>
|
|
156
156
|
|
|
157
|
-
<ImportFileUpload onDataAdded={(data) => onContinue(data)}/>
|
|
157
|
+
<ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
|
|
158
158
|
|
|
159
159
|
</div>}
|
|
160
160
|
|
|
@@ -185,14 +185,15 @@ export function TemplateButton({
|
|
|
185
185
|
}) {
|
|
186
186
|
|
|
187
187
|
return (
|
|
188
|
-
<Tooltip title={subtitle}
|
|
188
|
+
<Tooltip title={subtitle}
|
|
189
|
+
asChild={true}>
|
|
189
190
|
<Card
|
|
190
191
|
onClick={onClick}
|
|
191
|
-
className={
|
|
192
|
+
className={cls(
|
|
192
193
|
"my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
|
|
193
|
-
"text-
|
|
194
|
+
"text-surface-700 dark:text-surface-accent-300",
|
|
194
195
|
"hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
|
|
195
|
-
"border-
|
|
196
|
+
"border-surface-400 dark:border-surface-600 "
|
|
196
197
|
)}
|
|
197
198
|
>
|
|
198
199
|
{icon}
|