@firecms/collection_editor 3.0.0-canary.23 → 3.0.0-canary.232
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 +10077 -4794
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10743 -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 +94 -19
- 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 +11 -7
- 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 +33 -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/ui/collection_editor/utils/strings.ts +13 -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
|
|
|
@@ -106,17 +117,22 @@ export function CollectionDetailsForm({
|
|
|
106
117
|
}
|
|
107
118
|
|
|
108
119
|
const showErrors = submitCount > 0;
|
|
120
|
+
|
|
109
121
|
return (
|
|
110
122
|
<div className={"overflow-auto my-auto"}>
|
|
111
123
|
<Container maxWidth={"4xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
|
|
112
124
|
|
|
113
125
|
<div>
|
|
114
126
|
<div
|
|
115
|
-
className="flex flex-row py-2 pt-3 items-center">
|
|
127
|
+
className="flex flex-row gap-2 py-2 pt-3 items-center">
|
|
116
128
|
<Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
|
|
117
129
|
{isNewCollection ? "New collection" : `${values?.name} collection`}
|
|
118
130
|
</Typography>
|
|
119
|
-
<
|
|
131
|
+
<DefaultDatabaseField databaseId={values.databaseId}
|
|
132
|
+
onDatabaseIdUpdate={updateDatabaseId}/>
|
|
133
|
+
|
|
134
|
+
<Tooltip title={"Change icon"}
|
|
135
|
+
asChild={true}>
|
|
120
136
|
<IconButton
|
|
121
137
|
shape={"square"}
|
|
122
138
|
onClick={() => setIconDialogOpen(true)}>
|
|
@@ -139,14 +155,15 @@ export function CollectionDetailsForm({
|
|
|
139
155
|
value={values.name ?? ""}
|
|
140
156
|
onChange={(e: any) => updateName(e.target.value)}
|
|
141
157
|
label={"Name"}
|
|
158
|
+
autoFocus={true}
|
|
142
159
|
required
|
|
143
160
|
error={showErrors && Boolean(errors.name)}/>
|
|
144
161
|
<FieldCaption error={touched.name && Boolean(errors.name)}>
|
|
145
|
-
{touched.name && Boolean(errors.name) ? errors.name : "Name of
|
|
162
|
+
{touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
|
|
146
163
|
</FieldCaption>
|
|
147
164
|
</div>
|
|
148
165
|
|
|
149
|
-
<div className={
|
|
166
|
+
<div className={cls("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
|
|
150
167
|
<Field name={"path"}
|
|
151
168
|
as={DebouncedTextField}
|
|
152
169
|
label={"Path"}
|
|
@@ -190,16 +207,23 @@ export function CollectionDetailsForm({
|
|
|
190
207
|
})}
|
|
191
208
|
</Autocomplete>
|
|
192
209
|
<FieldCaption>
|
|
193
|
-
{showErrors && Boolean(errors.group) ? errors.group : "Group
|
|
210
|
+
{showErrors && Boolean(errors.group) ? errors.group : "Group in the home page"}
|
|
194
211
|
</FieldCaption>
|
|
212
|
+
|
|
213
|
+
|
|
195
214
|
</div>}
|
|
196
215
|
|
|
197
|
-
<
|
|
216
|
+
<LayoutModeSwitch
|
|
217
|
+
className={"col-span-12"}
|
|
218
|
+
value={values.openEntityMode ?? "side_panel"}
|
|
219
|
+
onChange={(value) => setFieldValue("openEntityMode", value)}/>
|
|
220
|
+
|
|
221
|
+
<div className={"col-span-12 mt-8"}>
|
|
198
222
|
<ExpandablePanel
|
|
199
223
|
expanded={advancedPanelExpanded}
|
|
200
224
|
onExpandedChange={setAdvancedPanelExpanded}
|
|
201
225
|
title={
|
|
202
|
-
<div className="flex flex-row text-
|
|
226
|
+
<div className="flex flex-row text-surface-500">
|
|
203
227
|
<SettingsIcon/>
|
|
204
228
|
<Typography variant={"subtitle2"}
|
|
205
229
|
className="ml-2">
|
|
@@ -216,7 +240,7 @@ export function CollectionDetailsForm({
|
|
|
216
240
|
label={"Collection id"}
|
|
217
241
|
error={showErrors && Boolean(errors.id)}/>
|
|
218
242
|
<FieldCaption error={touched.id && Boolean(errors.id)}>
|
|
219
|
-
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
|
|
243
|
+
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
|
|
220
244
|
</FieldCaption>
|
|
221
245
|
</div>
|
|
222
246
|
|
|
@@ -235,6 +259,35 @@ export function CollectionDetailsForm({
|
|
|
235
259
|
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
|
|
236
260
|
</FieldCaption>
|
|
237
261
|
</div>
|
|
262
|
+
<div className={"col-span-12"}>
|
|
263
|
+
<TextField
|
|
264
|
+
error={showErrors && Boolean(errors.sideDialogWidth)}
|
|
265
|
+
name={"sideDialogWidth"}
|
|
266
|
+
type={"number"}
|
|
267
|
+
aria-describedby={"sideDialogWidth-helper"}
|
|
268
|
+
onChange={(e) => {
|
|
269
|
+
setFieldTouched("sideDialogWidth", true);
|
|
270
|
+
const value = e.target.value;
|
|
271
|
+
if (!value) {
|
|
272
|
+
setFieldValue("sideDialogWidth", null);
|
|
273
|
+
} else if (!isNaN(Number(value))) {
|
|
274
|
+
setFieldValue("sideDialogWidth", Number(value));
|
|
275
|
+
}
|
|
276
|
+
}}
|
|
277
|
+
endAdornment={<IconButton
|
|
278
|
+
size={"small"}
|
|
279
|
+
onClick={() => {
|
|
280
|
+
setFieldValue("sideDialogWidth", null);
|
|
281
|
+
}}
|
|
282
|
+
disabled={!values.sideDialogWidth}>
|
|
283
|
+
<CloseIcon size={"small"}/>
|
|
284
|
+
</IconButton>}
|
|
285
|
+
value={values.sideDialogWidth ?? ""}
|
|
286
|
+
label={"Side dialog width"}/>
|
|
287
|
+
<FieldCaption error={showErrors && Boolean(errors.singularName)}>
|
|
288
|
+
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
|
|
289
|
+
</FieldCaption>
|
|
290
|
+
</div>
|
|
238
291
|
<div className={"col-span-12"}>
|
|
239
292
|
<TextField
|
|
240
293
|
error={showErrors && Boolean(errors.description)}
|
|
@@ -242,7 +295,7 @@ export function CollectionDetailsForm({
|
|
|
242
295
|
value={values.description ?? ""}
|
|
243
296
|
onChange={handleChange}
|
|
244
297
|
multiline
|
|
245
|
-
|
|
298
|
+
minRows={2}
|
|
246
299
|
aria-describedby="description-helper-text"
|
|
247
300
|
label="Description"
|
|
248
301
|
/>
|
|
@@ -254,6 +307,8 @@ export function CollectionDetailsForm({
|
|
|
254
307
|
<div className={"col-span-12"}>
|
|
255
308
|
<Select
|
|
256
309
|
name="defaultSize"
|
|
310
|
+
size={"large"}
|
|
311
|
+
fullWidth={true}
|
|
257
312
|
label="Default row size"
|
|
258
313
|
position={"item-aligned"}
|
|
259
314
|
onChange={handleChange}
|
|
@@ -272,18 +327,15 @@ export function CollectionDetailsForm({
|
|
|
272
327
|
<div className={"col-span-12"}>
|
|
273
328
|
<Select
|
|
274
329
|
name="customId"
|
|
275
|
-
label="
|
|
330
|
+
label="Document IDs generation"
|
|
276
331
|
position={"item-aligned"}
|
|
332
|
+
size={"large"}
|
|
333
|
+
fullWidth={true}
|
|
277
334
|
disabled={customIdValue === "code_defined"}
|
|
278
335
|
onValueChange={(v) => {
|
|
279
336
|
if (v === "code_defined")
|
|
280
337
|
throw new Error("This should not happen");
|
|
281
|
-
|
|
282
|
-
setFieldValue("customId", true);
|
|
283
|
-
else if (v === "false")
|
|
284
|
-
setFieldValue("customId", false);
|
|
285
|
-
else if (v === "optional")
|
|
286
|
-
setFieldValue("customId", "optional");
|
|
338
|
+
setFieldValue("customId", v);
|
|
287
339
|
}}
|
|
288
340
|
value={customIdValue ?? ""}
|
|
289
341
|
renderValue={(value: any) => {
|
|
@@ -308,9 +360,10 @@ export function CollectionDetailsForm({
|
|
|
308
360
|
</SelectItem>
|
|
309
361
|
</Select>
|
|
310
362
|
</div>
|
|
311
|
-
<div className={"col-span-12"}>
|
|
363
|
+
<div className={"col-span-12 mt-4"}>
|
|
312
364
|
<BooleanSwitchWithLabel
|
|
313
365
|
position={"start"}
|
|
366
|
+
size={"large"}
|
|
314
367
|
label="Collection group"
|
|
315
368
|
onValueChange={(v) => setFieldValue("collectionGroup", v)}
|
|
316
369
|
value={values.collectionGroup ?? false}
|
|
@@ -324,6 +377,7 @@ export function CollectionDetailsForm({
|
|
|
324
377
|
<div className={"col-span-12"}>
|
|
325
378
|
<BooleanSwitchWithLabel
|
|
326
379
|
position={"start"}
|
|
380
|
+
size={"large"}
|
|
327
381
|
label="Enable text search for this collection"
|
|
328
382
|
onValueChange={(v) => setFieldValue("textSearchEnabled", v)}
|
|
329
383
|
value={values.textSearchEnabled ?? false}
|
|
@@ -334,9 +388,13 @@ export function CollectionDetailsForm({
|
|
|
334
388
|
for large collections, as it may incur in performance and cost issues.
|
|
335
389
|
</FieldCaption>
|
|
336
390
|
</div>
|
|
391
|
+
|
|
392
|
+
|
|
337
393
|
</div>
|
|
338
394
|
</ExpandablePanel>
|
|
339
395
|
|
|
396
|
+
{children}
|
|
397
|
+
|
|
340
398
|
</div>
|
|
341
399
|
|
|
342
400
|
</div>
|
|
@@ -363,3 +421,20 @@ export function CollectionDetailsForm({
|
|
|
363
421
|
</div>
|
|
364
422
|
);
|
|
365
423
|
}
|
|
424
|
+
|
|
425
|
+
function DefaultDatabaseField({
|
|
426
|
+
databaseId,
|
|
427
|
+
onDatabaseIdUpdate
|
|
428
|
+
}: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
|
|
429
|
+
|
|
430
|
+
return <Tooltip title={"Database ID"}
|
|
431
|
+
side={"top"}
|
|
432
|
+
align={"start"}>
|
|
433
|
+
<TextField size={"small"}
|
|
434
|
+
invisible={true}
|
|
435
|
+
inputClassName={"text-end"}
|
|
436
|
+
value={databaseId ?? ""}
|
|
437
|
+
onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
|
|
438
|
+
placeholder={"(default)"}></TextField>
|
|
439
|
+
</Tooltip>
|
|
440
|
+
}
|
|
@@ -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 color={"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}
|