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

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 (59) hide show
  1. package/dist/index.es.js +2532 -2386
  2. package/dist/index.es.js.map +1 -1
  3. package/dist/index.umd.js +2 -2
  4. package/dist/index.umd.js.map +1 -1
  5. package/dist/types/collection_editor_controller.d.ts +3 -2
  6. package/dist/types/config_controller.d.ts +3 -3
  7. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -5
  8. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
  9. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -2
  10. package/dist/ui/collection_editor/EnumForm.d.ts +1 -2
  11. package/dist/ui/collection_editor/PropertyEditView.d.ts +5 -5
  12. package/dist/ui/collection_editor/PropertyTree.d.ts +14 -13
  13. package/dist/ui/collection_editor/SwitchControl.d.ts +8 -0
  14. package/dist/ui/collection_editor/properties/CommonPropertyFields.d.ts +0 -1
  15. package/dist/ui/collection_editor/util.d.ts +1 -0
  16. package/package.json +6 -5
  17. package/src/ConfigControllerProvider.tsx +24 -22
  18. package/src/types/collection_editor_controller.tsx +4 -3
  19. package/src/types/config_controller.tsx +3 -3
  20. package/src/ui/CollectionViewHeaderAction.tsx +1 -1
  21. package/src/ui/EditorCollectionAction.tsx +3 -3
  22. package/src/ui/HomePageEditorCollectionAction.tsx +2 -2
  23. package/src/ui/MissingReferenceWidget.tsx +2 -1
  24. package/src/ui/NewCollectionButton.tsx +3 -3
  25. package/src/ui/NewCollectionCard.tsx +2 -1
  26. package/src/ui/PropertyAddColumnComponent.tsx +1 -1
  27. package/src/ui/RootCollectionSuggestions.tsx +3 -2
  28. package/src/ui/collection_editor/CollectionDetailsForm.tsx +2 -2
  29. package/src/ui/collection_editor/CollectionEditorDialog.tsx +420 -374
  30. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +19 -12
  31. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +26 -18
  32. package/src/ui/collection_editor/EnumForm.tsx +118 -114
  33. package/src/ui/collection_editor/GetCodeDialog.tsx +1 -1
  34. package/src/ui/collection_editor/PropertyEditView.tsx +199 -142
  35. package/src/ui/collection_editor/PropertyFieldPreview.tsx +5 -1
  36. package/src/ui/collection_editor/PropertyTree.tsx +132 -113
  37. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +18 -11
  38. package/src/ui/collection_editor/SwitchControl.tsx +39 -0
  39. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +10 -2
  40. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +2 -2
  41. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +13 -9
  42. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +11 -37
  43. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +2 -2
  44. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -6
  45. package/src/ui/collection_editor/properties/MapPropertyField.tsx +2 -2
  46. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +2 -2
  47. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +11 -14
  48. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +10 -9
  49. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +15 -9
  50. package/src/ui/collection_editor/properties/StringPropertyField.tsx +2 -2
  51. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +2 -2
  52. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +27 -18
  53. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +2 -2
  54. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +27 -16
  55. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +33 -18
  56. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +99 -80
  57. package/src/ui/collection_editor/util.ts +7 -0
  58. package/src/ui/collection_editor/utils/strings.ts +2 -1
  59. package/src/useCollectionEditorPlugin.tsx +1 -16
@@ -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 "@firecms/formex";
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,68 @@ 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
+ autoComplete={"off"}
259
+ onSubmit={(e) => {
260
+ e.preventDefault();
261
+ e.stopPropagation();
262
+ formexRef.current?.handleSubmit(e)
263
+ }}>
264
+ <DialogContent>
265
+ <PropertyForm {...formProps}
266
+ onPropertyChanged={(params) => {
267
+ onPropertyChanged?.(params);
268
+ onOkClicked?.();
269
+ }}
270
+ collectionEditable={collectionEditable}
271
+ onPropertyChangedImmediate={false}
272
+ getController={getController}
273
+ getData={getData}
274
+ />
275
+ </DialogContent>
276
+
277
+ <DialogActions>
278
+
279
+ {onCancel && <Button
280
+ variant={"text"}
281
+ onClick={() => {
282
+ onCancel();
283
+ formexRef.current?.resetForm();
284
+ }}>
285
+ Cancel
286
+ </Button>}
287
+
288
+ <Button variant="outlined"
289
+ type={"submit"}
290
+ color="primary">
291
+ Ok
292
+ </Button>
293
+ </DialogActions>
294
+ </form>
258
295
  </Dialog>;
259
296
 
260
297
  }
261
298
 
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
- }: {
299
+ function PropertyEditFormFields({
300
+ values,
301
+ errors,
302
+ setValues,
303
+ existing,
304
+ autoUpdateId = false,
305
+ autoOpenTypeSelect,
306
+ includeIdAndTitle,
307
+ onPropertyChanged,
308
+ onDelete,
309
+ propertyNamespace,
310
+ onError,
311
+ showErrors,
312
+ disabled,
313
+ inArray,
314
+ getData,
315
+ allowDataInference,
316
+ propertyConfigs,
317
+ collectionEditable
318
+ }: {
285
319
  includeIdAndTitle?: boolean;
286
320
  existing: boolean;
287
321
  autoUpdateId?: boolean;
@@ -289,16 +323,15 @@ function PropertyEditView({
289
323
  propertyNamespace?: string;
290
324
  onPropertyChanged?: (params: OnPropertyChangedParams) => void;
291
325
  onDelete?: (id?: string, namespace?: string) => void;
292
- onError?: (id: string, namespace?: string, error?: FormikErrors<any>) => void;
326
+ onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
293
327
  showErrors: boolean;
294
328
  inArray: boolean;
295
329
  disabled: boolean;
296
- existingPropertyKeys?: string[];
297
330
  getData?: () => Promise<object[]>;
298
331
  allowDataInference: boolean;
299
332
  propertyConfigs: Record<string, PropertyConfig>;
300
333
  collectionEditable: boolean;
301
- } & FormikProps<PropertyWithId>) {
334
+ } & FormexController<PropertyWithId>) {
302
335
 
303
336
  const [selectOpen, setSelectOpen] = useState(autoOpenTypeSelect);
304
337
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
@@ -337,7 +370,7 @@ function PropertyEditView({
337
370
  }, [deferredValues, includeIdAndTitle, onPropertyChanged, propertyNamespace]);
338
371
 
339
372
  useEffect(() => {
340
- if (values?.id && onError && Object.keys(errors).length > 0) {
373
+ if (values?.id && onError) {
341
374
  onError(values?.id, propertyNamespace, errors);
342
375
  }
343
376
  }, [errors, onError, propertyNamespace, values?.id]);
@@ -530,7 +563,6 @@ function PropertyEditView({
530
563
  <CommonPropertyFields showErrors={showErrors}
531
564
  disabledId={existing}
532
565
  isNewProperty={!existing}
533
- existingPropertyKeys={existingPropertyKeys}
534
566
  disabled={disabled}
535
567
  autoUpdateId={autoUpdateId}
536
568
  ref={nameFieldRef}/>}
@@ -556,3 +588,28 @@ function PropertyEditView({
556
588
  </>
557
589
  );
558
590
  }
591
+
592
+ const idRegEx = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
593
+
594
+ function validateId(value?: string, existingPropertyKeys?: string[]) {
595
+
596
+ let error;
597
+ if (!value) {
598
+ error = "You must specify an id for the field";
599
+ }
600
+ if (value && !value.match(idRegEx)) {
601
+ error = "The id can only contain letters, numbers and underscores (_), and not start with a number";
602
+ }
603
+ if (value && existingPropertyKeys && existingPropertyKeys.includes(value)) {
604
+ error = "There is another field with this ID already";
605
+ }
606
+ return error;
607
+ }
608
+
609
+ function validateName(value: string) {
610
+ let error;
611
+ if (!value) {
612
+ error = "You must specify a title for the field";
613
+ }
614
+ return error;
615
+ }
@@ -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 : "",