@firecms/collection_editor 3.0.0-alpha.17 → 3.0.0-alpha.19
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/package.json +4 -3
- package/src/ConfigControllerProvider.tsx +177 -0
- package/src/components/EditorCollectionAction.tsx +95 -0
- package/src/components/HomePageEditorCollectionAction.tsx +81 -0
- package/src/components/NewCollectionCard.tsx +45 -0
- package/src/components/RootCollectionSuggestions.tsx +53 -0
- package/src/components/collection_editor/CollectionDetailsForm.tsx +312 -0
- package/src/components/collection_editor/CollectionEditorDialog.tsx +640 -0
- package/src/components/collection_editor/CollectionEditorWelcomeView.tsx +212 -0
- package/src/components/collection_editor/CollectionPropertiesEditorForm.tsx +450 -0
- package/src/components/collection_editor/CollectionYupValidation.tsx +6 -0
- package/src/components/collection_editor/EntityCustomViewsSelectDialog.tsx +29 -0
- package/src/components/collection_editor/EnumForm.tsx +354 -0
- package/src/components/collection_editor/PropertyEditView.tsx +535 -0
- package/src/components/collection_editor/PropertyFieldPreview.tsx +205 -0
- package/src/components/collection_editor/PropertySelectItem.tsx +31 -0
- package/src/components/collection_editor/PropertyTree.tsx +228 -0
- package/src/components/collection_editor/SelectIcons.tsx +72 -0
- package/src/components/collection_editor/SubcollectionsEditTab.tsx +239 -0
- package/src/components/collection_editor/UnsavedChangesDialog.tsx +47 -0
- package/src/components/collection_editor/import/CollectionEditorImportDataPreview.tsx +37 -0
- package/src/components/collection_editor/import/CollectionEditorImportMapping.tsx +236 -0
- package/src/components/collection_editor/import/clean_import_data.ts +53 -0
- package/src/components/collection_editor/properties/BlockPropertyField.tsx +131 -0
- package/src/components/collection_editor/properties/BooleanPropertyField.tsx +36 -0
- package/src/components/collection_editor/properties/CommonPropertyFields.tsx +112 -0
- package/src/components/collection_editor/properties/DateTimePropertyField.tsx +86 -0
- package/src/components/collection_editor/properties/EnumPropertyField.tsx +116 -0
- package/src/components/collection_editor/properties/FieldHelperView.tsx +13 -0
- package/src/components/collection_editor/properties/KeyValuePropertyField.tsx +20 -0
- package/src/components/collection_editor/properties/MapPropertyField.tsx +154 -0
- package/src/components/collection_editor/properties/NumberPropertyField.tsx +38 -0
- package/src/components/collection_editor/properties/ReferencePropertyField.tsx +184 -0
- package/src/components/collection_editor/properties/RepeatPropertyField.tsx +115 -0
- package/src/components/collection_editor/properties/StoragePropertyField.tsx +194 -0
- package/src/components/collection_editor/properties/StringPropertyField.tsx +85 -0
- package/src/components/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +36 -0
- package/src/components/collection_editor/properties/validation/ArrayPropertyValidation.tsx +50 -0
- package/src/components/collection_editor/properties/validation/GeneralPropertyValidation.tsx +49 -0
- package/src/components/collection_editor/properties/validation/NumberPropertyValidation.tsx +99 -0
- package/src/components/collection_editor/properties/validation/StringPropertyValidation.tsx +131 -0
- package/src/components/collection_editor/properties/validation/ValidationPanel.tsx +28 -0
- package/src/components/collection_editor/templates/blog_template.ts +115 -0
- package/src/components/collection_editor/templates/products_template.ts +89 -0
- package/src/components/collection_editor/templates/users_template.ts +34 -0
- package/src/components/collection_editor/util.ts +21 -0
- package/src/components/collection_editor/utils/supported_fields.tsx +28 -0
- package/src/components/collection_editor/utils/update_property_for_widget.ts +258 -0
- package/src/components/collection_editor/utils/useTraceUpdate.tsx +23 -0
- package/src/index.ts +31 -0
- package/src/types/collection_editor_controller.tsx +31 -0
- package/src/types/collection_inference.ts +3 -0
- package/src/types/config_controller.tsx +30 -0
- package/src/types/config_permissions.ts +20 -0
- package/src/types/persisted_collection.ts +7 -0
- package/src/useCollectionEditorController.tsx +9 -0
- package/src/useCollectionEditorPlugin.tsx +103 -0
- package/src/useCollectionsConfigController.tsx +9 -0
- package/src/utils/arrays.ts +3 -0
- package/src/utils/entities.ts +38 -0
- package/src/utils/icons.ts +17 -0
- package/src/utils/join_collections.ts +144 -0
- package/src/utils/synonyms.ts +1952 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
|
+
import { AddIcon, ArrayProperty, Button, FieldConfig, Paper, Property, Typography } from "@firecms/core";
|
|
3
|
+
import { getIn, useFormikContext } from "formik";
|
|
4
|
+
import { PropertyFormDialog } from "../PropertyEditView";
|
|
5
|
+
import { getFullId, idToPropertiesPath, namespaceToPropertiesOrderPath } from "../util";
|
|
6
|
+
import { PropertyTree } from "../PropertyTree";
|
|
7
|
+
|
|
8
|
+
export function BlockPropertyField({ disabled, getData, allowDataInference, customFields }: {
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
getData?: () => Promise<object[]>;
|
|
11
|
+
allowDataInference: boolean;
|
|
12
|
+
customFields: Record<string, FieldConfig>
|
|
13
|
+
}) {
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
values,
|
|
17
|
+
setFieldValue
|
|
18
|
+
} = useFormikContext<ArrayProperty>();
|
|
19
|
+
|
|
20
|
+
const [propertyDialogOpen, setPropertyDialogOpen] = useState<boolean>(false);
|
|
21
|
+
const [selectedPropertyKey, setSelectedPropertyKey] = useState<string | undefined>();
|
|
22
|
+
const [selectedPropertyNamespace, setSelectedPropertyNamespace] = useState<string | undefined>();
|
|
23
|
+
|
|
24
|
+
const onPropertyCreated = useCallback(({
|
|
25
|
+
id,
|
|
26
|
+
property
|
|
27
|
+
}: { id?: string, property: Property }) => {
|
|
28
|
+
if (!id)
|
|
29
|
+
throw Error();
|
|
30
|
+
setFieldValue("oneOf.properties", {
|
|
31
|
+
...(values.oneOf?.properties ?? {}),
|
|
32
|
+
[id]: property
|
|
33
|
+
}, false);
|
|
34
|
+
setFieldValue("oneOf.propertiesOrder", [...(values.oneOf?.propertiesOrder ?? Object.keys(values.oneOf?.properties ?? {})), id], false);
|
|
35
|
+
setPropertyDialogOpen(false);
|
|
36
|
+
}, [values.oneOf?.properties, values.oneOf?.propertiesOrder]);
|
|
37
|
+
|
|
38
|
+
const selectedPropertyFullId = selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined;
|
|
39
|
+
const selectedProperty = selectedPropertyFullId ? getIn(values.oneOf?.properties, selectedPropertyFullId.replaceAll(".", ".properties.")) : undefined;
|
|
40
|
+
|
|
41
|
+
const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
|
|
42
|
+
const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
|
|
43
|
+
if (!fullId)
|
|
44
|
+
throw Error("collection editor miss config");
|
|
45
|
+
|
|
46
|
+
setFieldValue(`oneOf.${idToPropertiesPath(fullId)}`, undefined, false);
|
|
47
|
+
const propertiesOrderPath = `oneOf.${namespaceToPropertiesOrderPath(namespace)}`;
|
|
48
|
+
const currentPropertiesOrder: string[] = getIn(values, propertiesOrderPath);
|
|
49
|
+
setFieldValue(propertiesOrderPath, currentPropertiesOrder.filter((p) => p !== propertyKey), false);
|
|
50
|
+
|
|
51
|
+
setPropertyDialogOpen(false);
|
|
52
|
+
setSelectedPropertyKey(undefined);
|
|
53
|
+
setSelectedPropertyNamespace(undefined);
|
|
54
|
+
}, [setFieldValue, values]);
|
|
55
|
+
|
|
56
|
+
const addChildButton = <Button
|
|
57
|
+
autoFocus
|
|
58
|
+
color="primary"
|
|
59
|
+
|
|
60
|
+
onClick={() => setPropertyDialogOpen(true)}
|
|
61
|
+
startIcon={<AddIcon/>}
|
|
62
|
+
>
|
|
63
|
+
Add property to {values.name ?? "this block"}
|
|
64
|
+
</Button>;
|
|
65
|
+
|
|
66
|
+
const onPropertyMove = useCallback((propertiesOrder: string[], namespace?: string) => {
|
|
67
|
+
setFieldValue(`oneOf.${namespaceToPropertiesOrderPath(namespace)}`, propertiesOrder, false);
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<>
|
|
72
|
+
<div className={"col-span-12"}>
|
|
73
|
+
<div className={"flex justify-between items-end mt-8 mb-4"}>
|
|
74
|
+
<Typography variant={"subtitle2"}>Properties in this
|
|
75
|
+
block</Typography>
|
|
76
|
+
{addChildButton}
|
|
77
|
+
</div>
|
|
78
|
+
<Paper className="p-2 pl-8">
|
|
79
|
+
|
|
80
|
+
<PropertyTree
|
|
81
|
+
properties={values.oneOf?.properties ?? {}}
|
|
82
|
+
propertiesOrder={values.oneOf?.propertiesOrder}
|
|
83
|
+
errors={{}}
|
|
84
|
+
onPropertyClick={disabled
|
|
85
|
+
? undefined
|
|
86
|
+
: (propertyKey, namespace) => {
|
|
87
|
+
setSelectedPropertyKey(propertyKey);
|
|
88
|
+
setSelectedPropertyNamespace(namespace);
|
|
89
|
+
setPropertyDialogOpen(true);
|
|
90
|
+
}}
|
|
91
|
+
onPropertyMove={disabled
|
|
92
|
+
? undefined
|
|
93
|
+
: onPropertyMove}/>
|
|
94
|
+
|
|
95
|
+
{!disabled && !values.oneOf?.propertiesOrder?.length &&
|
|
96
|
+
<div className="h-full flex items-center justify-center p-4">
|
|
97
|
+
Add the first property to this block
|
|
98
|
+
</div>}
|
|
99
|
+
|
|
100
|
+
</Paper>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
{!disabled && <PropertyFormDialog
|
|
104
|
+
inArray={false}
|
|
105
|
+
forceShowErrors={false}
|
|
106
|
+
open={propertyDialogOpen}
|
|
107
|
+
getData={getData}
|
|
108
|
+
allowDataInference={allowDataInference}
|
|
109
|
+
onCancel={() => {
|
|
110
|
+
setPropertyDialogOpen(false);
|
|
111
|
+
setSelectedPropertyKey(undefined);
|
|
112
|
+
setSelectedPropertyNamespace(undefined);
|
|
113
|
+
}}
|
|
114
|
+
onOkClicked={() => {
|
|
115
|
+
setPropertyDialogOpen(false);
|
|
116
|
+
setSelectedPropertyKey(undefined);
|
|
117
|
+
setSelectedPropertyNamespace(undefined);
|
|
118
|
+
}}
|
|
119
|
+
onDelete={deleteProperty}
|
|
120
|
+
propertyKey={selectedPropertyKey}
|
|
121
|
+
propertyNamespace={selectedPropertyNamespace}
|
|
122
|
+
property={selectedProperty}
|
|
123
|
+
existing={Boolean(selectedPropertyKey)}
|
|
124
|
+
autoUpdateId={!selectedPropertyKey}
|
|
125
|
+
autoOpenTypeSelect={!selectedPropertyKey}
|
|
126
|
+
onPropertyChanged={onPropertyCreated}
|
|
127
|
+
existingPropertyKeys={selectedPropertyKey ? undefined : values.oneOf?.propertiesOrder}
|
|
128
|
+
customFields={customFields}/>}
|
|
129
|
+
|
|
130
|
+
</>);
|
|
131
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { FastField, getIn, useFormikContext } from "formik";
|
|
3
|
+
|
|
4
|
+
import { GeneralPropertyValidation } from "./validation/GeneralPropertyValidation";
|
|
5
|
+
import { ValidationPanel } from "./validation/ValidationPanel";
|
|
6
|
+
import { SwitchControl } from "@firecms/core";
|
|
7
|
+
|
|
8
|
+
export function BooleanPropertyField({ disabled }: {
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
}) {
|
|
11
|
+
const { values } = useFormikContext();
|
|
12
|
+
const defaultValue = getIn(values, "defaultValue");
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<>
|
|
16
|
+
<div className={"col-span-12"}>
|
|
17
|
+
|
|
18
|
+
<ValidationPanel>
|
|
19
|
+
<GeneralPropertyValidation disabled={disabled}/>
|
|
20
|
+
</ValidationPanel>
|
|
21
|
+
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div className={"col-span-12"}>
|
|
25
|
+
|
|
26
|
+
<FastField type="checkbox"
|
|
27
|
+
name={"defaultValue"}
|
|
28
|
+
label={defaultValue === null || defaultValue === undefined ? "Default value not set" : ("Default value is " + defaultValue.toString())}
|
|
29
|
+
disabled={disabled}
|
|
30
|
+
allowIndeterminate={true}
|
|
31
|
+
component={SwitchControl}/>
|
|
32
|
+
|
|
33
|
+
</div>
|
|
34
|
+
</>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Field, getIn, useFormikContext } from "formik";
|
|
2
|
+
import { DebouncedTextField, TextField } from "@firecms/core";
|
|
3
|
+
import { PropertyWithId } from "../PropertyEditView";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { FieldHelperView } from "./FieldHelperView";
|
|
6
|
+
|
|
7
|
+
type CommonPropertyFieldsProps = {
|
|
8
|
+
showErrors: boolean,
|
|
9
|
+
disabledId: boolean,
|
|
10
|
+
existingPropertyKeys?: string[];
|
|
11
|
+
disabled: boolean;
|
|
12
|
+
isNewProperty: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const CommonPropertyFields = React.forwardRef<HTMLDivElement, CommonPropertyFieldsProps>(
|
|
16
|
+
function CommonPropertyFields({
|
|
17
|
+
showErrors,
|
|
18
|
+
disabledId,
|
|
19
|
+
existingPropertyKeys,
|
|
20
|
+
disabled,
|
|
21
|
+
isNewProperty
|
|
22
|
+
}, ref) {
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
errors
|
|
26
|
+
} = useFormikContext<PropertyWithId>();
|
|
27
|
+
|
|
28
|
+
const name = "name";
|
|
29
|
+
const nameError = showErrors && getIn(errors, name);
|
|
30
|
+
|
|
31
|
+
const id = "id";
|
|
32
|
+
const idError = showErrors && getIn(errors, id);
|
|
33
|
+
|
|
34
|
+
const description = "description";
|
|
35
|
+
const descriptionError = showErrors && getIn(errors, description);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className={"flex flex-col gap-2 col-span-12"}>
|
|
39
|
+
|
|
40
|
+
<div>
|
|
41
|
+
<Field
|
|
42
|
+
inputRef={ref}
|
|
43
|
+
name={name}
|
|
44
|
+
as={DebouncedTextField}
|
|
45
|
+
invisible={!isNewProperty}
|
|
46
|
+
style={{ fontSize: 20 }}
|
|
47
|
+
validate={validateName}
|
|
48
|
+
placeholder={"Field name"}
|
|
49
|
+
required
|
|
50
|
+
disabled={disabled}
|
|
51
|
+
error={Boolean(nameError)}/>
|
|
52
|
+
|
|
53
|
+
<FieldHelperView error={Boolean(nameError)}>
|
|
54
|
+
{nameError}
|
|
55
|
+
</FieldHelperView>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div>
|
|
59
|
+
<Field name={id}
|
|
60
|
+
as={TextField}
|
|
61
|
+
label={"ID"}
|
|
62
|
+
validate={(value?: string) => validateId(value, existingPropertyKeys)}
|
|
63
|
+
disabled={disabledId || disabled}
|
|
64
|
+
required
|
|
65
|
+
size="small"
|
|
66
|
+
error={Boolean(idError)}/>
|
|
67
|
+
<FieldHelperView error={Boolean(idError)}>
|
|
68
|
+
{idError}
|
|
69
|
+
</FieldHelperView>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div>
|
|
73
|
+
<Field name={description}
|
|
74
|
+
as={DebouncedTextField}
|
|
75
|
+
label={"Description"}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
error={Boolean(descriptionError)}/>
|
|
78
|
+
<FieldHelperView error={Boolean(descriptionError)}>
|
|
79
|
+
{descriptionError}
|
|
80
|
+
</FieldHelperView>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const idRegEx = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
90
|
+
|
|
91
|
+
function validateId(value?: string, existingPropertyKeys?: string[]) {
|
|
92
|
+
|
|
93
|
+
let error;
|
|
94
|
+
if (!value) {
|
|
95
|
+
error = "You must specify an id for the field";
|
|
96
|
+
}
|
|
97
|
+
if (value && !value.match(idRegEx)) {
|
|
98
|
+
error = "The id can only contain letters, numbers and underscores (_), and not start with a number";
|
|
99
|
+
}
|
|
100
|
+
if (value && existingPropertyKeys && existingPropertyKeys.includes(value)) {
|
|
101
|
+
error = "There is another field with this ID already";
|
|
102
|
+
}
|
|
103
|
+
return error;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function validateName(value: string) {
|
|
107
|
+
let error;
|
|
108
|
+
if (!value) {
|
|
109
|
+
error = "You must specify a title for the field";
|
|
110
|
+
}
|
|
111
|
+
return error;
|
|
112
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { getIn, useFormikContext } from "formik";
|
|
3
|
+
import { NumberProperty, Select, SelectItem, StringProperty } from "@firecms/core";
|
|
4
|
+
import { GeneralPropertyValidation } from "./validation/GeneralPropertyValidation";
|
|
5
|
+
import { ValidationPanel } from "./validation/ValidationPanel";
|
|
6
|
+
import { FieldHelperView } from "./FieldHelperView";
|
|
7
|
+
|
|
8
|
+
export function DateTimePropertyField({ disabled }: {
|
|
9
|
+
disabled: boolean;
|
|
10
|
+
}) {
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
values,
|
|
14
|
+
errors,
|
|
15
|
+
touched,
|
|
16
|
+
setFieldValue
|
|
17
|
+
} = useFormikContext<StringProperty | NumberProperty>();
|
|
18
|
+
|
|
19
|
+
const modePath = "mode";
|
|
20
|
+
const modeValue: string | undefined = getIn(values, modePath);
|
|
21
|
+
const modeError: string | undefined = getIn(touched, modePath) && getIn(errors, modePath);
|
|
22
|
+
|
|
23
|
+
const autoValuePath = "autoValue";
|
|
24
|
+
const autoValueValue: string | undefined = getIn(values, autoValuePath);
|
|
25
|
+
const autoValueError: string | undefined = getIn(touched, autoValuePath) && getIn(errors, autoValuePath);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<>
|
|
29
|
+
<div className={"flex flex-col col-span-12"}>
|
|
30
|
+
<Select name={modePath}
|
|
31
|
+
value={modeValue ?? "date"}
|
|
32
|
+
error={Boolean(modeError)}
|
|
33
|
+
onValueChange={(v) => setFieldValue(modePath, v)}
|
|
34
|
+
label={"Mode"}
|
|
35
|
+
renderValue={(v) => {
|
|
36
|
+
switch (v) {
|
|
37
|
+
case "date_time":
|
|
38
|
+
return "Date/Time";
|
|
39
|
+
case "date":
|
|
40
|
+
return "Date";
|
|
41
|
+
default:
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
}}
|
|
45
|
+
disabled={disabled}>
|
|
46
|
+
<SelectItem value={"date_time"}> Date/Time </SelectItem>
|
|
47
|
+
<SelectItem value={"date"}> Date </SelectItem>
|
|
48
|
+
</Select>
|
|
49
|
+
<FieldHelperView error={Boolean(modeError)}>
|
|
50
|
+
{modeError}
|
|
51
|
+
</FieldHelperView>
|
|
52
|
+
|
|
53
|
+
<Select name={autoValuePath}
|
|
54
|
+
disabled={disabled}
|
|
55
|
+
value={autoValueValue ?? ""}
|
|
56
|
+
onValueChange={(v) => setFieldValue(autoValuePath, v)}
|
|
57
|
+
renderValue={(v) => {
|
|
58
|
+
switch (v) {
|
|
59
|
+
case "on_create":
|
|
60
|
+
return "On create";
|
|
61
|
+
case "on_update":
|
|
62
|
+
return "On any update";
|
|
63
|
+
default:
|
|
64
|
+
return "None";
|
|
65
|
+
}
|
|
66
|
+
}}
|
|
67
|
+
error={Boolean(autoValueError)}
|
|
68
|
+
label={"Automatic value"}>
|
|
69
|
+
<SelectItem value={""}> None </SelectItem>
|
|
70
|
+
<SelectItem value={"on_create"}> On create </SelectItem>
|
|
71
|
+
<SelectItem value={"on_update"}> On any update </SelectItem>
|
|
72
|
+
</Select>
|
|
73
|
+
<FieldHelperView error={Boolean(autoValueError)}>
|
|
74
|
+
{autoValueError ?? "Update this field automatically when creating or updating the entity"}
|
|
75
|
+
</FieldHelperView>
|
|
76
|
+
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div className={"col-span-12"}>
|
|
80
|
+
<ValidationPanel>
|
|
81
|
+
<GeneralPropertyValidation disabled={disabled}/>
|
|
82
|
+
</ValidationPanel>
|
|
83
|
+
</div>
|
|
84
|
+
</>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React, { useMemo } from "react";
|
|
2
|
+
import { getIn, useFormikContext } from "formik";
|
|
3
|
+
import { EnumValueConfig, resolveEnumValues, Select, SelectItem, useSnackbarController } from "@firecms/core";
|
|
4
|
+
import { EnumForm } from "../EnumForm";
|
|
5
|
+
import { StringPropertyValidation } from "./validation/StringPropertyValidation";
|
|
6
|
+
import { ArrayPropertyValidation } from "./validation/ArrayPropertyValidation";
|
|
7
|
+
import { ValidationPanel } from "./validation/ValidationPanel";
|
|
8
|
+
import { PropertyWithId } from "../PropertyEditView";
|
|
9
|
+
|
|
10
|
+
export function EnumPropertyField({
|
|
11
|
+
multiselect,
|
|
12
|
+
updateIds,
|
|
13
|
+
disabled,
|
|
14
|
+
showErrors,
|
|
15
|
+
allowDataInference,
|
|
16
|
+
getData
|
|
17
|
+
}: {
|
|
18
|
+
multiselect: boolean;
|
|
19
|
+
updateIds: boolean;
|
|
20
|
+
disabled: boolean;
|
|
21
|
+
showErrors: boolean;
|
|
22
|
+
allowDataInference?: boolean;
|
|
23
|
+
getData?: () => Promise<object[]>;
|
|
24
|
+
}) {
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
values,
|
|
28
|
+
handleChange,
|
|
29
|
+
errors,
|
|
30
|
+
touched,
|
|
31
|
+
setFieldError,
|
|
32
|
+
setFieldValue
|
|
33
|
+
} = useFormikContext<PropertyWithId>();
|
|
34
|
+
|
|
35
|
+
const snackbarContext = useSnackbarController();
|
|
36
|
+
|
|
37
|
+
const enumValuesPath = multiselect ? "of.enumValues" : "enumValues";
|
|
38
|
+
|
|
39
|
+
const defaultValue = getIn(values, "defaultValue");
|
|
40
|
+
|
|
41
|
+
const valuesEnumValues = getIn(values, enumValuesPath);
|
|
42
|
+
const enumValues: EnumValueConfig[] = useMemo(() => {
|
|
43
|
+
if (!valuesEnumValues || typeof valuesEnumValues === "boolean")
|
|
44
|
+
return [] as EnumValueConfig[];
|
|
45
|
+
return resolveEnumValues(valuesEnumValues) ?? [] as EnumValueConfig[];
|
|
46
|
+
}, [valuesEnumValues]);
|
|
47
|
+
|
|
48
|
+
const onValuesChanged = (value: EnumValueConfig[]) => {
|
|
49
|
+
if (!values)
|
|
50
|
+
return;
|
|
51
|
+
setFieldValue(enumValuesPath, value);
|
|
52
|
+
if (!multiselect) {
|
|
53
|
+
const enumIds = value.filter(v => Boolean(v?.id)).map((v: any) => v.id);
|
|
54
|
+
if (defaultValue && !enumIds.includes(defaultValue)) {
|
|
55
|
+
setFieldValue("defaultValue", undefined);
|
|
56
|
+
snackbarContext.open({
|
|
57
|
+
type: "warning",
|
|
58
|
+
message: "Default value was cleared"
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<div className={"col-span-12"}>
|
|
67
|
+
<EnumForm enumValues={enumValues}
|
|
68
|
+
updateIds={updateIds}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
allowDataInference={allowDataInference}
|
|
71
|
+
onError={(hasError) => {
|
|
72
|
+
setFieldError(enumValuesPath, hasError ? "" : undefined);
|
|
73
|
+
}}
|
|
74
|
+
getData={getData
|
|
75
|
+
? () => getData()
|
|
76
|
+
.then(res => res.map(d => values.id && getIn(d, values.id)).filter(Boolean))
|
|
77
|
+
: undefined}
|
|
78
|
+
onValuesChanged={onValuesChanged}/>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div className={"col-span-12"}>
|
|
82
|
+
|
|
83
|
+
<ValidationPanel>
|
|
84
|
+
{!multiselect &&
|
|
85
|
+
<StringPropertyValidation disabled={disabled}
|
|
86
|
+
showErrors={showErrors}/>}
|
|
87
|
+
{multiselect &&
|
|
88
|
+
<ArrayPropertyValidation disabled={disabled}/>}
|
|
89
|
+
</ValidationPanel>
|
|
90
|
+
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{!multiselect && <div className={"col-span-12"}>
|
|
94
|
+
|
|
95
|
+
<Select
|
|
96
|
+
disabled={disabled}
|
|
97
|
+
position={"item-aligned"}
|
|
98
|
+
onValueChange={(value: string) => {
|
|
99
|
+
setFieldValue("defaultValue", value);
|
|
100
|
+
}}
|
|
101
|
+
label={"Default value"}
|
|
102
|
+
value={defaultValue ?? ""}>
|
|
103
|
+
{enumValues
|
|
104
|
+
.filter((enumValue) => Boolean(enumValue?.id))
|
|
105
|
+
.map((enumValue) => (
|
|
106
|
+
<SelectItem key={enumValue.id}
|
|
107
|
+
value={enumValue.id?.toString()}>
|
|
108
|
+
{enumValue.label}
|
|
109
|
+
</SelectItem>
|
|
110
|
+
))}
|
|
111
|
+
</Select>
|
|
112
|
+
|
|
113
|
+
</div>}
|
|
114
|
+
</>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Typography } from "@firecms/core";
|
|
2
|
+
|
|
3
|
+
export function FieldHelperView({
|
|
4
|
+
error,
|
|
5
|
+
children
|
|
6
|
+
}: { error?: boolean, children?: React.ReactNode }) {
|
|
7
|
+
if (!children) return null;
|
|
8
|
+
return (
|
|
9
|
+
<Typography variant={"caption"} color={error ? "error" : "secondary"} className={"ml-3.5 mt-0.5"}>
|
|
10
|
+
{children}
|
|
11
|
+
</Typography>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ValidationPanel } from "./validation/ValidationPanel";
|
|
3
|
+
import { GeneralPropertyValidation } from "./validation/GeneralPropertyValidation";
|
|
4
|
+
|
|
5
|
+
export function KeyValuePropertyField({ disabled }: {
|
|
6
|
+
disabled: boolean;
|
|
7
|
+
}) {
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<div className={"col-span-12"}>
|
|
12
|
+
|
|
13
|
+
<ValidationPanel>
|
|
14
|
+
<GeneralPropertyValidation disabled={disabled}/>
|
|
15
|
+
</ValidationPanel>
|
|
16
|
+
|
|
17
|
+
</div>
|
|
18
|
+
</>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
AddIcon,
|
|
4
|
+
BooleanSwitchWithLabel,
|
|
5
|
+
Button,
|
|
6
|
+
FieldConfig,
|
|
7
|
+
MapProperty,
|
|
8
|
+
Paper,
|
|
9
|
+
Property,
|
|
10
|
+
Typography
|
|
11
|
+
} from "@firecms/core";
|
|
12
|
+
import { PropertyFormDialog } from "../PropertyEditView";
|
|
13
|
+
import { getIn, useFormikContext } from "formik";
|
|
14
|
+
import { PropertyTree } from "../PropertyTree";
|
|
15
|
+
import { getFullId, idToPropertiesPath, namespaceToPropertiesOrderPath, namespaceToPropertiesPath } from "../util";
|
|
16
|
+
import { FieldHelperView } from "./FieldHelperView";
|
|
17
|
+
|
|
18
|
+
export function MapPropertyField({ disabled, getData, allowDataInference, customFields }: {
|
|
19
|
+
disabled: boolean;
|
|
20
|
+
getData?: () => Promise<object[]>;
|
|
21
|
+
allowDataInference: boolean;
|
|
22
|
+
customFields: Record<string, FieldConfig>
|
|
23
|
+
}) {
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
values,
|
|
27
|
+
setFieldValue
|
|
28
|
+
} = useFormikContext<MapProperty>();
|
|
29
|
+
|
|
30
|
+
const [propertyDialogOpen, setPropertyDialogOpen] = useState<boolean>(false);
|
|
31
|
+
const [selectedPropertyKey, setSelectedPropertyKey] = useState<string | undefined>();
|
|
32
|
+
const [selectedPropertyNamespace, setSelectedPropertyNamespace] = useState<string | undefined>();
|
|
33
|
+
|
|
34
|
+
const propertiesOrder = values.propertiesOrder ?? Object.keys(values.properties ?? {});
|
|
35
|
+
const onPropertyCreated = useCallback(({
|
|
36
|
+
id,
|
|
37
|
+
property
|
|
38
|
+
}: { id?: string, property: Property }) => {
|
|
39
|
+
if (!id)
|
|
40
|
+
throw Error();
|
|
41
|
+
setFieldValue("properties", {
|
|
42
|
+
...(values.properties ?? {}),
|
|
43
|
+
[id]: property
|
|
44
|
+
}, false);
|
|
45
|
+
setFieldValue("propertiesOrder", [...propertiesOrder, id], false);
|
|
46
|
+
setPropertyDialogOpen(false);
|
|
47
|
+
}, [values.properties, propertiesOrder]);
|
|
48
|
+
|
|
49
|
+
const deleteProperty = useCallback((propertyKey?: string, namespace?: string) => {
|
|
50
|
+
const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
|
|
51
|
+
if (!fullId)
|
|
52
|
+
throw Error("collection editor miss config");
|
|
53
|
+
|
|
54
|
+
const propertiesPath = idToPropertiesPath(fullId);
|
|
55
|
+
const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
|
|
56
|
+
|
|
57
|
+
const currentPropertiesOrder: string[] = getIn(values, propertiesOrderPath) ?? Object.keys(getIn(values, namespaceToPropertiesPath(namespace)));
|
|
58
|
+
|
|
59
|
+
setFieldValue(propertiesPath, undefined, false);
|
|
60
|
+
setFieldValue(propertiesOrderPath, currentPropertiesOrder.filter((p) => p !== propertyKey), false);
|
|
61
|
+
|
|
62
|
+
setPropertyDialogOpen(false);
|
|
63
|
+
setSelectedPropertyKey(undefined);
|
|
64
|
+
setSelectedPropertyNamespace(undefined);
|
|
65
|
+
}, [setFieldValue, values]);
|
|
66
|
+
|
|
67
|
+
const selectedPropertyFullId = selectedPropertyKey ? getFullId(selectedPropertyKey, selectedPropertyNamespace) : undefined;
|
|
68
|
+
const selectedProperty = selectedPropertyFullId ? getIn(values.properties, selectedPropertyFullId.replaceAll(".", ".properties.")) : undefined;
|
|
69
|
+
|
|
70
|
+
const addChildButton = <Button
|
|
71
|
+
color="primary"
|
|
72
|
+
variant={"outlined"}
|
|
73
|
+
onClick={() => setPropertyDialogOpen(true)}
|
|
74
|
+
startIcon={<AddIcon/>}
|
|
75
|
+
>
|
|
76
|
+
Add property to {values.name ?? "this group"}
|
|
77
|
+
</Button>;
|
|
78
|
+
|
|
79
|
+
const empty = !propertiesOrder || propertiesOrder.length < 1;
|
|
80
|
+
|
|
81
|
+
const onPropertyMove = useCallback((propertiesOrder: string[], namespace?: string) => {
|
|
82
|
+
setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<div className={"col-span-12"}>
|
|
88
|
+
<div className="flex justify-between items-end my-4">
|
|
89
|
+
<Typography variant={"subtitle2"}>Properties in this group</Typography>
|
|
90
|
+
{addChildButton}
|
|
91
|
+
</div>
|
|
92
|
+
<Paper className="p-2 pl-8">
|
|
93
|
+
<PropertyTree
|
|
94
|
+
properties={values.properties ?? {}}
|
|
95
|
+
propertiesOrder={propertiesOrder}
|
|
96
|
+
errors={{}}
|
|
97
|
+
onPropertyClick={(propertyKey, namespace) => {
|
|
98
|
+
setSelectedPropertyKey(propertyKey);
|
|
99
|
+
setSelectedPropertyNamespace(namespace);
|
|
100
|
+
setPropertyDialogOpen(true);
|
|
101
|
+
}}
|
|
102
|
+
onPropertyMove={onPropertyMove}/>
|
|
103
|
+
|
|
104
|
+
{empty &&
|
|
105
|
+
<Typography variant={"label"}
|
|
106
|
+
className="h-full flex items-center justify-center p-2">
|
|
107
|
+
Add the first property to this group
|
|
108
|
+
</Typography>}
|
|
109
|
+
</Paper>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div className={"col-span-12"}>
|
|
113
|
+
<BooleanSwitchWithLabel
|
|
114
|
+
position={"start"}
|
|
115
|
+
size={"small"}
|
|
116
|
+
label="Spread children as columns"
|
|
117
|
+
onValueChange={(v) => setFieldValue("spreadChildren", v)}
|
|
118
|
+
value={values.spreadChildren ?? false}
|
|
119
|
+
/>
|
|
120
|
+
<FieldHelperView>
|
|
121
|
+
Set this flag to true if you want to display the children of this group as individual columns.
|
|
122
|
+
</FieldHelperView>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<PropertyFormDialog
|
|
126
|
+
inArray={false}
|
|
127
|
+
forceShowErrors={false}
|
|
128
|
+
open={propertyDialogOpen}
|
|
129
|
+
allowDataInference={allowDataInference}
|
|
130
|
+
onCancel={() => {
|
|
131
|
+
setPropertyDialogOpen(false);
|
|
132
|
+
setSelectedPropertyKey(undefined);
|
|
133
|
+
setSelectedPropertyNamespace(undefined);
|
|
134
|
+
}}
|
|
135
|
+
onOkClicked={() => {
|
|
136
|
+
setPropertyDialogOpen(false);
|
|
137
|
+
setSelectedPropertyKey(undefined);
|
|
138
|
+
setSelectedPropertyNamespace(undefined);
|
|
139
|
+
}}
|
|
140
|
+
getData={getData}
|
|
141
|
+
onDelete={deleteProperty}
|
|
142
|
+
propertyKey={selectedPropertyKey}
|
|
143
|
+
propertyNamespace={selectedPropertyNamespace}
|
|
144
|
+
property={selectedProperty}
|
|
145
|
+
existing={Boolean(selectedPropertyKey)}
|
|
146
|
+
autoUpdateId={!selectedPropertyKey}
|
|
147
|
+
autoOpenTypeSelect={!selectedPropertyKey}
|
|
148
|
+
onPropertyChanged={onPropertyCreated}
|
|
149
|
+
existingPropertyKeys={selectedPropertyKey ? undefined : propertiesOrder}
|
|
150
|
+
customFields={customFields}
|
|
151
|
+
/>
|
|
152
|
+
|
|
153
|
+
</>);
|
|
154
|
+
}
|