@firecms/collection_editor 3.0.0-canary.2 → 3.0.0-canary.200
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 +10058 -4774
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10751 -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 +2 -2
- 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 +4 -3
- package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +1 -1
- package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
- package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
- package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
- package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
- package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
- package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
- package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
- package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
- package/dist/ui/collection_editor/utils/supported_fields.d.ts +2 -2
- package/dist/useCollectionEditorPlugin.d.ts +8 -11
- package/dist/utils/collections.d.ts +6 -0
- package/package.json +25 -36
- package/src/ConfigControllerProvider.tsx +68 -65
- 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 +3 -4
- 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/MissingReferenceWidget.tsx +2 -1
- package/src/ui/NewCollectionButton.tsx +12 -10
- package/src/ui/NewCollectionCard.tsx +3 -3
- package/src/ui/PropertyAddColumnComponent.tsx +11 -6
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +105 -29
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +114 -43
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +8 -7
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +39 -36
- package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
- package/src/ui/collection_editor/EnumForm.tsx +12 -9
- 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 +258 -80
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
- package/src/ui/collection_editor/PropertyTree.tsx +9 -7
- package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
- package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +26 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
- package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +7 -8
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +55 -49
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
- package/src/ui/collection_editor/properties/MapPropertyField.tsx +10 -10
- package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
- package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +5 -4
- 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/StringPropertyValidation.tsx +3 -4
- 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/supported_fields.tsx +3 -3
- package/src/useCollectionEditorPlugin.tsx +35 -36
- 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/dist/ui/collection_editor/properties/FieldHelperView.d.ts +0 -4
- package/src/ui/RootCollectionSuggestions.tsx +0 -63
- package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
- package/src/ui/collection_editor/properties/FieldHelperView.tsx +0 -13
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
|
-
import { EntityCollection, IconForView, SearchIconsView, singular, toSnakeCase, } from "@firecms/core";
|
|
2
|
+
import { EntityCollection, FieldCaption, IconForView, SearchIconsView, singular, toSnakeCase, } from "@firecms/core";
|
|
3
3
|
import {
|
|
4
4
|
Autocomplete,
|
|
5
5
|
AutocompleteItem,
|
|
6
6
|
BooleanSwitchWithLabel,
|
|
7
7
|
Chip,
|
|
8
|
-
|
|
8
|
+
CloseIcon,
|
|
9
|
+
cls,
|
|
9
10
|
Container,
|
|
10
11
|
DebouncedTextField,
|
|
11
12
|
Dialog,
|
|
@@ -20,8 +21,9 @@ import {
|
|
|
20
21
|
useAutoComplete
|
|
21
22
|
} from "@firecms/ui";
|
|
22
23
|
|
|
23
|
-
import { FieldHelperView } from "./properties/FieldHelperView";
|
|
24
24
|
import { Field, getIn, useFormex } from "@firecms/formex";
|
|
25
|
+
import { useCollectionEditorController } from "../../useCollectionEditorController";
|
|
26
|
+
import { LayoutModeSwitch } from "./LayoutModeSwitch";
|
|
25
27
|
|
|
26
28
|
export function CollectionDetailsForm({
|
|
27
29
|
isNewCollection,
|
|
@@ -29,7 +31,8 @@ export function CollectionDetailsForm({
|
|
|
29
31
|
existingPaths,
|
|
30
32
|
existingIds,
|
|
31
33
|
groups,
|
|
32
|
-
parentCollection
|
|
34
|
+
parentCollection,
|
|
35
|
+
children
|
|
33
36
|
}: {
|
|
34
37
|
isNewCollection: boolean,
|
|
35
38
|
reservedGroups?: string[];
|
|
@@ -38,6 +41,7 @@ export function CollectionDetailsForm({
|
|
|
38
41
|
groups: string[] | null;
|
|
39
42
|
parentCollection?: EntityCollection;
|
|
40
43
|
parentCollectionIds?: string[];
|
|
44
|
+
children?: React.ReactNode;
|
|
41
45
|
}) {
|
|
42
46
|
|
|
43
47
|
const groupRef = React.useRef<HTMLInputElement>(null);
|
|
@@ -52,9 +56,15 @@ export function CollectionDetailsForm({
|
|
|
52
56
|
submitCount
|
|
53
57
|
} = useFormex<EntityCollection>();
|
|
54
58
|
|
|
59
|
+
const collectionEditor = useCollectionEditorController();
|
|
60
|
+
|
|
55
61
|
const [iconDialogOpen, setIconDialogOpen] = useState(false);
|
|
56
62
|
const [advancedPanelExpanded, setAdvancedPanelExpanded] = useState(false);
|
|
57
63
|
|
|
64
|
+
const updateDatabaseId = (databaseId: string) => {
|
|
65
|
+
setFieldValue("databaseId", databaseId ?? undefined);
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
const updateName = (name: string) => {
|
|
59
69
|
setFieldValue("name", name);
|
|
60
70
|
|
|
@@ -113,11 +123,15 @@ export function CollectionDetailsForm({
|
|
|
113
123
|
|
|
114
124
|
<div>
|
|
115
125
|
<div
|
|
116
|
-
className="flex flex-row py-2 pt-3 items-center">
|
|
126
|
+
className="flex flex-row gap-2 py-2 pt-3 items-center">
|
|
117
127
|
<Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
|
|
118
128
|
{isNewCollection ? "New collection" : `${values?.name} collection`}
|
|
119
129
|
</Typography>
|
|
120
|
-
<
|
|
130
|
+
<DefaultDatabaseField databaseId={values.databaseId}
|
|
131
|
+
onDatabaseIdUpdate={updateDatabaseId}/>
|
|
132
|
+
|
|
133
|
+
<Tooltip title={"Change icon"}
|
|
134
|
+
asChild={true}>
|
|
121
135
|
<IconButton
|
|
122
136
|
shape={"square"}
|
|
123
137
|
onClick={() => setIconDialogOpen(true)}>
|
|
@@ -140,14 +154,15 @@ export function CollectionDetailsForm({
|
|
|
140
154
|
value={values.name ?? ""}
|
|
141
155
|
onChange={(e: any) => updateName(e.target.value)}
|
|
142
156
|
label={"Name"}
|
|
157
|
+
autoFocus={true}
|
|
143
158
|
required
|
|
144
159
|
error={showErrors && Boolean(errors.name)}/>
|
|
145
|
-
<
|
|
146
|
-
{touched.name && Boolean(errors.name) ? errors.name : "Name of
|
|
147
|
-
</
|
|
160
|
+
<FieldCaption error={touched.name && Boolean(errors.name)}>
|
|
161
|
+
{touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
|
|
162
|
+
</FieldCaption>
|
|
148
163
|
</div>
|
|
149
164
|
|
|
150
|
-
<div className={
|
|
165
|
+
<div className={cls("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
|
|
151
166
|
<Field name={"path"}
|
|
152
167
|
as={DebouncedTextField}
|
|
153
168
|
label={"Path"}
|
|
@@ -155,11 +170,11 @@ export function CollectionDetailsForm({
|
|
|
155
170
|
required
|
|
156
171
|
error={showErrors && Boolean(errors.path)}/>
|
|
157
172
|
|
|
158
|
-
<
|
|
173
|
+
<FieldCaption error={touched.path && Boolean(errors.path)}>
|
|
159
174
|
{touched.path && Boolean(errors.path)
|
|
160
175
|
? errors.path
|
|
161
176
|
: isSubcollection ? "Relative path to the parent (no need to include the parent path)" : "Path that this collection is stored in, in the database"}
|
|
162
|
-
</
|
|
177
|
+
</FieldCaption>
|
|
163
178
|
|
|
164
179
|
</div>
|
|
165
180
|
|
|
@@ -190,17 +205,24 @@ export function CollectionDetailsForm({
|
|
|
190
205
|
</AutocompleteItem>;
|
|
191
206
|
})}
|
|
192
207
|
</Autocomplete>
|
|
193
|
-
<
|
|
194
|
-
{showErrors && Boolean(errors.group) ? errors.group : "Group
|
|
195
|
-
</
|
|
208
|
+
<FieldCaption>
|
|
209
|
+
{showErrors && Boolean(errors.group) ? errors.group : "Group in the home page"}
|
|
210
|
+
</FieldCaption>
|
|
211
|
+
|
|
212
|
+
|
|
196
213
|
</div>}
|
|
197
214
|
|
|
198
|
-
<
|
|
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"}>
|
|
199
221
|
<ExpandablePanel
|
|
200
222
|
expanded={advancedPanelExpanded}
|
|
201
223
|
onExpandedChange={setAdvancedPanelExpanded}
|
|
202
224
|
title={
|
|
203
|
-
<div className="flex flex-row text-
|
|
225
|
+
<div className="flex flex-row text-surface-500">
|
|
204
226
|
<SettingsIcon/>
|
|
205
227
|
<Typography variant={"subtitle2"}
|
|
206
228
|
className="ml-2">
|
|
@@ -216,9 +238,9 @@ export function CollectionDetailsForm({
|
|
|
216
238
|
disabled={!isNewCollection}
|
|
217
239
|
label={"Collection id"}
|
|
218
240
|
error={showErrors && Boolean(errors.id)}/>
|
|
219
|
-
<
|
|
220
|
-
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
|
|
221
|
-
</
|
|
241
|
+
<FieldCaption error={touched.id && Boolean(errors.id)}>
|
|
242
|
+
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
|
|
243
|
+
</FieldCaption>
|
|
222
244
|
</div>
|
|
223
245
|
|
|
224
246
|
<div className={"col-span-12"}>
|
|
@@ -232,9 +254,38 @@ export function CollectionDetailsForm({
|
|
|
232
254
|
}}
|
|
233
255
|
value={values.singularName ?? ""}
|
|
234
256
|
label={"Singular name"}/>
|
|
235
|
-
<
|
|
257
|
+
<FieldCaption error={showErrors && Boolean(errors.singularName)}>
|
|
236
258
|
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
|
|
237
|
-
</
|
|
259
|
+
</FieldCaption>
|
|
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>
|
|
238
289
|
</div>
|
|
239
290
|
<div className={"col-span-12"}>
|
|
240
291
|
<TextField
|
|
@@ -247,14 +298,16 @@ export function CollectionDetailsForm({
|
|
|
247
298
|
aria-describedby="description-helper-text"
|
|
248
299
|
label="Description"
|
|
249
300
|
/>
|
|
250
|
-
<
|
|
301
|
+
<FieldCaption error={showErrors && Boolean(errors.description)}>
|
|
251
302
|
{showErrors && Boolean(errors.description) ? errors.description : "Description of the collection, you can use markdown"}
|
|
252
|
-
</
|
|
303
|
+
</FieldCaption>
|
|
253
304
|
</div>
|
|
254
305
|
|
|
255
306
|
<div className={"col-span-12"}>
|
|
256
307
|
<Select
|
|
257
308
|
name="defaultSize"
|
|
309
|
+
size={"large"}
|
|
310
|
+
fullWidth={true}
|
|
258
311
|
label="Default row size"
|
|
259
312
|
position={"item-aligned"}
|
|
260
313
|
onChange={handleChange}
|
|
@@ -273,8 +326,10 @@ export function CollectionDetailsForm({
|
|
|
273
326
|
<div className={"col-span-12"}>
|
|
274
327
|
<Select
|
|
275
328
|
name="customId"
|
|
276
|
-
label="
|
|
329
|
+
label="Document IDs generation"
|
|
277
330
|
position={"item-aligned"}
|
|
331
|
+
size={"large"}
|
|
332
|
+
fullWidth={true}
|
|
278
333
|
disabled={customIdValue === "code_defined"}
|
|
279
334
|
onValueChange={(v) => {
|
|
280
335
|
if (v === "code_defined")
|
|
@@ -316,11 +371,11 @@ export function CollectionDetailsForm({
|
|
|
316
371
|
onValueChange={(v) => setFieldValue("collectionGroup", v)}
|
|
317
372
|
value={values.collectionGroup ?? false}
|
|
318
373
|
/>
|
|
319
|
-
<
|
|
374
|
+
<FieldCaption>
|
|
320
375
|
A collection group consists of all collections with the same path. This allows
|
|
321
376
|
you
|
|
322
377
|
to query over multiple collections at once.
|
|
323
|
-
</
|
|
378
|
+
</FieldCaption>
|
|
324
379
|
</div>
|
|
325
380
|
<div className={"col-span-12"}>
|
|
326
381
|
<BooleanSwitchWithLabel
|
|
@@ -329,15 +384,19 @@ export function CollectionDetailsForm({
|
|
|
329
384
|
onValueChange={(v) => setFieldValue("textSearchEnabled", v)}
|
|
330
385
|
value={values.textSearchEnabled ?? false}
|
|
331
386
|
/>
|
|
332
|
-
<
|
|
387
|
+
<FieldCaption>
|
|
333
388
|
Allow text search for this collection. If you have not specified a text search
|
|
334
389
|
delegate, this will use the built-in local text search. This is not recommended
|
|
335
390
|
for large collections, as it may incur in performance and cost issues.
|
|
336
|
-
</
|
|
391
|
+
</FieldCaption>
|
|
337
392
|
</div>
|
|
393
|
+
|
|
394
|
+
|
|
338
395
|
</div>
|
|
339
396
|
</ExpandablePanel>
|
|
340
397
|
|
|
398
|
+
{children}
|
|
399
|
+
|
|
341
400
|
</div>
|
|
342
401
|
|
|
343
402
|
</div>
|
|
@@ -364,3 +423,20 @@ export function CollectionDetailsForm({
|
|
|
364
423
|
</div>
|
|
365
424
|
);
|
|
366
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}/>}
|
|
@@ -136,7 +142,7 @@ type EditorView = "welcome"
|
|
|
136
142
|
| "extra_view"
|
|
137
143
|
| "subcollections";
|
|
138
144
|
|
|
139
|
-
export function CollectionEditor
|
|
145
|
+
export function CollectionEditor(props: CollectionEditorDialogProps & {
|
|
140
146
|
handleCancel: () => void,
|
|
141
147
|
setFormDirty: (dirty: boolean) => void
|
|
142
148
|
}) {
|
|
@@ -154,14 +160,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
154
160
|
const collectionsInThisLevel = (props.parentCollection ? props.parentCollection.subcollections : collections) ?? [];
|
|
155
161
|
const existingPaths = collectionsInThisLevel.map(col => col.path.trim().toLowerCase());
|
|
156
162
|
const existingIds = collectionsInThisLevel.map(col => col.id?.trim().toLowerCase()).filter(Boolean) as string[];
|
|
157
|
-
const [collection, setCollection] = React.useState<PersistedCollection<
|
|
163
|
+
const [collection, setCollection] = React.useState<PersistedCollection<any> | undefined>();
|
|
158
164
|
const [initialLoadingCompleted, setInitialLoadingCompleted] = React.useState(false);
|
|
159
165
|
|
|
160
166
|
useEffect(() => {
|
|
161
167
|
try {
|
|
162
168
|
if (navigation.initialised) {
|
|
163
169
|
if (props.editedCollectionId) {
|
|
164
|
-
setCollection(navigation.getCollectionFromPaths
|
|
170
|
+
setCollection(navigation.getCollectionFromPaths([...(props.parentCollectionIds ?? []), props.editedCollectionId]));
|
|
165
171
|
} else {
|
|
166
172
|
setCollection(undefined);
|
|
167
173
|
}
|
|
@@ -170,7 +176,8 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
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
|
}
|
|
@@ -186,14 +193,14 @@ export function CollectionEditor<M extends Record<string, any>>(props: Collectio
|
|
|
186
193
|
}
|
|
187
194
|
: undefined;
|
|
188
195
|
|
|
189
|
-
const initialValues: PersistedCollection<
|
|
196
|
+
const initialValues: PersistedCollection<any> = initialCollection
|
|
190
197
|
? applyPropertyConfigs(initialCollection, propertyConfigs)
|
|
191
198
|
: {
|
|
192
199
|
id: initialValuesProp?.path ?? randomString(16),
|
|
193
200
|
path: initialValuesProp?.path ?? "",
|
|
194
201
|
name: initialValuesProp?.name ?? "",
|
|
195
202
|
group: initialValuesProp?.group ?? "",
|
|
196
|
-
properties: {} as PropertiesOrBuilders
|
|
203
|
+
properties: {} as PropertiesOrBuilders,
|
|
197
204
|
propertiesOrder: [],
|
|
198
205
|
icon: coolIconKeys[Math.floor(Math.random() * coolIconKeys.length)],
|
|
199
206
|
ownerId: authController.user?.uid ?? ""
|
|
@@ -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 {
|
|
@@ -473,31 +483,43 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
473
483
|
submitCount
|
|
474
484
|
} = formController;
|
|
475
485
|
|
|
476
|
-
|
|
486
|
+
// TODO: getting data is only working in root collections with this code
|
|
487
|
+
const path = values.path;
|
|
477
488
|
const updatedFullPath = fullPath?.includes("/") ? fullPath?.split("/").slice(0, -1).join("/") + "/" + path : path; // TODO: this path is wrong
|
|
478
489
|
const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
|
|
479
490
|
|
|
480
491
|
const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
|
|
481
|
-
const resolvedPath = !pathError ? navigation.
|
|
482
|
-
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;
|
|
483
501
|
|
|
484
502
|
useEffect(() => {
|
|
485
503
|
setFormDirty(dirty);
|
|
486
504
|
}, [dirty]);
|
|
487
505
|
|
|
488
|
-
function onImportDataSet(data: object[]) {
|
|
506
|
+
function onImportDataSet(data: object[], propertiesOrder?: string[]) {
|
|
489
507
|
importConfig.setInUse(true);
|
|
490
508
|
buildEntityPropertiesFromData(data, getInferenceType)
|
|
491
509
|
.then((properties) => {
|
|
492
510
|
const res = cleanPropertiesFromImport(properties);
|
|
493
511
|
|
|
494
|
-
setFieldValue("properties", res.properties);
|
|
495
|
-
setFieldValue("propertiesOrder", Object.keys(res.properties));
|
|
496
|
-
|
|
497
512
|
importConfig.setIdColumn(res.idColumn);
|
|
498
513
|
importConfig.setImportData(data);
|
|
499
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);
|
|
500
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);
|
|
501
523
|
});
|
|
502
524
|
}
|
|
503
525
|
|
|
@@ -513,14 +535,30 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
513
535
|
};
|
|
514
536
|
|
|
515
537
|
const editable = collection?.editable === undefined || collection?.editable === true;
|
|
538
|
+
// @ts-ignore
|
|
539
|
+
const isMergedCollection = collection?.merged ?? false;
|
|
516
540
|
const collectionEditable = editable || isNewCollection;
|
|
517
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
|
+
|
|
518
556
|
return <DialogContent fullHeight={true}>
|
|
519
557
|
<Formex value={formController}>
|
|
520
558
|
|
|
521
559
|
<>
|
|
522
560
|
{!isNewCollection && <Tabs value={currentView}
|
|
523
|
-
|
|
561
|
+
innerClassName={cls(defaultBorderMixin, "px-4 h-14 w-full justify-end bg-surface-50 dark:bg-surface-950 border-b")}
|
|
524
562
|
onValueChange={(v) => setCurrentView(v as EditorView)}>
|
|
525
563
|
<Tab value={"details"}>
|
|
526
564
|
Details
|
|
@@ -535,7 +573,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
535
573
|
|
|
536
574
|
<form noValidate
|
|
537
575
|
onSubmit={formController.handleSubmit}
|
|
538
|
-
className={
|
|
576
|
+
className={cls(
|
|
539
577
|
isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
|
|
540
578
|
"flex-grow flex flex-col relative")}>
|
|
541
579
|
|
|
@@ -550,9 +588,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
550
588
|
{currentView === "welcome" &&
|
|
551
589
|
<CollectionEditorWelcomeView
|
|
552
590
|
path={path}
|
|
553
|
-
onContinue={(importData) => {
|
|
591
|
+
onContinue={(importData, propertiesOrder) => {
|
|
592
|
+
// console.log("Import data", importData, propertiesOrder)
|
|
554
593
|
if (importData) {
|
|
555
|
-
onImportDataSet(importData);
|
|
594
|
+
onImportDataSet(importData, propertiesOrder);
|
|
556
595
|
setCurrentView("import_data_mapping");
|
|
557
596
|
} else {
|
|
558
597
|
setCurrentView("details");
|
|
@@ -575,6 +614,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
575
614
|
{currentView === "import_data_saving" && importConfig &&
|
|
576
615
|
<ImportSaveInProgress importConfig={importConfig}
|
|
577
616
|
collection={values}
|
|
617
|
+
path={path}
|
|
578
618
|
onImportSuccess={async (importedCollection) => {
|
|
579
619
|
snackbarController.open({
|
|
580
620
|
type: "info",
|
|
@@ -592,7 +632,18 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
592
632
|
groups={groups}
|
|
593
633
|
parentCollectionIds={parentCollectionIds}
|
|
594
634
|
parentCollection={parentCollection}
|
|
595
|
-
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>}
|
|
596
647
|
|
|
597
648
|
{currentView === "subcollections" && collection &&
|
|
598
649
|
<SubcollectionsEditTab
|
|
@@ -704,7 +755,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
704
755
|
loading={isSubmitting}
|
|
705
756
|
disabled={isSubmitting || (currentView === "details" && !validValues)}
|
|
706
757
|
startIcon={currentView === "properties"
|
|
707
|
-
? <
|
|
758
|
+
? <CheckIcon/>
|
|
708
759
|
: undefined}
|
|
709
760
|
>
|
|
710
761
|
{currentView === "details" && "Next"}
|
|
@@ -726,18 +777,35 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
726
777
|
|
|
727
778
|
</Formex>
|
|
728
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
|
+
|
|
729
789
|
</DialogContent>
|
|
730
790
|
|
|
731
791
|
}
|
|
732
792
|
|
|
733
793
|
function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
|
|
734
|
-
const {
|
|
794
|
+
const {
|
|
795
|
+
properties,
|
|
796
|
+
...rest
|
|
797
|
+
} = collection;
|
|
735
798
|
const propertiesResult: PropertiesOrBuilders<any> = {};
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
799
|
+
if (properties) {
|
|
800
|
+
Object.keys(properties).forEach((key) => {
|
|
801
|
+
propertiesResult[key] = applyPropertiesConfig(properties[key] as PropertyOrBuilder, propertyConfigs);
|
|
802
|
+
});
|
|
803
|
+
}
|
|
739
804
|
|
|
740
|
-
return {
|
|
805
|
+
return {
|
|
806
|
+
...rest,
|
|
807
|
+
properties: propertiesResult
|
|
808
|
+
};
|
|
741
809
|
}
|
|
742
810
|
|
|
743
811
|
function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
|
|
@@ -757,7 +825,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
|
|
|
757
825
|
Object.keys(internalProperty.properties).forEach((key) => {
|
|
758
826
|
properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
|
|
759
827
|
});
|
|
760
|
-
internalProperty = {
|
|
828
|
+
internalProperty = {
|
|
829
|
+
...internalProperty,
|
|
830
|
+
properties
|
|
831
|
+
};
|
|
761
832
|
}
|
|
762
833
|
|
|
763
834
|
}
|