@firecms/collection_editor 3.0.0-canary.24 → 3.0.0-canary.241
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/README.md +165 -1
- package/dist/ConfigControllerProvider.d.ts +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +10109 -4774
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10795 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +3 -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 +2 -2
- 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 +64 -66
- package/src/index.ts +1 -0
- package/src/types/collection_editor_controller.tsx +6 -5
- 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 +112 -18
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +101 -34
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +13 -28
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +41 -39
- 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 +32 -32
- package/src/utils/collections.ts +37 -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,42 @@ 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
|
|
|
216
|
+
<LayoutModeSwitch
|
|
217
|
+
className={"col-span-12"}
|
|
218
|
+
value={values.openEntityMode ?? "side_panel"}
|
|
219
|
+
onChange={(value) => setFieldValue("openEntityMode", value)}/>
|
|
220
|
+
|
|
197
221
|
<div className={"col-span-12"}>
|
|
222
|
+
<BooleanSwitchWithLabel
|
|
223
|
+
position={"start"}
|
|
224
|
+
size={"large"}
|
|
225
|
+
allowIndeterminate={true}
|
|
226
|
+
label={values.history === null ? "Document history revisions enabled if enabled globally" : (
|
|
227
|
+
values.history ? "Document history revisions ENABLED" : "Document history revisions NOT enabled"
|
|
228
|
+
)}
|
|
229
|
+
onValueChange={(v) => setFieldValue("history", v)}
|
|
230
|
+
value={values.history ?? null}
|
|
231
|
+
/>
|
|
232
|
+
<FieldCaption>
|
|
233
|
+
When enabled, each document in this collection will have a history of changes.
|
|
234
|
+
This is useful for auditing purposes. The data is stored in a subcollection of the document
|
|
235
|
+
in your database, called <b>__history</b>.
|
|
236
|
+
</FieldCaption>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
<div className={"col-span-12 mt-8"}>
|
|
198
241
|
<ExpandablePanel
|
|
199
242
|
expanded={advancedPanelExpanded}
|
|
200
243
|
onExpandedChange={setAdvancedPanelExpanded}
|
|
201
244
|
title={
|
|
202
|
-
<div className="flex flex-row text-
|
|
245
|
+
<div className="flex flex-row text-surface-500">
|
|
203
246
|
<SettingsIcon/>
|
|
204
247
|
<Typography variant={"subtitle2"}
|
|
205
248
|
className="ml-2">
|
|
@@ -216,7 +259,7 @@ export function CollectionDetailsForm({
|
|
|
216
259
|
label={"Collection id"}
|
|
217
260
|
error={showErrors && Boolean(errors.id)}/>
|
|
218
261
|
<FieldCaption error={touched.id && Boolean(errors.id)}>
|
|
219
|
-
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
|
|
262
|
+
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
|
|
220
263
|
</FieldCaption>
|
|
221
264
|
</div>
|
|
222
265
|
|
|
@@ -235,6 +278,35 @@ export function CollectionDetailsForm({
|
|
|
235
278
|
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
|
|
236
279
|
</FieldCaption>
|
|
237
280
|
</div>
|
|
281
|
+
<div className={"col-span-12"}>
|
|
282
|
+
<TextField
|
|
283
|
+
error={showErrors && Boolean(errors.sideDialogWidth)}
|
|
284
|
+
name={"sideDialogWidth"}
|
|
285
|
+
type={"number"}
|
|
286
|
+
aria-describedby={"sideDialogWidth-helper"}
|
|
287
|
+
onChange={(e) => {
|
|
288
|
+
setFieldTouched("sideDialogWidth", true);
|
|
289
|
+
const value = e.target.value;
|
|
290
|
+
if (!value) {
|
|
291
|
+
setFieldValue("sideDialogWidth", null);
|
|
292
|
+
} else if (!isNaN(Number(value))) {
|
|
293
|
+
setFieldValue("sideDialogWidth", Number(value));
|
|
294
|
+
}
|
|
295
|
+
}}
|
|
296
|
+
endAdornment={<IconButton
|
|
297
|
+
size={"small"}
|
|
298
|
+
onClick={() => {
|
|
299
|
+
setFieldValue("sideDialogWidth", null);
|
|
300
|
+
}}
|
|
301
|
+
disabled={!values.sideDialogWidth}>
|
|
302
|
+
<CloseIcon size={"small"}/>
|
|
303
|
+
</IconButton>}
|
|
304
|
+
value={values.sideDialogWidth ?? ""}
|
|
305
|
+
label={"Side dialog width"}/>
|
|
306
|
+
<FieldCaption error={showErrors && Boolean(errors.singularName)}>
|
|
307
|
+
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
|
|
308
|
+
</FieldCaption>
|
|
309
|
+
</div>
|
|
238
310
|
<div className={"col-span-12"}>
|
|
239
311
|
<TextField
|
|
240
312
|
error={showErrors && Boolean(errors.description)}
|
|
@@ -242,7 +314,7 @@ export function CollectionDetailsForm({
|
|
|
242
314
|
value={values.description ?? ""}
|
|
243
315
|
onChange={handleChange}
|
|
244
316
|
multiline
|
|
245
|
-
|
|
317
|
+
minRows={2}
|
|
246
318
|
aria-describedby="description-helper-text"
|
|
247
319
|
label="Description"
|
|
248
320
|
/>
|
|
@@ -254,6 +326,8 @@ export function CollectionDetailsForm({
|
|
|
254
326
|
<div className={"col-span-12"}>
|
|
255
327
|
<Select
|
|
256
328
|
name="defaultSize"
|
|
329
|
+
size={"large"}
|
|
330
|
+
fullWidth={true}
|
|
257
331
|
label="Default row size"
|
|
258
332
|
position={"item-aligned"}
|
|
259
333
|
onChange={handleChange}
|
|
@@ -272,18 +346,15 @@ export function CollectionDetailsForm({
|
|
|
272
346
|
<div className={"col-span-12"}>
|
|
273
347
|
<Select
|
|
274
348
|
name="customId"
|
|
275
|
-
label="
|
|
349
|
+
label="Document IDs generation"
|
|
276
350
|
position={"item-aligned"}
|
|
351
|
+
size={"large"}
|
|
352
|
+
fullWidth={true}
|
|
277
353
|
disabled={customIdValue === "code_defined"}
|
|
278
354
|
onValueChange={(v) => {
|
|
279
355
|
if (v === "code_defined")
|
|
280
356
|
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");
|
|
357
|
+
setFieldValue("customId", v);
|
|
287
358
|
}}
|
|
288
359
|
value={customIdValue ?? ""}
|
|
289
360
|
renderValue={(value: any) => {
|
|
@@ -308,9 +379,10 @@ export function CollectionDetailsForm({
|
|
|
308
379
|
</SelectItem>
|
|
309
380
|
</Select>
|
|
310
381
|
</div>
|
|
311
|
-
<div className={"col-span-12"}>
|
|
382
|
+
<div className={"col-span-12 mt-4"}>
|
|
312
383
|
<BooleanSwitchWithLabel
|
|
313
384
|
position={"start"}
|
|
385
|
+
size={"large"}
|
|
314
386
|
label="Collection group"
|
|
315
387
|
onValueChange={(v) => setFieldValue("collectionGroup", v)}
|
|
316
388
|
value={values.collectionGroup ?? false}
|
|
@@ -324,6 +396,7 @@ export function CollectionDetailsForm({
|
|
|
324
396
|
<div className={"col-span-12"}>
|
|
325
397
|
<BooleanSwitchWithLabel
|
|
326
398
|
position={"start"}
|
|
399
|
+
size={"large"}
|
|
327
400
|
label="Enable text search for this collection"
|
|
328
401
|
onValueChange={(v) => setFieldValue("textSearchEnabled", v)}
|
|
329
402
|
value={values.textSearchEnabled ?? false}
|
|
@@ -334,9 +407,13 @@ export function CollectionDetailsForm({
|
|
|
334
407
|
for large collections, as it may incur in performance and cost issues.
|
|
335
408
|
</FieldCaption>
|
|
336
409
|
</div>
|
|
410
|
+
|
|
411
|
+
|
|
337
412
|
</div>
|
|
338
413
|
</ExpandablePanel>
|
|
339
414
|
|
|
415
|
+
{children}
|
|
416
|
+
|
|
340
417
|
</div>
|
|
341
418
|
|
|
342
419
|
</div>
|
|
@@ -363,3 +440,20 @@ export function CollectionDetailsForm({
|
|
|
363
440
|
</div>
|
|
364
441
|
);
|
|
365
442
|
}
|
|
443
|
+
|
|
444
|
+
function DefaultDatabaseField({
|
|
445
|
+
databaseId,
|
|
446
|
+
onDatabaseIdUpdate
|
|
447
|
+
}: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
|
|
448
|
+
|
|
449
|
+
return <Tooltip title={"Database ID"}
|
|
450
|
+
side={"top"}
|
|
451
|
+
align={"start"}>
|
|
452
|
+
<TextField size={"small"}
|
|
453
|
+
invisible={true}
|
|
454
|
+
inputClassName={"text-end"}
|
|
455
|
+
value={databaseId ?? ""}
|
|
456
|
+
onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
|
|
457
|
+
placeholder={"(default)"}></TextField>
|
|
458
|
+
</Tooltip>
|
|
459
|
+
}
|
|
@@ -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
|
}
|