@firecms/collection_editor 3.0.0-canary.18 → 3.0.0-canary.182
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +114 -21
- package/dist/ConfigControllerProvider.d.ts +11 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +10069 -4770
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10759 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +14 -2
- package/dist/types/collection_inference.d.ts +1 -1
- package/dist/types/config_permissions.d.ts +2 -2
- package/dist/types/persisted_collection.d.ts +1 -1
- package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
- package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
- package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
- package/dist/ui/collection_editor/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 +17 -11
- package/dist/utils/collections.d.ts +6 -0
- package/package.json +24 -35
- package/src/ConfigControllerProvider.tsx +76 -64
- package/src/index.ts +1 -0
- package/src/types/collection_editor_controller.tsx +14 -4
- package/src/types/collection_inference.ts +1 -1
- package/src/types/config_permissions.ts +1 -1
- package/src/types/persisted_collection.ts +2 -3
- package/src/ui/CollectionViewHeaderAction.tsx +10 -5
- package/src/ui/EditorCollectionAction.tsx +10 -63
- package/src/ui/EditorCollectionActionStart.tsx +88 -0
- package/src/ui/HomePageEditorCollectionAction.tsx +19 -13
- package/src/ui/NewCollectionButton.tsx +1 -1
- package/src/ui/NewCollectionCard.tsx +3 -3
- package/src/ui/PropertyAddColumnComponent.tsx +11 -6
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +90 -11
- 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 +39 -36
- package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
- package/src/ui/collection_editor/EnumForm.tsx +10 -6
- package/src/ui/collection_editor/GetCodeDialog.tsx +56 -26
- package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
- package/src/ui/collection_editor/PropertyEditView.tsx +257 -80
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
- package/src/ui/collection_editor/PropertyTree.tsx +9 -7
- package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
- package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +26 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
- package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
- package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
- package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -0
- package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
- package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
- package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
- package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
- package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
- package/src/ui/collection_editor/templates/pages_template.ts +1 -6
- package/src/useCollectionEditorPlugin.tsx +41 -31
- package/src/utils/collections.ts +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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PluginHomePageAdditionalCardsProps, useAuthController } from "@firecms/core";
|
|
2
|
-
import { AddIcon, Card,
|
|
2
|
+
import { AddIcon, Card, cls, Typography } from "@firecms/ui";
|
|
3
3
|
import { useCollectionEditorController } from "../useCollectionEditorController";
|
|
4
4
|
|
|
5
5
|
export function NewCollectionCard({
|
|
@@ -20,7 +20,7 @@ export function NewCollectionCard({
|
|
|
20
20
|
: true;
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<Card className={
|
|
23
|
+
<Card className={cls("h-full p-4 min-h-[124px]")}
|
|
24
24
|
onClick={collectionEditorController && canCreateCollections
|
|
25
25
|
? () => collectionEditorController.createCollection({
|
|
26
26
|
initialValues: group ? { group } : undefined,
|
|
@@ -31,7 +31,7 @@ export function NewCollectionCard({
|
|
|
31
31
|
: undefined}>
|
|
32
32
|
|
|
33
33
|
<div
|
|
34
|
-
className="flex
|
|
34
|
+
className="flex items-center justify-center h-full w-full flex-grow flex-col">
|
|
35
35
|
<AddIcon color="primary" size={"large"}/>
|
|
36
36
|
<Typography color="primary"
|
|
37
37
|
variant={"caption"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDefaultPropertiesOrder, useAuthController } from "@firecms/core";
|
|
1
|
+
import { EntityTableController, getDefaultPropertiesOrder, useAuthController } from "@firecms/core";
|
|
2
2
|
import { AddIcon, Tooltip } from "@firecms/ui";
|
|
3
3
|
import { useCollectionEditorController } from "../useCollectionEditorController";
|
|
4
4
|
import { PersistedCollection } from "../types/persisted_collection";
|
|
@@ -6,11 +6,13 @@ import { PersistedCollection } from "../types/persisted_collection";
|
|
|
6
6
|
export function PropertyAddColumnComponent({
|
|
7
7
|
fullPath,
|
|
8
8
|
parentCollectionIds,
|
|
9
|
-
collection
|
|
9
|
+
collection,
|
|
10
|
+
tableController
|
|
10
11
|
}: {
|
|
11
12
|
fullPath: string,
|
|
12
13
|
parentCollectionIds: string[],
|
|
13
14
|
collection: PersistedCollection;
|
|
15
|
+
tableController: EntityTableController;
|
|
14
16
|
}) {
|
|
15
17
|
|
|
16
18
|
const authController = useAuthController();
|
|
@@ -23,16 +25,19 @@ export function PropertyAddColumnComponent({
|
|
|
23
25
|
: true;
|
|
24
26
|
|
|
25
27
|
return (
|
|
26
|
-
<Tooltip
|
|
28
|
+
<Tooltip
|
|
29
|
+
asChild={true}
|
|
30
|
+
title={canEditCollection ? "Add new property" : "You don't have permission to add new properties"}>
|
|
27
31
|
<div
|
|
28
|
-
className={"p-0.5 w-20 h-full flex items-center justify-center cursor-pointer bg-
|
|
29
|
-
// className={onHover ? "bg-white dark:bg-
|
|
32
|
+
className={"p-0.5 w-20 h-full flex items-center justify-center cursor-pointer bg-surface-100 bg-opacity-40 hover:bg-surface-100 dark:bg-surface-950 dark:bg-opacity-40 dark:hover:bg-surface-950"}
|
|
33
|
+
// className={onHover ? "bg-white dark:bg-surface-950" : undefined}
|
|
30
34
|
onClick={() => {
|
|
31
35
|
collectionEditorController.editProperty({
|
|
32
36
|
editedCollectionId: collection.id,
|
|
33
37
|
parentCollectionIds,
|
|
34
38
|
currentPropertiesOrder: getDefaultPropertiesOrder(collection),
|
|
35
|
-
collection
|
|
39
|
+
collection,
|
|
40
|
+
existingEntities: tableController.data
|
|
36
41
|
});
|
|
37
42
|
}}>
|
|
38
43
|
<AddIcon color={"inherit"}/>
|
|
@@ -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
|
|
|
@@ -80,6 +91,8 @@ export function CollectionDetailsForm({
|
|
|
80
91
|
}
|
|
81
92
|
}, [errors.id]);
|
|
82
93
|
|
|
94
|
+
const DatabaseField = collectionEditor.components?.DatabaseField ?? DefaultDatabaseField;
|
|
95
|
+
|
|
83
96
|
const collectionIcon = <IconForView collectionOrView={values}/>;
|
|
84
97
|
|
|
85
98
|
const groupOptions = groups?.filter((group) => !reservedGroups?.includes(group));
|
|
@@ -112,11 +125,15 @@ export function CollectionDetailsForm({
|
|
|
112
125
|
|
|
113
126
|
<div>
|
|
114
127
|
<div
|
|
115
|
-
className="flex flex-row py-2 pt-3 items-center">
|
|
128
|
+
className="flex flex-row gap-2 py-2 pt-3 items-center">
|
|
116
129
|
<Typography variant={!isNewCollection ? "h5" : "h4"} className={"flex-grow"}>
|
|
117
130
|
{isNewCollection ? "New collection" : `${values?.name} collection`}
|
|
118
131
|
</Typography>
|
|
119
|
-
<
|
|
132
|
+
<DatabaseField databaseId={values.databaseId}
|
|
133
|
+
onDatabaseIdUpdate={updateDatabaseId}/>
|
|
134
|
+
|
|
135
|
+
<Tooltip title={"Change icon"}
|
|
136
|
+
asChild={true}>
|
|
120
137
|
<IconButton
|
|
121
138
|
shape={"square"}
|
|
122
139
|
onClick={() => setIconDialogOpen(true)}>
|
|
@@ -139,14 +156,15 @@ export function CollectionDetailsForm({
|
|
|
139
156
|
value={values.name ?? ""}
|
|
140
157
|
onChange={(e: any) => updateName(e.target.value)}
|
|
141
158
|
label={"Name"}
|
|
159
|
+
autoFocus={true}
|
|
142
160
|
required
|
|
143
161
|
error={showErrors && Boolean(errors.name)}/>
|
|
144
162
|
<FieldCaption error={touched.name && Boolean(errors.name)}>
|
|
145
|
-
{touched.name && Boolean(errors.name) ? errors.name : "Name of
|
|
163
|
+
{touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
|
|
146
164
|
</FieldCaption>
|
|
147
165
|
</div>
|
|
148
166
|
|
|
149
|
-
<div className={
|
|
167
|
+
<div className={cls("col-span-12 ", isSubcollection ? "" : "sm:col-span-8")}>
|
|
150
168
|
<Field name={"path"}
|
|
151
169
|
as={DebouncedTextField}
|
|
152
170
|
label={"Path"}
|
|
@@ -190,16 +208,23 @@ export function CollectionDetailsForm({
|
|
|
190
208
|
})}
|
|
191
209
|
</Autocomplete>
|
|
192
210
|
<FieldCaption>
|
|
193
|
-
{showErrors && Boolean(errors.group) ? errors.group : "Group
|
|
211
|
+
{showErrors && Boolean(errors.group) ? errors.group : "Group in the home page"}
|
|
194
212
|
</FieldCaption>
|
|
213
|
+
|
|
214
|
+
|
|
195
215
|
</div>}
|
|
196
216
|
|
|
197
|
-
<
|
|
217
|
+
<LayoutModeSwitch
|
|
218
|
+
className={"col-span-12"}
|
|
219
|
+
value={values.openEntityMode ?? "side_panel"}
|
|
220
|
+
onChange={(value) => setFieldValue("openEntityMode", value)}/>
|
|
221
|
+
|
|
222
|
+
<div className={"col-span-12 mt-8"}>
|
|
198
223
|
<ExpandablePanel
|
|
199
224
|
expanded={advancedPanelExpanded}
|
|
200
225
|
onExpandedChange={setAdvancedPanelExpanded}
|
|
201
226
|
title={
|
|
202
|
-
<div className="flex flex-row text-
|
|
227
|
+
<div className="flex flex-row text-surface-500">
|
|
203
228
|
<SettingsIcon/>
|
|
204
229
|
<Typography variant={"subtitle2"}
|
|
205
230
|
className="ml-2">
|
|
@@ -216,7 +241,7 @@ export function CollectionDetailsForm({
|
|
|
216
241
|
label={"Collection id"}
|
|
217
242
|
error={showErrors && Boolean(errors.id)}/>
|
|
218
243
|
<FieldCaption error={touched.id && Boolean(errors.id)}>
|
|
219
|
-
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection"}
|
|
244
|
+
{touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
|
|
220
245
|
</FieldCaption>
|
|
221
246
|
</div>
|
|
222
247
|
|
|
@@ -235,6 +260,35 @@ export function CollectionDetailsForm({
|
|
|
235
260
|
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
|
|
236
261
|
</FieldCaption>
|
|
237
262
|
</div>
|
|
263
|
+
<div className={"col-span-12"}>
|
|
264
|
+
<TextField
|
|
265
|
+
error={showErrors && Boolean(errors.sideDialogWidth)}
|
|
266
|
+
name={"sideDialogWidth"}
|
|
267
|
+
type={"number"}
|
|
268
|
+
aria-describedby={"sideDialogWidth-helper"}
|
|
269
|
+
onChange={(e) => {
|
|
270
|
+
setFieldTouched("sideDialogWidth", true);
|
|
271
|
+
const value = e.target.value;
|
|
272
|
+
if (!value) {
|
|
273
|
+
setFieldValue("sideDialogWidth", null);
|
|
274
|
+
} else if (!isNaN(Number(value))) {
|
|
275
|
+
setFieldValue("sideDialogWidth", Number(value));
|
|
276
|
+
}
|
|
277
|
+
}}
|
|
278
|
+
endAdornment={<IconButton
|
|
279
|
+
size={"small"}
|
|
280
|
+
onClick={() => {
|
|
281
|
+
setFieldValue("sideDialogWidth", null);
|
|
282
|
+
}}
|
|
283
|
+
disabled={!values.sideDialogWidth}>
|
|
284
|
+
<CloseIcon size={"small"}/>
|
|
285
|
+
</IconButton>}
|
|
286
|
+
value={values.sideDialogWidth ?? ""}
|
|
287
|
+
label={"Side dialog width"}/>
|
|
288
|
+
<FieldCaption error={showErrors && Boolean(errors.singularName)}>
|
|
289
|
+
{showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
|
|
290
|
+
</FieldCaption>
|
|
291
|
+
</div>
|
|
238
292
|
<div className={"col-span-12"}>
|
|
239
293
|
<TextField
|
|
240
294
|
error={showErrors && Boolean(errors.description)}
|
|
@@ -254,6 +308,8 @@ export function CollectionDetailsForm({
|
|
|
254
308
|
<div className={"col-span-12"}>
|
|
255
309
|
<Select
|
|
256
310
|
name="defaultSize"
|
|
311
|
+
size={"large"}
|
|
312
|
+
fullWidth={true}
|
|
257
313
|
label="Default row size"
|
|
258
314
|
position={"item-aligned"}
|
|
259
315
|
onChange={handleChange}
|
|
@@ -272,8 +328,10 @@ export function CollectionDetailsForm({
|
|
|
272
328
|
<div className={"col-span-12"}>
|
|
273
329
|
<Select
|
|
274
330
|
name="customId"
|
|
275
|
-
label="
|
|
331
|
+
label="Document IDs generation"
|
|
276
332
|
position={"item-aligned"}
|
|
333
|
+
size={"large"}
|
|
334
|
+
fullWidth={true}
|
|
277
335
|
disabled={customIdValue === "code_defined"}
|
|
278
336
|
onValueChange={(v) => {
|
|
279
337
|
if (v === "code_defined")
|
|
@@ -334,9 +392,13 @@ export function CollectionDetailsForm({
|
|
|
334
392
|
for large collections, as it may incur in performance and cost issues.
|
|
335
393
|
</FieldCaption>
|
|
336
394
|
</div>
|
|
395
|
+
|
|
396
|
+
|
|
337
397
|
</div>
|
|
338
398
|
</ExpandablePanel>
|
|
339
399
|
|
|
400
|
+
{children}
|
|
401
|
+
|
|
340
402
|
</div>
|
|
341
403
|
|
|
342
404
|
</div>
|
|
@@ -363,3 +425,20 @@ export function CollectionDetailsForm({
|
|
|
363
425
|
</div>
|
|
364
426
|
);
|
|
365
427
|
}
|
|
428
|
+
|
|
429
|
+
function DefaultDatabaseField({
|
|
430
|
+
databaseId,
|
|
431
|
+
onDatabaseIdUpdate
|
|
432
|
+
}: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
|
|
433
|
+
|
|
434
|
+
return <Tooltip title={"Database ID"}
|
|
435
|
+
side={"top"}
|
|
436
|
+
align={"start"}>
|
|
437
|
+
<TextField size={"small"}
|
|
438
|
+
invisible={true}
|
|
439
|
+
inputClassName={"text-end"}
|
|
440
|
+
value={databaseId ?? ""}
|
|
441
|
+
onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
|
|
442
|
+
placeholder={"(default)"}></TextField>
|
|
443
|
+
</Tooltip>
|
|
444
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
3
|
import {
|
|
4
4
|
CircularProgressCenter,
|
|
5
|
+
ConfirmationDialog,
|
|
6
|
+
Entity,
|
|
5
7
|
EntityCollection,
|
|
6
8
|
ErrorView,
|
|
7
9
|
isPropertyBuilder,
|
|
@@ -25,17 +27,19 @@ import {
|
|
|
25
27
|
import {
|
|
26
28
|
ArrowBackIcon,
|
|
27
29
|
Button,
|
|
28
|
-
|
|
30
|
+
CheckIcon,
|
|
31
|
+
cls,
|
|
29
32
|
coolIconKeys,
|
|
30
33
|
defaultBorderMixin,
|
|
31
34
|
Dialog,
|
|
32
35
|
DialogActions,
|
|
33
36
|
DialogContent,
|
|
34
|
-
|
|
37
|
+
DialogTitle,
|
|
35
38
|
IconButton,
|
|
36
39
|
LoadingButton,
|
|
37
40
|
Tab,
|
|
38
|
-
Tabs
|
|
41
|
+
Tabs,
|
|
42
|
+
Typography
|
|
39
43
|
} from "@firecms/ui";
|
|
40
44
|
import { YupSchema } from "./CollectionYupValidation";
|
|
41
45
|
import { CollectionDetailsForm } from "./CollectionDetailsForm";
|
|
@@ -76,9 +80,10 @@ export interface CollectionEditorDialogProps {
|
|
|
76
80
|
icon: React.ReactNode
|
|
77
81
|
};
|
|
78
82
|
pathSuggestions?: (path?: string) => Promise<string[]>;
|
|
79
|
-
getUser
|
|
83
|
+
getUser?: (uid: string) => User | null;
|
|
80
84
|
getData?: (path: string, parentPaths: string[]) => Promise<object[]>;
|
|
81
85
|
parentCollection?: PersistedCollection;
|
|
86
|
+
existingEntities?: Entity<any>[];
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
@@ -88,13 +93,13 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
88
93
|
const [formDirty, setFormDirty] = React.useState<boolean>(false);
|
|
89
94
|
const [unsavedChangesDialogOpen, setUnsavedChangesDialogOpen] = React.useState<boolean>(false);
|
|
90
95
|
|
|
91
|
-
const handleCancel =
|
|
96
|
+
const handleCancel = () => {
|
|
92
97
|
if (!formDirty) {
|
|
93
98
|
props.handleClose(undefined);
|
|
94
99
|
} else {
|
|
95
100
|
setUnsavedChangesDialogOpen(true);
|
|
96
101
|
}
|
|
97
|
-
}
|
|
102
|
+
};
|
|
98
103
|
|
|
99
104
|
useEffect(() => {
|
|
100
105
|
if (!open) {
|
|
@@ -112,6 +117,7 @@ export function CollectionEditorDialog(props: CollectionEditorDialogProps) {
|
|
|
112
117
|
maxWidth={"7xl"}
|
|
113
118
|
onOpenChange={(open) => !open ? handleCancel() : undefined}
|
|
114
119
|
>
|
|
120
|
+
<DialogTitle hidden>Collection editor</DialogTitle>
|
|
115
121
|
{open && <CollectionEditor {...props}
|
|
116
122
|
handleCancel={handleCancel}
|
|
117
123
|
setFormDirty={setFormDirty}/>}
|
|
@@ -170,7 +176,8 @@ export function CollectionEditor(props: CollectionEditorDialogProps & {
|
|
|
170
176
|
} catch (e) {
|
|
171
177
|
console.error(e);
|
|
172
178
|
}
|
|
173
|
-
}, [
|
|
179
|
+
}, [props.editedCollectionId, props.parentCollectionIds, navigation.initialised, navigation.getCollectionFromPaths]);
|
|
180
|
+
|
|
174
181
|
if (!topLevelNavigation) {
|
|
175
182
|
throw Error("Internal: Navigation not ready in collection editor");
|
|
176
183
|
}
|
|
@@ -243,7 +250,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
243
250
|
setCollection,
|
|
244
251
|
initialValues,
|
|
245
252
|
propertyConfigs,
|
|
246
|
-
groups
|
|
253
|
+
groups,
|
|
254
|
+
existingEntities
|
|
247
255
|
}: CollectionEditorDialogProps & {
|
|
248
256
|
handleCancel: () => void,
|
|
249
257
|
setFormDirty: (dirty: boolean) => void,
|
|
@@ -272,6 +280,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
272
280
|
|
|
273
281
|
const saveCollection = (updatedCollection: PersistedCollection<M>): Promise<boolean> => {
|
|
274
282
|
const id = updatedCollection.id || updatedCollection.path;
|
|
283
|
+
|
|
275
284
|
return configController.saveCollection({
|
|
276
285
|
id,
|
|
277
286
|
collectionData: updatedCollection,
|
|
@@ -293,7 +302,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
293
302
|
});
|
|
294
303
|
};
|
|
295
304
|
|
|
296
|
-
const setNextMode =
|
|
305
|
+
const setNextMode = () => {
|
|
297
306
|
if (currentView === "details") {
|
|
298
307
|
if (importConfig.inUse) {
|
|
299
308
|
setCurrentView("import_data_saving");
|
|
@@ -314,14 +323,14 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
314
323
|
setCurrentView("details");
|
|
315
324
|
}
|
|
316
325
|
|
|
317
|
-
}
|
|
326
|
+
};
|
|
318
327
|
|
|
319
|
-
const doCollectionInference =
|
|
328
|
+
const doCollectionInference = (collection: PersistedCollection<any>) => {
|
|
320
329
|
if (!collectionInference) return undefined;
|
|
321
|
-
return collectionInference?.(collection.path, collection.collectionGroup ?? false,
|
|
322
|
-
}
|
|
330
|
+
return collectionInference?.(collection.path, collection.collectionGroup ?? false, parentPaths ?? []);
|
|
331
|
+
};
|
|
323
332
|
|
|
324
|
-
const inferCollectionFromData =
|
|
333
|
+
const inferCollectionFromData = async (newCollection: PersistedCollection<M>) => {
|
|
325
334
|
|
|
326
335
|
try {
|
|
327
336
|
if (!doCollectionInference) {
|
|
@@ -365,15 +374,15 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
365
374
|
});
|
|
366
375
|
return newCollection;
|
|
367
376
|
}
|
|
368
|
-
}
|
|
377
|
+
};
|
|
369
378
|
|
|
370
379
|
const onSubmit = (newCollectionState: PersistedCollection<M>, formexController: FormexController<PersistedCollection<M>>) => {
|
|
371
|
-
console.
|
|
380
|
+
console.debug("Submitting collection", newCollectionState);
|
|
372
381
|
try {
|
|
373
382
|
|
|
374
383
|
if (!isNewCollection) {
|
|
375
384
|
saveCollection(newCollectionState).then(() => {
|
|
376
|
-
formexController.resetForm(
|
|
385
|
+
formexController.resetForm();
|
|
377
386
|
handleClose(newCollectionState);
|
|
378
387
|
});
|
|
379
388
|
return;
|
|
@@ -462,7 +471,8 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
462
471
|
const formController = useCreateFormex<PersistedCollection<M>>({
|
|
463
472
|
initialValues,
|
|
464
473
|
onSubmit,
|
|
465
|
-
validation
|
|
474
|
+
validation,
|
|
475
|
+
debugId: "COLLECTION_EDITOR"
|
|
466
476
|
});
|
|
467
477
|
|
|
468
478
|
const {
|
|
@@ -479,26 +489,37 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
479
489
|
const pathError = validatePath(path, isNewCollection, existingPaths, values.id);
|
|
480
490
|
|
|
481
491
|
const parentPaths = !pathError && parentCollectionIds ? navigation.convertIdsToPaths(parentCollectionIds) : undefined;
|
|
482
|
-
const resolvedPath = !pathError ? navigation.
|
|
483
|
-
const getDataWithPath = resolvedPath && getData ? () =>
|
|
492
|
+
const resolvedPath = !pathError ? navigation.resolveIdsFrom(updatedFullPath) : undefined;
|
|
493
|
+
const getDataWithPath = resolvedPath && getData ? async () => {
|
|
494
|
+
const data = await getData(resolvedPath, parentPaths ?? []);
|
|
495
|
+
if (existingEntities) {
|
|
496
|
+
const existingData = existingEntities.map(e => e.values);
|
|
497
|
+
data.push(...existingData);
|
|
498
|
+
}
|
|
499
|
+
return data;
|
|
500
|
+
} : undefined;
|
|
484
501
|
|
|
485
502
|
useEffect(() => {
|
|
486
503
|
setFormDirty(dirty);
|
|
487
504
|
}, [dirty]);
|
|
488
505
|
|
|
489
|
-
function onImportDataSet(data: object[]) {
|
|
506
|
+
function onImportDataSet(data: object[], propertiesOrder?: string[]) {
|
|
490
507
|
importConfig.setInUse(true);
|
|
491
508
|
buildEntityPropertiesFromData(data, getInferenceType)
|
|
492
509
|
.then((properties) => {
|
|
493
510
|
const res = cleanPropertiesFromImport(properties);
|
|
494
511
|
|
|
495
|
-
setFieldValue("properties", res.properties);
|
|
496
|
-
setFieldValue("propertiesOrder", Object.keys(res.properties));
|
|
497
|
-
|
|
498
512
|
importConfig.setIdColumn(res.idColumn);
|
|
499
513
|
importConfig.setImportData(data);
|
|
500
514
|
importConfig.setHeadersMapping(res.headersMapping);
|
|
515
|
+
const filteredHeadingsOrder = ((propertiesOrder ?? [])
|
|
516
|
+
.filter((key) => res.headersMapping[key]) as string[]) ?? Object.keys(res.properties);
|
|
517
|
+
importConfig.setHeadingsOrder(filteredHeadingsOrder);
|
|
501
518
|
importConfig.setOriginProperties(res.properties);
|
|
519
|
+
|
|
520
|
+
const mappedHeadings = (propertiesOrder ?? []).map((key) => res.headersMapping[key]).filter(Boolean) as string[] ?? Object.keys(res.properties);
|
|
521
|
+
setFieldValue("properties", res.properties);
|
|
522
|
+
setFieldValue("propertiesOrder", mappedHeadings);
|
|
502
523
|
});
|
|
503
524
|
}
|
|
504
525
|
|
|
@@ -514,14 +535,30 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
514
535
|
};
|
|
515
536
|
|
|
516
537
|
const editable = collection?.editable === undefined || collection?.editable === true;
|
|
538
|
+
// @ts-ignore
|
|
539
|
+
const isMergedCollection = collection?.merged ?? false;
|
|
517
540
|
const collectionEditable = editable || isNewCollection;
|
|
518
541
|
|
|
542
|
+
const [deleteRequested, setDeleteRequested] = useState(false);
|
|
543
|
+
|
|
544
|
+
const deleteCollection = () => {
|
|
545
|
+
if (!collection) return;
|
|
546
|
+
configController?.deleteCollection({ id: collection.id }).then(() => {
|
|
547
|
+
setDeleteRequested(false);
|
|
548
|
+
handleCancel();
|
|
549
|
+
snackbarController.open({
|
|
550
|
+
message: "Collection deleted",
|
|
551
|
+
type: "success"
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
};
|
|
555
|
+
|
|
519
556
|
return <DialogContent fullHeight={true}>
|
|
520
557
|
<Formex value={formController}>
|
|
521
558
|
|
|
522
559
|
<>
|
|
523
560
|
{!isNewCollection && <Tabs value={currentView}
|
|
524
|
-
|
|
561
|
+
innerClassName={cls(defaultBorderMixin, "px-4 h-14 w-full justify-end bg-surface-50 dark:bg-surface-950 border-b")}
|
|
525
562
|
onValueChange={(v) => setCurrentView(v as EditorView)}>
|
|
526
563
|
<Tab value={"details"}>
|
|
527
564
|
Details
|
|
@@ -536,7 +573,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
536
573
|
|
|
537
574
|
<form noValidate
|
|
538
575
|
onSubmit={formController.handleSubmit}
|
|
539
|
-
className={
|
|
576
|
+
className={cls(
|
|
540
577
|
isNewCollection ? "h-full" : "h-[calc(100%-48px)]",
|
|
541
578
|
"flex-grow flex flex-col relative")}>
|
|
542
579
|
|
|
@@ -551,9 +588,10 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
551
588
|
{currentView === "welcome" &&
|
|
552
589
|
<CollectionEditorWelcomeView
|
|
553
590
|
path={path}
|
|
554
|
-
onContinue={(importData) => {
|
|
591
|
+
onContinue={(importData, propertiesOrder) => {
|
|
592
|
+
// console.log("Import data", importData, propertiesOrder)
|
|
555
593
|
if (importData) {
|
|
556
|
-
onImportDataSet(importData);
|
|
594
|
+
onImportDataSet(importData, propertiesOrder);
|
|
557
595
|
setCurrentView("import_data_mapping");
|
|
558
596
|
} else {
|
|
559
597
|
setCurrentView("details");
|
|
@@ -594,7 +632,18 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
594
632
|
groups={groups}
|
|
595
633
|
parentCollectionIds={parentCollectionIds}
|
|
596
634
|
parentCollection={parentCollection}
|
|
597
|
-
isNewCollection={isNewCollection}
|
|
635
|
+
isNewCollection={isNewCollection}>
|
|
636
|
+
{!isNewCollection && isMergedCollection && <div className={"flex flex-col gap-4 mt-8"}>
|
|
637
|
+
<Typography variant={"body2"} color={"secondary"}>This collection is defined in code.
|
|
638
|
+
The changes done in this editor will override the properties defined in code.
|
|
639
|
+
You can delete the overridden values to revert to the state defined in code.
|
|
640
|
+
</Typography>
|
|
641
|
+
<Button variant={"neutral"}
|
|
642
|
+
onClick={() => {
|
|
643
|
+
setDeleteRequested(true);
|
|
644
|
+
}}>Reset to code</Button>
|
|
645
|
+
</div>}
|
|
646
|
+
</CollectionDetailsForm>}
|
|
598
647
|
|
|
599
648
|
{currentView === "subcollections" && collection &&
|
|
600
649
|
<SubcollectionsEditTab
|
|
@@ -706,7 +755,7 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
706
755
|
loading={isSubmitting}
|
|
707
756
|
disabled={isSubmitting || (currentView === "details" && !validValues)}
|
|
708
757
|
startIcon={currentView === "properties"
|
|
709
|
-
? <
|
|
758
|
+
? <CheckIcon/>
|
|
710
759
|
: undefined}
|
|
711
760
|
>
|
|
712
761
|
{currentView === "details" && "Next"}
|
|
@@ -728,12 +777,24 @@ function CollectionEditorInternal<M extends Record<string, any>>({
|
|
|
728
777
|
|
|
729
778
|
</Formex>
|
|
730
779
|
|
|
780
|
+
<ConfirmationDialog
|
|
781
|
+
open={deleteRequested}
|
|
782
|
+
onAccept={deleteCollection}
|
|
783
|
+
onCancel={() => setDeleteRequested(false)}
|
|
784
|
+
title={<>Delete the stored config?</>}
|
|
785
|
+
body={<> This will <b>not
|
|
786
|
+
delete any data</b>, only
|
|
787
|
+
the stored config, and reset to the code state.</>}/>
|
|
788
|
+
|
|
731
789
|
</DialogContent>
|
|
732
790
|
|
|
733
791
|
}
|
|
734
792
|
|
|
735
793
|
function applyPropertyConfigs<M extends Record<string, any> = any>(collection: PersistedCollection<M>, propertyConfigs: Record<string, PropertyConfig<any>>): PersistedCollection<M> {
|
|
736
|
-
const {
|
|
794
|
+
const {
|
|
795
|
+
properties,
|
|
796
|
+
...rest
|
|
797
|
+
} = collection;
|
|
737
798
|
const propertiesResult: PropertiesOrBuilders<any> = {};
|
|
738
799
|
if (properties) {
|
|
739
800
|
Object.keys(properties).forEach((key) => {
|
|
@@ -741,7 +802,10 @@ function applyPropertyConfigs<M extends Record<string, any> = any>(collection: P
|
|
|
741
802
|
});
|
|
742
803
|
}
|
|
743
804
|
|
|
744
|
-
return {
|
|
805
|
+
return {
|
|
806
|
+
...rest,
|
|
807
|
+
properties: propertiesResult
|
|
808
|
+
};
|
|
745
809
|
}
|
|
746
810
|
|
|
747
811
|
function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Record<string, PropertyConfig<any>>) {
|
|
@@ -761,7 +825,10 @@ function applyPropertiesConfig(property: PropertyOrBuilder, propertyConfigs: Rec
|
|
|
761
825
|
Object.keys(internalProperty.properties).forEach((key) => {
|
|
762
826
|
properties[key] = applyPropertiesConfig(((internalProperty as MapProperty).properties as Properties)[key] as Property, propertyConfigs);
|
|
763
827
|
});
|
|
764
|
-
internalProperty = {
|
|
828
|
+
internalProperty = {
|
|
829
|
+
...internalProperty,
|
|
830
|
+
properties
|
|
831
|
+
};
|
|
765
832
|
}
|
|
766
833
|
|
|
767
834
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { EntityCollection, unslugify, } from "@firecms/core";
|
|
3
|
-
import { Button, Card, Chip, CircularProgress,
|
|
3
|
+
import { Button, Card, Chip, CircularProgress, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
|
|
4
4
|
|
|
5
5
|
import { productsCollectionTemplate } from "./templates/products_template";
|
|
6
6
|
import { blogCollectionTemplate } from "./templates/blog_template";
|
|
@@ -19,7 +19,7 @@ export function CollectionEditorWelcomeView({
|
|
|
19
19
|
path: string;
|
|
20
20
|
pathSuggestions?: (path: string) => Promise<string[]>;
|
|
21
21
|
parentCollection?: EntityCollection;
|
|
22
|
-
onContinue: (importData?: object[]) => void;
|
|
22
|
+
onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
|
|
23
23
|
existingCollectionPaths?: string[];
|
|
24
24
|
}) {
|
|
25
25
|
|
|
@@ -154,7 +154,7 @@ export function CollectionEditorWelcomeView({
|
|
|
154
154
|
● Create a collection from a file (csv, json, xls, xslx...)
|
|
155
155
|
</Typography>
|
|
156
156
|
|
|
157
|
-
<ImportFileUpload onDataAdded={(data) => onContinue(data)}/>
|
|
157
|
+
<ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
|
|
158
158
|
|
|
159
159
|
</div>}
|
|
160
160
|
|
|
@@ -185,14 +185,15 @@ export function TemplateButton({
|
|
|
185
185
|
}) {
|
|
186
186
|
|
|
187
187
|
return (
|
|
188
|
-
<Tooltip title={subtitle}
|
|
188
|
+
<Tooltip title={subtitle}
|
|
189
|
+
asChild={true}>
|
|
189
190
|
<Card
|
|
190
191
|
onClick={onClick}
|
|
191
|
-
className={
|
|
192
|
+
className={cls(
|
|
192
193
|
"my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
|
|
193
|
-
"text-
|
|
194
|
+
"text-surface-700 dark:text-surface-accent-300",
|
|
194
195
|
"hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
|
|
195
|
-
"border-
|
|
196
|
+
"border-surface-400 dark:border-surface-600 "
|
|
196
197
|
)}
|
|
197
198
|
>
|
|
198
199
|
{icon}
|