@firecms/collection_editor 3.0.0-canary.21 → 3.0.0-canary.211
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 +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +10060 -4770
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10750 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +4 -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/CollectionDetailsForm.d.ts +3 -1
- package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -2
- 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 +8 -11
- package/dist/utils/collections.d.ts +6 -0
- package/package.json +24 -35
- package/src/ConfigControllerProvider.tsx +67 -64
- package/src/index.ts +1 -0
- package/src/types/collection_editor_controller.tsx +7 -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 +1 -1
- package/src/ui/NewCollectionCard.tsx +3 -3
- package/src/ui/PropertyAddColumnComponent.tsx +11 -6
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +89 -12
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +101 -34
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +37 -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 +56 -26
- package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
- package/src/ui/collection_editor/PropertyEditView.tsx +257 -79
- 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 +33 -32
- package/src/utils/collections.ts +36 -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,
|
|
@@ -28,7 +31,8 @@ export function CollectionDetailsForm({
|
|
|
28
31
|
existingPaths,
|
|
29
32
|
existingIds,
|
|
30
33
|
groups,
|
|
31
|
-
parentCollection
|
|
34
|
+
parentCollection,
|
|
35
|
+
children
|
|
32
36
|
}: {
|
|
33
37
|
isNewCollection: boolean,
|
|
34
38
|
reservedGroups?: string[];
|
|
@@ -37,6 +41,7 @@ export function CollectionDetailsForm({
|
|
|
37
41
|
groups: string[] | null;
|
|
38
42
|
parentCollection?: EntityCollection;
|
|
39
43
|
parentCollectionIds?: string[];
|
|
44
|
+
children?: React.ReactNode;
|
|
40
45
|
}) {
|
|
41
46
|
|
|
42
47
|
const groupRef = React.useRef<HTMLInputElement>(null);
|
|
@@ -51,9 +56,15 @@ export function CollectionDetailsForm({
|
|
|
51
56
|
submitCount
|
|
52
57
|
} = useFormex<EntityCollection>();
|
|
53
58
|
|
|
59
|
+
const collectionEditor = useCollectionEditorController();
|
|
60
|
+
|
|
54
61
|
const [iconDialogOpen, setIconDialogOpen] = useState(false);
|
|
55
62
|
const [advancedPanelExpanded, setAdvancedPanelExpanded] = useState(false);
|
|
56
63
|
|
|
64
|
+
const updateDatabaseId = (databaseId: string) => {
|
|
65
|
+
setFieldValue("databaseId", databaseId ?? undefined);
|
|
66
|
+
}
|
|
67
|
+
|
|
57
68
|
const updateName = (name: string) => {
|
|
58
69
|
setFieldValue("name", name);
|
|
59
70
|
|
|
@@ -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
|
+
<DefaultDatabaseField 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)}
|
|
@@ -242,7 +294,7 @@ export function CollectionDetailsForm({
|
|
|
242
294
|
value={values.description ?? ""}
|
|
243
295
|
onChange={handleChange}
|
|
244
296
|
multiline
|
|
245
|
-
|
|
297
|
+
minRows={2}
|
|
246
298
|
aria-describedby="description-helper-text"
|
|
247
299
|
label="Description"
|
|
248
300
|
/>
|
|
@@ -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")
|
|
@@ -334,9 +390,13 @@ export function CollectionDetailsForm({
|
|
|
334
390
|
for large collections, as it may incur in performance and cost issues.
|
|
335
391
|
</FieldCaption>
|
|
336
392
|
</div>
|
|
393
|
+
|
|
394
|
+
|
|
337
395
|
</div>
|
|
338
396
|
</ExpandablePanel>
|
|
339
397
|
|
|
398
|
+
{children}
|
|
399
|
+
|
|
340
400
|
</div>
|
|
341
401
|
|
|
342
402
|
</div>
|
|
@@ -363,3 +423,20 @@ export function CollectionDetailsForm({
|
|
|
363
423
|
</div>
|
|
364
424
|
);
|
|
365
425
|
}
|
|
426
|
+
|
|
427
|
+
function DefaultDatabaseField({
|
|
428
|
+
databaseId,
|
|
429
|
+
onDatabaseIdUpdate
|
|
430
|
+
}: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
|
|
431
|
+
|
|
432
|
+
return <Tooltip title={"Database ID"}
|
|
433
|
+
side={"top"}
|
|
434
|
+
align={"start"}>
|
|
435
|
+
<TextField size={"small"}
|
|
436
|
+
invisible={true}
|
|
437
|
+
inputClassName={"text-end"}
|
|
438
|
+
value={databaseId ?? ""}
|
|
439
|
+
onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
|
|
440
|
+
placeholder={"(default)"}></TextField>
|
|
441
|
+
</Tooltip>
|
|
442
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import {
|
|
4
4
|
CircularProgressCenter,
|
|
5
|
+
ConfirmationDialog,
|
|
6
|
+
Entity,
|
|
5
7
|
EntityCollection,
|
|
6
8
|
ErrorView,
|
|
7
9
|
isPropertyBuilder,
|
|
@@ -25,17 +27,19 @@ import {
|
|
|
25
27
|
import {
|
|
26
28
|
ArrowBackIcon,
|
|
27
29
|
Button,
|
|
28
|
-
|
|
30
|
+
CheckIcon,
|
|
31
|
+
cls,
|
|
29
32
|
coolIconKeys,
|
|
30
33
|
defaultBorderMixin,
|
|
31
34
|
Dialog,
|
|
32
35
|
DialogActions,
|
|
33
36
|
DialogContent,
|
|
34
|
-
|
|
37
|
+
DialogTitle,
|
|
35
38
|
IconButton,
|
|
36
39
|
LoadingButton,
|
|
37
40
|
Tab,
|
|
38
|
-
Tabs
|
|
41
|
+
Tabs,
|
|
42
|
+
Typography
|
|
39
43
|
} from "@firecms/ui";
|
|
40
44
|
import { YupSchema } from "./CollectionYupValidation";
|
|
41
45
|
import { CollectionDetailsForm } from "./CollectionDetailsForm";
|
|
@@ -76,9 +80,10 @@ export interface CollectionEditorDialogProps {
|
|
|
76
80
|
icon: React.ReactNode
|
|
77
81
|
};
|
|
78
82
|
pathSuggestions?: (path?: string) => Promise<string[]>;
|
|
79
|
-
getUser
|
|
83
|
+
getUser?: (uid: string) => User | null;
|
|
80
84
|
getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
|
|
81
85
|
parentCollection?: PersistedCollection;
|
|
86
|
+
existingEntities?: Entity<any>[];
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
@@ -88,13 +93,13 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
88
93
|
const [formDirty, setFormDirty] = React.useState<boolean>(false);
|
|
89
94
|
const [unsavedChangesDialogOpen, setUnsavedChangesDialogOpen] = React.useState<boolean>(false);
|
|
90
95
|
|
|
91
|
-
const handleCancel =
|
|
96
|
+
const handleCancel = () => {
|
|
92
97
|
if (!formDirty) {
|
|
93
98
|
props.handleClose(undefined);
|
|
94
99
|
} else {
|
|
95
100
|
setUnsavedChangesDialogOpen(true);
|
|
96
101
|
}
|
|
97
|
-
}
|
|
102
|
+
};
|
|
98
103
|
|
|
99
104
|
useEffect(() => {
|
|
100
105
|
if (!open) {
|
|
@@ -112,6 +117,7 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
112
117
|
maxWidth={"7xl"}
|
|
113
118
|
onOpenChange={(open) => !open ? handleCancel() : undefined}
|
|
114
119
|
>
|
|
120
|
+
<DialogTitle hidden>Collection editor</DialogTitle>
|
|
115
121
|
{open && <CollectionEditor {...props}
|
|
116
122
|
handleCancel={handleCancel}
|
|
117
123
|
setFormDirty={setFormDirty}/>}
|
|
@@ -170,7 +176,8 @@ export function CollectionEditor(props: CollectionEditorDialogProps & {
|
|
|
170
176
|
} catch (e) {
|
|
171
177
|
console.error(e);
|
|
172
178
|
}
|
|
173
|
-
}, [
|
|
179
|
+
}, [props.editedCollectionId, props.parentCollectionIds, navigation.initialised, navigation.getCollectionFromPaths]);
|
|
180
|
+
|
|
174
181
|
if (!topLevelNavigation) {
|
|
175
182
|
throw Error("Internal: Navigation not ready in collection editor");
|
|
176
183
|
}
|
|
@@ -243,7 +250,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
243
250
|
setCollection,
|
|
244
251
|
initialValues,
|
|
245
252
|
propertyConfigs,
|
|
246
|
-
groups
|
|
253
|
+
groups,
|
|
254
|
+
existingEntities
|
|
247
255
|
}: CollectionEditorDialogProps & {
|
|
248
256
|
handleCancel: () => void,
|
|
249
257
|
setFormDirty: (dirty: boolean) => void,
|
|
@@ -272,6 +280,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
272
280
|
|
|
273
281
|
const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
|
|
274
282
|
const id = updatedCollection.id || updatedCollection.path;
|
|
283
|
+
|
|
275
284
|
return configController.saveCollection({
|
|
276
285
|
id,
|
|
277
286
|
collectionData: updatedCollection,
|
|
@@ -293,7 +302,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
293
302
|
});
|
|
294
303
|
};
|
|
295
304
|
|
|
296
|
-
const setNextMode =
|
|
305
|
+
const setNextMode = () => {
|
|
297
306
|
if (currentView === "details") {
|
|
298
307
|
if (importConfig.inUse) {
|
|
299
308
|
setCurrentView("import_data_saving");
|
|
@@ -314,14 +323,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
314
323
|
setCurrentView("details");
|
|
315
324
|
}
|
|
316
325
|
|
|
317
|
-
}
|
|
326
|
+
};
|
|
318
327
|
|
|
319
|
-
const doCollectionInference =
|
|
328
|
+
const doCollectionInference = (collection: PersistedCollection<any>) => {
|
|
320
329
|
if (!collectionInference) return undefined;
|
|
321
|
-
return collectionInference?.(collection.path, collection.collectionGroup ?? false,
|
|
322
|
-
}
|
|
330
|
+
return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? []);
|
|
331
|
+
};
|
|
323
332
|
|
|
324
|
-
const inferCollectionFromData =
|
|
333
|
+
const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
|
|
325
334
|
|
|
326
335
|
try {
|
|
327
336
|
if (!doCollectionInference) {
|
|
@@ -365,15 +374,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
365
374
|
});
|
|
366
375
|
return newCollection;
|
|
367
376
|
}
|
|
368
|
-
}
|
|
377
|
+
};
|
|
369
378
|
|
|
370
379
|
const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
|
|
371
|
-
console.
|
|
380
|
+
console.debug("Submitting collection", newCollectionState);
|
|
372
381
|
try {
|
|
373
382
|
|
|
374
383
|
if (!isNewCollection) {
|
|
375
384
|
saveCollection(newCollectionState).then(() => {
|
|
376
|
-
formexController.resetForm(
|
|
385
|
+
formexController.resetForm();
|
|
377
386
|
handleClose(newCollectionState);
|
|
378
387
|
});
|
|
379
388
|
return;
|
|
@@ -462,7 +471,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
462
471
|
const formController = useCreateFormex<PersistedCollection<M>>({
|
|
463
472
|
initialValues,
|
|
464
473
|
onSubmit,
|
|
465
|
-
validation
|
|
474
|
+
validation,
|
|
475
|
+
debugId: "COLLECTION_EDITOR"
|
|
466
476
|
});
|
|
467
477
|
|
|
468
478
|
const {
|
|
@@ -479,26 +489,37 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
479
489
|
const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
|
|
480
490
|
|
|
481
491
|
const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
|
|
482
|
-
const resolvedPath = !pathError ? navigation.
|
|
483
|
-
const getDataWithPath = resolvedPath && getData ? () =>
|
|
492
|
+
const resolvedPath = !pathError ? navigation.resolveIdsFrom(updatedFullPath) : undefined;
|
|
493
|
+
const getDataWithPath = resolvedPath && getData ? async () => {
|
|
494
|
+
const data = await getData(resolvedPath, parentPaths ?? []);
|
|
495
|
+
if (existingEntities) {
|
|
496
|
+
const existingData = existingEntities.map(e => e.values);
|
|
497
|
+
data.push(...existingData);
|
|
498
|
+
}
|
|
499
|
+
return data;
|
|
500
|
+
} : undefined;
|
|
484
501
|
|
|
485
502
|
useEffect(() => {
|
|
486
503
|
setFormDirty(dirty);
|
|
487
504
|
}, [dirty]);
|
|
488
505
|
|
|
489
|
-
function onImportDataSet(data: object[]) {
|
|
506
|
+
function onImportDataSet(data: object[], propertiesOrder?: string[]) {
|
|
490
507
|
importConfig.setInUse(true);
|
|
491
508
|
buildEntityPropertiesFromData(data, getInferenceType)
|
|
492
509
|
.then((properties) => {
|
|
493
510
|
const res = cleanPropertiesFromImport(properties);
|
|
494
511
|
|
|
495
|
-
setFieldValue("properties", res.properties);
|
|
496
|
-
setFieldValue("propertiesOrder", Object.keys(res.properties));
|
|
497
|
-
|
|
498
512
|
importConfig.setIdColumn(res.idColumn);
|
|
499
513
|
importConfig.setImportData(data);
|
|
500
514
|
importConfig.setHeadersMapping(res.headersMapping);
|
|
515
|
+
const filteredHeadingsOrder = ((propertiesOrder ?? [])
|
|
516
|
+
.filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
|
|
517
|
+
importConfig.setHeadingsOrder(filteredHeadingsOrder);
|
|
501
518
|
importConfig.setOriginProperties(res.properties);
|
|
519
|
+
|
|
520
|
+
const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
|
|
521
|
+
setFieldValue("properties", res.properties);
|
|
522
|
+
setFieldValue("propertiesOrder", mappedHeadings);
|
|
502
523
|
});
|
|
503
524
|
}
|
|
504
525
|
|
|
@@ -514,14 +535,30 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
514
535
|
};
|
|
515
536
|
|
|
516
537
|
const editable = collection?.editable === undefined || collection?.editable === true;
|
|
538
|
+
// @ts-ignore
|
|
539
|
+
const isMergedCollection = collection?.merged ?? false;
|
|
517
540
|
const collectionEditable = editable || isNewCollection;
|
|
518
541
|
|
|
542
|
+
const [deleteRequested, setDeleteRequested] = useState(false);
|
|
543
|
+
|
|
544
|
+
const deleteCollection = () => {
|
|
545
|
+
if (!collection) return;
|
|
546
|
+
configController?.deleteCollection({ id: collection.id }).then(() => {
|
|
547
|
+
setDeleteRequested(false);
|
|
548
|
+
handleCancel();
|
|
549
|
+
snackbarController.open({
|
|
550
|
+
message: "Collection deleted",
|
|
551
|
+
type: "success"
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
};
|
|
555
|
+
|
|
519
556
|
return <DialogContent fullHeight={true}>
|
|
520
557
|
<Formex value={formController}>
|
|
521
558
|
|
|
522
559
|
<>
|
|
523
560
|
{!isNewCollection && <Tabs value={currentView}
|
|
524
|
-
|
|
561
|
+
innerClassName={cls(defaultBorderMixin, "px-4 h-14 w-full justify-end bg-surface-50 dark:bg-surface-950 border-b")}
|
|
525
562
|
onValueChange={(v) => setCurrentView(v as EditorView)}>
|
|
526
563
|
<Tab value={"details"}>
|
|
527
564
|
Details
|
|
@@ -536,7 +573,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
536
573
|
|
|
537
574
|
<form noValidate
|
|
538
575
|
onSubmit={formController.handleSubmit}
|
|
539
|
-
className={
|
|
576
|
+
className={cls(
|
|
540
577
|
isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
|
|
541
578
|
"flex-grow flex flex-col relative")}>
|
|
542
579
|
|
|
@@ -551,9 +588,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
551
588
|
{currentView === "welcome" &&
|
|
552
589
|
<CollectionEditorWelcomeView
|
|
553
590
|
path={path}
|
|
554
|
-
onContinue={(importData) => {
|
|
591
|
+
onContinue={(importData, propertiesOrder) => {
|
|
592
|
+
// console.log("Import data", importData, propertiesOrder)
|
|
555
593
|
if (importData) {
|
|
556
|
-
onImportDataSet(importData);
|
|
594
|
+
onImportDataSet(importData, propertiesOrder);
|
|
557
595
|
setCurrentView("import_data_mapping");
|
|
558
596
|
} else {
|
|
559
597
|
setCurrentView("details");
|
|
@@ -594,7 +632,18 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
594
632
|
groups={groups}
|
|
595
633
|
parentCollectionIds={parentCollectionIds}
|
|
596
634
|
parentCollection={parentCollection}
|
|
597
|
-
isNewCollection={isNewCollection}
|
|
635
|
+
isNewCollection={isNewCollection}>
|
|
636
|
+
{!isNewCollection && isMergedCollection && <div className={"flex flex-col gap-4 mt-8"}>
|
|
637
|
+
<Typography variant={"body2"} color={"secondary"}>This collection is defined in code.
|
|
638
|
+
The changes done in this editor will override the properties defined in code.
|
|
639
|
+
You can delete the overridden values to revert to the state defined in code.
|
|
640
|
+
</Typography>
|
|
641
|
+
<Button variant={"neutral"}
|
|
642
|
+
onClick={() => {
|
|
643
|
+
setDeleteRequested(true);
|
|
644
|
+
}}>Reset to code</Button>
|
|
645
|
+
</div>}
|
|
646
|
+
</CollectionDetailsForm>}
|
|
598
647
|
|
|
599
648
|
{currentView === "subcollections" && collection &&
|
|
600
649
|
<SubcollectionsEditTab
|
|
@@ -706,7 +755,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
706
755
|
loading={isSubmitting}
|
|
707
756
|
disabled={isSubmitting || (currentView === "details" && !validValues)}
|
|
708
757
|
startIcon={currentView === "properties"
|
|
709
|
-
? <
|
|
758
|
+
? <CheckIcon/>
|
|
710
759
|
: undefined}
|
|
711
760
|
>
|
|
712
761
|
{currentView === "details" && "Next"}
|
|
@@ -728,12 +777,24 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
728
777
|
|
|
729
778
|
</Formex>
|
|
730
779
|
|
|
780
|
+
<ConfirmationDialog
|
|
781
|
+
open={deleteRequested}
|
|
782
|
+
onAccept={deleteCollection}
|
|
783
|
+
onCancel={() => setDeleteRequested(false)}
|
|
784
|
+
title={<>Delete the stored config?</>}
|
|
785
|
+
body={<> This will <b>not
|
|
786
|
+
delete any data</b>, only
|
|
787
|
+
the stored config, and reset to the code state.</>}/>
|
|
788
|
+
|
|
731
789
|
</DialogContent>
|
|
732
790
|
|
|
733
791
|
}
|
|
734
792
|
|
|
735
793
|
function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
|
|
736
|
-
const {
|
|
794
|
+
const {
|
|
795
|
+
properties,
|
|
796
|
+
...rest
|
|
797
|
+
} = collection;
|
|
737
798
|
const propertiesResult: PropertiesOrBuilders<any> = {};
|
|
738
799
|
if (properties) {
|
|
739
800
|
Object.keys(properties).forEach((key) => {
|
|
@@ -741,7 +802,10 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
|
|
|
741
802
|
});
|
|
742
803
|
}
|
|
743
804
|
|
|
744
|
-
return {
|
|
805
|
+
return {
|
|
806
|
+
...rest,
|
|
807
|
+
properties: propertiesResult
|
|
808
|
+
};
|
|
745
809
|
}
|
|
746
810
|
|
|
747
811
|
function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
|
|
@@ -761,7 +825,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
|
|
|
761
825
|
Object.keys(internalProperty.properties).forEach((key) => {
|
|
762
826
|
properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
|
|
763
827
|
});
|
|
764
|
-
internalProperty = {
|
|
828
|
+
internalProperty = {
|
|
829
|
+
...internalProperty,
|
|
830
|
+
properties
|
|
831
|
+
};
|
|
765
832
|
}
|
|
766
833
|
|
|
767
834
|
}
|
|
@@ -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}
|