@firecms/collection_editor 3.0.0-beta.2-pre.2 → 3.0.0-beta.2-pre.3

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.
Files changed (71) hide show
  1. package/dist/form/Field.d.ts +53 -0
  2. package/dist/form/Formex.d.ts +4 -0
  3. package/dist/form/index.d.ts +5 -0
  4. package/dist/form/types.d.ts +25 -0
  5. package/dist/form/useCreateFormex.d.ts +9 -0
  6. package/dist/form/utils.d.ts +44 -0
  7. package/dist/index.es.js +2612 -2328
  8. package/dist/index.es.js.map +1 -1
  9. package/dist/index.umd.js +2 -2
  10. package/dist/index.umd.js.map +1 -1
  11. package/dist/types/collection_editor_controller.d.ts +3 -2
  12. package/dist/types/config_controller.d.ts +3 -3
  13. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -5
  14. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
  15. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -2
  16. package/dist/ui/collection_editor/EnumForm.d.ts +1 -2
  17. package/dist/ui/collection_editor/PropertyEditView.d.ts +5 -5
  18. package/dist/ui/collection_editor/PropertyTree.d.ts +14 -13
  19. package/dist/ui/collection_editor/SwitchControl.d.ts +8 -0
  20. package/dist/ui/collection_editor/properties/CommonPropertyFields.d.ts +0 -1
  21. package/dist/ui/collection_editor/util.d.ts +1 -0
  22. package/package.json +5 -5
  23. package/src/ConfigControllerProvider.tsx +23 -21
  24. package/src/form/Field.tsx +162 -0
  25. package/src/form/Formex.tsx +8 -0
  26. package/src/form/README.md +165 -0
  27. package/src/form/index.ts +5 -0
  28. package/src/form/types.ts +27 -0
  29. package/src/form/useCreateFormex.tsx +137 -0
  30. package/src/form/utils.ts +169 -0
  31. package/src/types/collection_editor_controller.tsx +4 -3
  32. package/src/types/config_controller.tsx +3 -3
  33. package/src/ui/CollectionViewHeaderAction.tsx +1 -1
  34. package/src/ui/EditorCollectionAction.tsx +3 -3
  35. package/src/ui/HomePageEditorCollectionAction.tsx +2 -2
  36. package/src/ui/MissingReferenceWidget.tsx +2 -1
  37. package/src/ui/NewCollectionButton.tsx +3 -3
  38. package/src/ui/NewCollectionCard.tsx +2 -1
  39. package/src/ui/PropertyAddColumnComponent.tsx +1 -1
  40. package/src/ui/RootCollectionSuggestions.tsx +2 -1
  41. package/src/ui/collection_editor/CollectionDetailsForm.tsx +2 -2
  42. package/src/ui/collection_editor/CollectionEditorDialog.tsx +422 -374
  43. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +19 -12
  44. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +26 -18
  45. package/src/ui/collection_editor/EnumForm.tsx +118 -114
  46. package/src/ui/collection_editor/GetCodeDialog.tsx +1 -1
  47. package/src/ui/collection_editor/PropertyEditView.tsx +198 -142
  48. package/src/ui/collection_editor/PropertyFieldPreview.tsx +5 -1
  49. package/src/ui/collection_editor/PropertyTree.tsx +132 -113
  50. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +18 -11
  51. package/src/ui/collection_editor/SwitchControl.tsx +39 -0
  52. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +10 -2
  53. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +2 -2
  54. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +13 -9
  55. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +11 -37
  56. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +2 -2
  57. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -6
  58. package/src/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
  59. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +2 -2
  60. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +11 -14
  61. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +10 -9
  62. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +15 -9
  63. package/src/ui/collection_editor/properties/StringPropertyField.tsx +2 -2
  64. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +2 -2
  65. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +27 -18
  66. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +2 -2
  67. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +27 -16
  68. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +33 -18
  69. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +99 -80
  70. package/src/ui/collection_editor/util.ts +7 -0
  71. package/src/ui/collection_editor/utils/strings.ts +2 -1
@@ -1,18 +1,19 @@
1
1
  import React, { useDeferredValue, useEffect, useRef, useState } from "react";
2
2
  import equal from "react-fast-compare"
3
3
 
4
- import { Formik, FormikErrors, FormikProps, getIn } from "formik";
4
+ import { Formex, FormexController, getIn, useCreateFormex } from "../../form";
5
5
  import {
6
6
  DEFAULT_FIELD_CONFIGS,
7
7
  DeleteConfirmationDialog,
8
- PropertyConfigBadge,
9
8
  FieldConfigId,
10
9
  getFieldConfig,
11
10
  getFieldId,
12
11
  isPropertyBuilder,
12
+ isValidRegExp,
13
13
  mergeDeep,
14
14
  Property,
15
15
  PropertyConfig,
16
+ PropertyConfigBadge,
16
17
  } from "@firecms/core";
17
18
  import {
18
19
  Button,
@@ -68,40 +69,42 @@ export type PropertyFormProps = {
68
69
  onPropertyChanged?: (params: OnPropertyChangedParams) => void;
69
70
  onPropertyChangedImmediate?: boolean;
70
71
  onDelete?: (id?: string, namespace?: string) => void;
71
- onError?: (id: string, namespace?: string, error?: FormikErrors<any>) => void;
72
- initialErrors?: FormikErrors<any>;
73
- forceShowErrors?: boolean;
72
+ onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
73
+ initialErrors?: Record<string, any>;
74
74
  existingPropertyKeys?: string[];
75
+ forceShowErrors?: boolean;
75
76
  allowDataInference: boolean;
76
77
  getData?: () => Promise<object[]>;
77
- getHelpers?: (formikProps: FormikProps<PropertyWithId>) => void;
78
+ getController?: (formex: FormexController<PropertyWithId>) => void;
78
79
  propertyConfigs: Record<string, PropertyConfig>;
79
80
  collectionEditable: boolean;
80
81
  };
81
82
 
82
83
  export const PropertyForm = React.memo(
83
- function PropertyForm({
84
- includeIdAndName = true,
85
- autoOpenTypeSelect,
86
- existingProperty,
87
- autoUpdateId,
88
- inArray,
89
- propertyKey,
90
- propertyNamespace,
91
- property,
92
- onPropertyChanged,
93
- onPropertyChangedImmediate = true,
94
- onDelete,
95
- onError,
96
- initialErrors,
97
- forceShowErrors,
98
- existingPropertyKeys,
99
- allowDataInference,
100
- getHelpers,
101
- getData,
102
- propertyConfigs,
103
- collectionEditable
104
- }: PropertyFormProps) {
84
+ function PropertyForm(props: PropertyFormProps) {
85
+
86
+ const {
87
+ includeIdAndName = true,
88
+ autoOpenTypeSelect,
89
+ existingProperty,
90
+ autoUpdateId,
91
+ inArray,
92
+ propertyKey,
93
+ existingPropertyKeys,
94
+ propertyNamespace,
95
+ property,
96
+ onPropertyChanged,
97
+ onPropertyChangedImmediate = true,
98
+ onDelete,
99
+ onError,
100
+ initialErrors,
101
+ forceShowErrors,
102
+ allowDataInference,
103
+ getController,
104
+ getData,
105
+ propertyConfigs,
106
+ collectionEditable
107
+ } = props;
105
108
 
106
109
  const initialValue: PropertyWithId = {
107
110
  id: "",
@@ -130,13 +133,14 @@ export const PropertyForm = React.memo(
130
133
  onPropertyChanged?.(params);
131
134
  };
132
135
 
133
- return <Formik
134
- key={`property_view_${propertyKey}`}
135
- initialErrors={initialErrors}
136
- initialValues={property
136
+ const formexController = useCreateFormex<PropertyWithId>({
137
+ initialValues: property
137
138
  ? { id: propertyKey, ...property } as PropertyWithId
138
- : initialValue}
139
- onSubmit={(newPropertyWithId: PropertyWithId, helpers) => {
139
+ : initialValue,
140
+ initialErrors,
141
+ validateOnChange: true,
142
+ validateOnInitialRender: true,
143
+ onSubmit: (newPropertyWithId, controller) => {
140
144
  console.debug("onSubmit", newPropertyWithId);
141
145
  const {
142
146
  id,
@@ -147,54 +151,80 @@ export const PropertyForm = React.memo(
147
151
  property: { ...property, editable: property.editable ?? true }
148
152
  });
149
153
  if (!existingProperty)
150
- helpers.resetForm({ values: initialValue });
151
- }}
152
- // validate={(values) => {
153
- // console.log("validate property", values)
154
- // const errors: any = {};
155
- // if (!values?.dataType || !getFieldConfig(values)) {
156
- // errors.selectedWidget = "Required";
157
- // }
158
- // if (existingPropertyKeys && values?.id && existingPropertyKeys.includes(values?.id)) {
159
- // errors.id = "";
160
- // }
161
- // console.log("errors", errors)
162
- // return errors;
163
- // }}
164
- >
165
- {(props) => {
166
-
167
- // eslint-disable-next-line react-hooks/rules-of-hooks
168
- useEffect(() => {
169
- getHelpers?.(props);
170
- }, [props]);
171
-
172
- return <PropertyEditView
173
- onPropertyChanged={onPropertyChangedImmediate
174
- ? doOnPropertyChanged
175
- : undefined}
176
- onDelete={onDelete}
177
- includeIdAndTitle={includeIdAndName}
178
- propertyNamespace={propertyNamespace}
179
- onError={onError}
180
- showErrors={forceShowErrors || props.submitCount > 0}
181
- existing={existingProperty}
182
- autoUpdateId={autoUpdateId}
183
- inArray={inArray}
184
- autoOpenTypeSelect={autoOpenTypeSelect}
185
- existingPropertyKeys={existingPropertyKeys}
186
- disabled={disabled}
187
- getData={getData}
188
- allowDataInference={allowDataInference}
189
- propertyConfigs={propertyConfigs}
190
- collectionEditable={collectionEditable}
191
- {...props}/>;
192
-
193
- }}
194
-
195
- </Formik>
154
+ controller.resetForm({ values: initialValue });
155
+ },
156
+ validation: (values) => {
157
+ const errors: Record<string, any> = {};
158
+ if (includeIdAndName) {
159
+ if (!values.name) {
160
+ errors.name = "Required";
161
+ } else {
162
+ const nameError = validateName(values.name);
163
+ if (nameError)
164
+ errors.name = nameError;
165
+ }
166
+ if (!values.id) {
167
+ errors.id = "Required";
168
+ } else {
169
+ const idError = validateId(values.id, existingPropertyKeys);
170
+ if (idError)
171
+ errors.id = idError;
172
+ }
173
+ }
174
+
175
+ if (values.dataType === "string") {
176
+ if (values.validation?.matches && !isValidRegExp(values.validation?.matches.toString())) {
177
+ errors.validation = {
178
+ matches: "Invalid regular expression"
179
+ }
180
+ }
181
+ }
182
+ if (values.dataType === "reference" && !values.path) {
183
+ errors.path = "You must specify a target collection for the field";
184
+ }
185
+ if (values.propertyConfig === "repeat") {
186
+ if (!(values as any).of) {
187
+ errors.of = "You need to specify a repeat field";
188
+ }
189
+ }
190
+ if (values.propertyConfig === "block") {
191
+ if (!(values as any).oneOf) {
192
+ errors.oneOf = "You need to specify the properties of this block";
193
+ }
194
+ }
195
+ return errors;
196
+ }
197
+ });
198
+
199
+ useEffect(() => {
200
+ getController?.(formexController);
201
+ }, [formexController, getController]);
202
+
203
+ return <Formex value={formexController}>
204
+ <PropertyEditFormFields
205
+ onPropertyChanged={onPropertyChangedImmediate
206
+ ? doOnPropertyChanged
207
+ : undefined}
208
+ onDelete={onDelete}
209
+ includeIdAndTitle={includeIdAndName}
210
+ propertyNamespace={propertyNamespace}
211
+ onError={onError}
212
+ showErrors={forceShowErrors || formexController.submitCount > 0}
213
+ existing={existingProperty}
214
+ autoUpdateId={autoUpdateId}
215
+ inArray={inArray}
216
+ autoOpenTypeSelect={autoOpenTypeSelect}
217
+ disabled={disabled}
218
+ getData={getData}
219
+ allowDataInference={allowDataInference}
220
+ propertyConfigs={propertyConfigs}
221
+ collectionEditable={collectionEditable}
222
+ {...formexController}/>
223
+ </Formex>;
196
224
  }, (a, b) =>
197
225
  a.getData === b.getData &&
226
+ a.propertyKey === b.propertyKey &&
227
+ a.propertyNamespace === b.propertyNamespace &&
198
228
  a.includeIdAndName === b.includeIdAndName &&
199
229
  a.autoOpenTypeSelect === b.autoOpenTypeSelect &&
200
230
  a.autoUpdateId === b.autoUpdateId &&
@@ -214,9 +244,9 @@ export function PropertyFormDialog({
214
244
  onOkClicked?: () => void;
215
245
  onCancel?: () => void;
216
246
  }) {
217
- const helpersRef = useRef<FormikProps<PropertyWithId>>();
218
- const getHelpers = (helpers: FormikProps<PropertyWithId>) => {
219
- helpersRef.current = helpers;
247
+ const formexRef = useRef<FormexController<PropertyWithId>>();
248
+ const getController = (helpers: FormexController<PropertyWithId>) => {
249
+ formexRef.current = helpers;
220
250
  };
221
251
 
222
252
  return <Dialog
@@ -224,64 +254,67 @@ export function PropertyFormDialog({
224
254
  maxWidth={"xl"}
225
255
  fullWidth={true}
226
256
  >
227
-
228
- <DialogContent>
229
- <PropertyForm {...formProps}
230
- onPropertyChanged={(params) => {
231
- onPropertyChanged?.(params);
232
- onOkClicked?.();
233
- }}
234
- collectionEditable={collectionEditable}
235
- onPropertyChangedImmediate={false}
236
- getHelpers={getHelpers}
237
- getData={getData}
238
- />
239
- </DialogContent>
240
-
241
- <DialogActions>
242
-
243
- {onCancel && <Button
244
- variant={"text"}
245
- onClick={() => {
246
- onCancel();
247
- helpersRef.current?.resetForm();
248
- }}>
249
- Cancel
250
- </Button>}
251
-
252
- <Button variant="outlined"
253
- color="primary"
254
- onClick={() => helpersRef.current?.submitForm()}>
255
- Ok
256
- </Button>
257
- </DialogActions>
257
+ <form noValidate={true}
258
+ onSubmit={(e) => {
259
+ e.preventDefault();
260
+ e.stopPropagation();
261
+ formexRef.current?.submitForm(e)
262
+ }}>
263
+ <DialogContent>
264
+ <PropertyForm {...formProps}
265
+ onPropertyChanged={(params) => {
266
+ onPropertyChanged?.(params);
267
+ onOkClicked?.();
268
+ }}
269
+ collectionEditable={collectionEditable}
270
+ onPropertyChangedImmediate={false}
271
+ getController={getController}
272
+ getData={getData}
273
+ />
274
+ </DialogContent>
275
+
276
+ <DialogActions>
277
+
278
+ {onCancel && <Button
279
+ variant={"text"}
280
+ onClick={() => {
281
+ onCancel();
282
+ formexRef.current?.resetForm();
283
+ }}>
284
+ Cancel
285
+ </Button>}
286
+
287
+ <Button variant="outlined"
288
+ type={"submit"}
289
+ color="primary">
290
+ Ok
291
+ </Button>
292
+ </DialogActions>
293
+ </form>
258
294
  </Dialog>;
259
295
 
260
296
  }
261
297
 
262
- function PropertyEditView({
263
- values,
264
- errors,
265
- touched,
266
- setValues,
267
- setFieldValue,
268
- existing,
269
- autoUpdateId = false,
270
- autoOpenTypeSelect,
271
- includeIdAndTitle,
272
- onPropertyChanged,
273
- onDelete,
274
- propertyNamespace,
275
- onError,
276
- showErrors,
277
- disabled,
278
- inArray,
279
- existingPropertyKeys,
280
- getData,
281
- allowDataInference,
282
- propertyConfigs,
283
- collectionEditable
284
- }: {
298
+ function PropertyEditFormFields({
299
+ values,
300
+ errors,
301
+ setValues,
302
+ existing,
303
+ autoUpdateId = false,
304
+ autoOpenTypeSelect,
305
+ includeIdAndTitle,
306
+ onPropertyChanged,
307
+ onDelete,
308
+ propertyNamespace,
309
+ onError,
310
+ showErrors,
311
+ disabled,
312
+ inArray,
313
+ getData,
314
+ allowDataInference,
315
+ propertyConfigs,
316
+ collectionEditable
317
+ }: {
285
318
  includeIdAndTitle?: boolean;
286
319
  existing: boolean;
287
320
  autoUpdateId?: boolean;
@@ -289,16 +322,15 @@ function PropertyEditView({
289
322
  propertyNamespace?: string;
290
323
  onPropertyChanged?: (params: OnPropertyChangedParams) => void;
291
324
  onDelete?: (id?: string, namespace?: string) => void;
292
- onError?: (id: string, namespace?: string, error?: FormikErrors<any>) => void;
325
+ onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
293
326
  showErrors: boolean;
294
327
  inArray: boolean;
295
328
  disabled: boolean;
296
- existingPropertyKeys?: string[];
297
329
  getData?: () => Promise<object[]>;
298
330
  allowDataInference: boolean;
299
331
  propertyConfigs: Record<string, PropertyConfig>;
300
332
  collectionEditable: boolean;
301
- } & FormikProps<PropertyWithId>) {
333
+ } & FormexController<PropertyWithId>) {
302
334
 
303
335
  const [selectOpen, setSelectOpen] = useState(autoOpenTypeSelect);
304
336
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@@ -337,7 +369,7 @@ function PropertyEditView({
337
369
  }, [deferredValues, includeIdAndTitle, onPropertyChanged, propertyNamespace]);
338
370
 
339
371
  useEffect(() => {
340
- if (values?.id && onError && Object.keys(errors).length > 0) {
372
+ if (values?.id && onError) {
341
373
  onError(values?.id, propertyNamespace, errors);
342
374
  }
343
375
  }, [errors, onError, propertyNamespace, values?.id]);
@@ -530,7 +562,6 @@ function PropertyEditView({
530
562
  <CommonPropertyFields showErrors={showErrors}
531
563
  disabledId={existing}
532
564
  isNewProperty={!existing}
533
- existingPropertyKeys={existingPropertyKeys}
534
565
  disabled={disabled}
535
566
  autoUpdateId={autoUpdateId}
536
567
  ref={nameFieldRef}/>}
@@ -556,3 +587,28 @@ function PropertyEditView({
556
587
  </>
557
588
  );
558
589
  }
590
+
591
+ const idRegEx = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
592
+
593
+ function validateId(value?: string, existingPropertyKeys?: string[]) {
594
+
595
+ let error;
596
+ if (!value) {
597
+ error = "You must specify an id for the field";
598
+ }
599
+ if (value && !value.match(idRegEx)) {
600
+ error = "The id can only contain letters, numbers and underscores (_), and not start with a number";
601
+ }
602
+ if (value && existingPropertyKeys && existingPropertyKeys.includes(value)) {
603
+ error = "There is another field with this ID already";
604
+ }
605
+ return error;
606
+ }
607
+
608
+ function validateName(value: string) {
609
+ let error;
610
+ if (!value) {
611
+ error = "You must specify a title for the field";
612
+ }
613
+ return error;
614
+ }
@@ -42,9 +42,12 @@ export function PropertyFieldPreview({
42
42
  const disabled = !editableProperty(property);
43
43
 
44
44
  const borderColorClass = hasError
45
- ? "border-red-500"
45
+ ? "border-red-500 dark:border-red-500 border-opacity-100 dark:border-opacity-100 ring-0 dark:ring-0"
46
46
  : (selected ? "border-primary" : "border-transparent");
47
47
 
48
+ if(hasError)
49
+ console.log("PropertyFieldPreview", property)
50
+
48
51
  return <ErrorBoundary>
49
52
  <div
50
53
  onClick={onClick}
@@ -54,6 +57,7 @@ export function PropertyFieldPreview({
54
57
  </div>
55
58
  <Paper
56
59
  className={cn(
60
+ "border",
57
61
  "pl-2 w-full flex flex-row gap-4 items-center",
58
62
  cardMixin,
59
63
  onClick ? cardClickableMixin : "",