@firecms/collection_editor 3.0.0-3.0.0-beta.4.pre.1.0

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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -0
  3. package/dist/ConfigControllerProvider.d.ts +37 -0
  4. package/dist/index.d.ts +11 -0
  5. package/dist/index.es.js +5454 -0
  6. package/dist/index.es.js.map +1 -0
  7. package/dist/index.umd.js +4 -0
  8. package/dist/index.umd.js.map +1 -0
  9. package/dist/types/collection_editor_controller.d.ts +36 -0
  10. package/dist/types/collection_inference.d.ts +2 -0
  11. package/dist/types/config_controller.d.ts +51 -0
  12. package/dist/types/config_permissions.d.ts +19 -0
  13. package/dist/types/persisted_collection.d.ts +6 -0
  14. package/dist/ui/CollectionViewHeaderAction.d.ts +10 -0
  15. package/dist/ui/EditorCollectionAction.d.ts +2 -0
  16. package/dist/ui/HomePageEditorCollectionAction.d.ts +2 -0
  17. package/dist/ui/MissingReferenceWidget.d.ts +3 -0
  18. package/dist/ui/NewCollectionButton.d.ts +1 -0
  19. package/dist/ui/NewCollectionCard.d.ts +2 -0
  20. package/dist/ui/PropertyAddColumnComponent.d.ts +6 -0
  21. package/dist/ui/RootCollectionSuggestions.d.ts +3 -0
  22. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +10 -0
  23. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +36 -0
  24. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +15 -0
  25. package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +19 -0
  26. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +14 -0
  27. package/dist/ui/collection_editor/EntityCustomViewsSelectDialog.d.ts +4 -0
  28. package/dist/ui/collection_editor/EnumForm.d.ts +12 -0
  29. package/dist/ui/collection_editor/GetCodeDialog.d.ts +5 -0
  30. package/dist/ui/collection_editor/PropertyEditView.d.ts +40 -0
  31. package/dist/ui/collection_editor/PropertyFieldPreview.d.ts +15 -0
  32. package/dist/ui/collection_editor/PropertySelectItem.d.ts +8 -0
  33. package/dist/ui/collection_editor/PropertyTree.d.ts +33 -0
  34. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +12 -0
  35. package/dist/ui/collection_editor/SwitchControl.d.ts +8 -0
  36. package/dist/ui/collection_editor/UnsavedChangesDialog.d.ts +9 -0
  37. package/dist/ui/collection_editor/import/CollectionEditorImportDataPreview.d.ts +7 -0
  38. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
  39. package/dist/ui/collection_editor/import/clean_import_data.d.ts +7 -0
  40. package/dist/ui/collection_editor/properties/BlockPropertyField.d.ts +8 -0
  41. package/dist/ui/collection_editor/properties/BooleanPropertyField.d.ts +3 -0
  42. package/dist/ui/collection_editor/properties/CommonPropertyFields.d.ts +10 -0
  43. package/dist/ui/collection_editor/properties/DateTimePropertyField.d.ts +3 -0
  44. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +8 -0
  45. package/dist/ui/collection_editor/properties/KeyValuePropertyField.d.ts +3 -0
  46. package/dist/ui/collection_editor/properties/MapPropertyField.d.ts +8 -0
  47. package/dist/ui/collection_editor/properties/NumberPropertyField.d.ts +3 -0
  48. package/dist/ui/collection_editor/properties/ReferencePropertyField.d.ts +13 -0
  49. package/dist/ui/collection_editor/properties/RepeatPropertyField.d.ts +10 -0
  50. package/dist/ui/collection_editor/properties/StoragePropertyField.d.ts +5 -0
  51. package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +5 -0
  52. package/dist/ui/collection_editor/properties/UrlPropertyField.d.ts +4 -0
  53. package/dist/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.d.ts +3 -0
  54. package/dist/ui/collection_editor/properties/validation/ArrayPropertyValidation.d.ts +5 -0
  55. package/dist/ui/collection_editor/properties/validation/GeneralPropertyValidation.d.ts +4 -0
  56. package/dist/ui/collection_editor/properties/validation/NumberPropertyValidation.d.ts +3 -0
  57. package/dist/ui/collection_editor/properties/validation/StringPropertyValidation.d.ts +11 -0
  58. package/dist/ui/collection_editor/properties/validation/ValidationPanel.d.ts +2 -0
  59. package/dist/ui/collection_editor/templates/blog_template.d.ts +2 -0
  60. package/dist/ui/collection_editor/templates/pages_template.d.ts +2 -0
  61. package/dist/ui/collection_editor/templates/products_template.d.ts +2 -0
  62. package/dist/ui/collection_editor/templates/users_template.d.ts +2 -0
  63. package/dist/ui/collection_editor/util.d.ts +5 -0
  64. package/dist/ui/collection_editor/utils/strings.d.ts +1 -0
  65. package/dist/ui/collection_editor/utils/supported_fields.d.ts +3 -0
  66. package/dist/ui/collection_editor/utils/update_property_for_widget.d.ts +2 -0
  67. package/dist/ui/collection_editor/utils/useTraceUpdate.d.ts +1 -0
  68. package/dist/useCollectionEditorController.d.ts +6 -0
  69. package/dist/useCollectionEditorPlugin.d.ts +49 -0
  70. package/dist/useCollectionsConfigController.d.ts +6 -0
  71. package/dist/utils/arrays.d.ts +1 -0
  72. package/dist/utils/entities.d.ts +3 -0
  73. package/package.json +85 -0
  74. package/src/ConfigControllerProvider.tsx +338 -0
  75. package/src/index.ts +35 -0
  76. package/src/types/collection_editor_controller.tsx +43 -0
  77. package/src/types/collection_inference.ts +3 -0
  78. package/src/types/config_controller.tsx +60 -0
  79. package/src/types/config_permissions.ts +20 -0
  80. package/src/types/persisted_collection.ts +9 -0
  81. package/src/ui/CollectionViewHeaderAction.tsx +43 -0
  82. package/src/ui/EditorCollectionAction.tsx +109 -0
  83. package/src/ui/HomePageEditorCollectionAction.tsx +84 -0
  84. package/src/ui/MissingReferenceWidget.tsx +37 -0
  85. package/src/ui/NewCollectionButton.tsx +16 -0
  86. package/src/ui/NewCollectionCard.tsx +48 -0
  87. package/src/ui/PropertyAddColumnComponent.tsx +42 -0
  88. package/src/ui/RootCollectionSuggestions.tsx +63 -0
  89. package/src/ui/collection_editor/CollectionDetailsForm.tsx +365 -0
  90. package/src/ui/collection_editor/CollectionEditorDialog.tsx +801 -0
  91. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +213 -0
  92. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +506 -0
  93. package/src/ui/collection_editor/CollectionYupValidation.tsx +7 -0
  94. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +37 -0
  95. package/src/ui/collection_editor/EnumForm.tsx +357 -0
  96. package/src/ui/collection_editor/GetCodeDialog.tsx +110 -0
  97. package/src/ui/collection_editor/PropertyEditView.tsx +615 -0
  98. package/src/ui/collection_editor/PropertyFieldPreview.tsx +207 -0
  99. package/src/ui/collection_editor/PropertySelectItem.tsx +32 -0
  100. package/src/ui/collection_editor/PropertyTree.tsx +252 -0
  101. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +262 -0
  102. package/src/ui/collection_editor/SwitchControl.tsx +39 -0
  103. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +47 -0
  104. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +37 -0
  105. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +268 -0
  106. package/src/ui/collection_editor/import/clean_import_data.ts +53 -0
  107. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +138 -0
  108. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +40 -0
  109. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +110 -0
  110. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +86 -0
  111. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +114 -0
  112. package/src/ui/collection_editor/properties/KeyValuePropertyField.tsx +20 -0
  113. package/src/ui/collection_editor/properties/MapPropertyField.tsx +150 -0
  114. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +38 -0
  115. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +160 -0
  116. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +109 -0
  117. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +200 -0
  118. package/src/ui/collection_editor/properties/StringPropertyField.tsx +79 -0
  119. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +89 -0
  120. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +45 -0
  121. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +50 -0
  122. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +61 -0
  123. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +115 -0
  124. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +150 -0
  125. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +28 -0
  126. package/src/ui/collection_editor/templates/blog_template.ts +115 -0
  127. package/src/ui/collection_editor/templates/pages_template.ts +188 -0
  128. package/src/ui/collection_editor/templates/products_template.ts +88 -0
  129. package/src/ui/collection_editor/templates/users_template.ts +42 -0
  130. package/src/ui/collection_editor/util.ts +28 -0
  131. package/src/ui/collection_editor/utils/strings.ts +9 -0
  132. package/src/ui/collection_editor/utils/supported_fields.tsx +29 -0
  133. package/src/ui/collection_editor/utils/update_property_for_widget.ts +271 -0
  134. package/src/ui/collection_editor/utils/useTraceUpdate.tsx +23 -0
  135. package/src/useCollectionEditorController.tsx +9 -0
  136. package/src/useCollectionEditorPlugin.tsx +154 -0
  137. package/src/useCollectionsConfigController.tsx +9 -0
  138. package/src/utils/arrays.ts +3 -0
  139. package/src/utils/entities.ts +38 -0
  140. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,615 @@
1
+ import React, { useDeferredValue, useEffect, useRef, useState } from "react";
2
+ import equal from "react-fast-compare"
3
+
4
+ import { Formex, FormexController, getIn, useCreateFormex } from "@firecms/formex";
5
+ import {
6
+ DEFAULT_FIELD_CONFIGS,
7
+ DeleteConfirmationDialog,
8
+ PropertyConfigId,
9
+ getFieldConfig,
10
+ getFieldId,
11
+ isPropertyBuilder,
12
+ isValidRegExp,
13
+ mergeDeep,
14
+ Property,
15
+ PropertyConfig,
16
+ PropertyConfigBadge,
17
+ } from "@firecms/core";
18
+ import {
19
+ Button,
20
+ cn,
21
+ DeleteIcon,
22
+ Dialog,
23
+ DialogActions,
24
+ DialogContent,
25
+ IconButton,
26
+ InfoLabel,
27
+ Select,
28
+ Typography
29
+ } from "@firecms/ui";
30
+ import { EnumPropertyField } from "./properties/EnumPropertyField";
31
+ import { StoragePropertyField } from "./properties/StoragePropertyField";
32
+ import { MapPropertyField } from "./properties/MapPropertyField";
33
+ import { RepeatPropertyField } from "./properties/RepeatPropertyField";
34
+ import { CommonPropertyFields } from "./properties/CommonPropertyFields";
35
+ import { StringPropertyField } from "./properties/StringPropertyField";
36
+ import { BooleanPropertyField } from "./properties/BooleanPropertyField";
37
+ import { BlockPropertyField } from "./properties/BlockPropertyField";
38
+ import { NumberPropertyField } from "./properties/NumberPropertyField";
39
+ import { ReferencePropertyField } from "./properties/ReferencePropertyField";
40
+ import { DateTimePropertyField } from "./properties/DateTimePropertyField";
41
+ import { AdvancedPropertyValidation } from "./properties/advanced/AdvancedPropertyValidation";
42
+ import { editableProperty } from "../../utils/entities";
43
+ import { KeyValuePropertyField } from "./properties/KeyValuePropertyField";
44
+ import { updatePropertyFromWidget } from "./utils/update_property_for_widget";
45
+ import { PropertySelectItem } from "./PropertySelectItem";
46
+ import { UrlPropertyField } from "./properties/UrlPropertyField";
47
+ import { supportedFields } from "./utils/supported_fields";
48
+
49
+ export type PropertyWithId = Property & {
50
+ id?: string
51
+ };
52
+
53
+ export type OnPropertyChangedParams = {
54
+ id?: string,
55
+ property: Property,
56
+ namespace?: string,
57
+ previousId?: string
58
+ };
59
+
60
+ export type PropertyFormProps = {
61
+ includeIdAndName?: boolean;
62
+ existingProperty: boolean;
63
+ autoUpdateId?: boolean;
64
+ autoOpenTypeSelect: boolean;
65
+ inArray: boolean;
66
+ propertyKey?: string;
67
+ propertyNamespace?: string;
68
+ property?: Property;
69
+ onPropertyChanged?: (params: OnPropertyChangedParams) => void;
70
+ onPropertyChangedImmediate?: boolean;
71
+ onDelete?: (id?: string, namespace?: string) => void;
72
+ onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
73
+ initialErrors?: Record<string, any>;
74
+ existingPropertyKeys?: string[];
75
+ forceShowErrors?: boolean;
76
+ allowDataInference: boolean;
77
+ getData?: () => Promise<object[]>;
78
+ getController?: (formex: FormexController<PropertyWithId>) => void;
79
+ propertyConfigs: Record<string, PropertyConfig>;
80
+ collectionEditable: boolean;
81
+ };
82
+
83
+ export const PropertyForm = React.memo(
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;
108
+
109
+ const initialValue: PropertyWithId = {
110
+ id: "",
111
+ name: ""
112
+ } as PropertyWithId;
113
+
114
+ const disabled = (Boolean(property && !editableProperty(property)) && !collectionEditable);
115
+
116
+ const lastSubmittedProperty = useRef<OnPropertyChangedParams | undefined>(property ? {
117
+ id: propertyKey,
118
+ previousId: propertyKey,
119
+ property
120
+ } : undefined);
121
+
122
+ const doOnPropertyChanged = ({
123
+ id,
124
+ property
125
+ }: OnPropertyChangedParams) => {
126
+ const params = {
127
+ id,
128
+ previousId: lastSubmittedProperty.current?.id,
129
+ property,
130
+ namespace: propertyNamespace
131
+ };
132
+ lastSubmittedProperty.current = params;
133
+ onPropertyChanged?.(params);
134
+ };
135
+
136
+ const formexController = useCreateFormex<PropertyWithId>({
137
+ initialValues: property
138
+ ? { id: propertyKey, ...property } as PropertyWithId
139
+ : initialValue,
140
+ initialErrors,
141
+ validateOnChange: true,
142
+ validateOnInitialRender: true,
143
+ onSubmit: (newPropertyWithId, controller) => {
144
+ console.debug("onSubmit", newPropertyWithId);
145
+ const {
146
+ id,
147
+ ...property
148
+ } = newPropertyWithId;
149
+ doOnPropertyChanged({
150
+ id,
151
+ property: { ...property, editable: property.editable ?? true }
152
+ });
153
+ if (!existingProperty)
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>;
224
+ }, (a, b) =>
225
+ a.getData === b.getData &&
226
+ a.propertyKey === b.propertyKey &&
227
+ a.propertyNamespace === b.propertyNamespace &&
228
+ a.includeIdAndName === b.includeIdAndName &&
229
+ a.autoOpenTypeSelect === b.autoOpenTypeSelect &&
230
+ a.autoUpdateId === b.autoUpdateId &&
231
+ a.existingProperty === b.existingProperty
232
+ );
233
+
234
+ export function PropertyFormDialog({
235
+ open,
236
+ onCancel,
237
+ onOkClicked,
238
+ onPropertyChanged,
239
+ getData,
240
+ collectionEditable,
241
+ ...formProps
242
+ }: PropertyFormProps & {
243
+ open?: boolean;
244
+ onOkClicked?: () => void;
245
+ onCancel?: () => void;
246
+ }) {
247
+ const formexRef = useRef<FormexController<PropertyWithId>>();
248
+ const getController = (helpers: FormexController<PropertyWithId>) => {
249
+ formexRef.current = helpers;
250
+ };
251
+
252
+ return <Dialog
253
+ open={open ?? false}
254
+ maxWidth={"xl"}
255
+ fullWidth={true}
256
+ >
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>
295
+ </Dialog>;
296
+
297
+ }
298
+
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
+ }: {
319
+ includeIdAndTitle?: boolean;
320
+ existing: boolean;
321
+ autoUpdateId?: boolean;
322
+ autoOpenTypeSelect: boolean;
323
+ propertyNamespace?: string;
324
+ onPropertyChanged?: (params: OnPropertyChangedParams) => void;
325
+ onDelete?: (id?: string, namespace?: string) => void;
326
+ onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
327
+ showErrors: boolean;
328
+ inArray: boolean;
329
+ disabled: boolean;
330
+ getData?: () => Promise<object[]>;
331
+ allowDataInference: boolean;
332
+ propertyConfigs: Record<string, PropertyConfig>;
333
+ collectionEditable: boolean;
334
+ } & FormexController<PropertyWithId>) {
335
+
336
+ const [selectOpen, setSelectOpen] = useState(autoOpenTypeSelect);
337
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
338
+ const [selectedFieldConfigId, setSelectedFieldConfigId] = useState<string | undefined>(values?.dataType ? getFieldId(values) : undefined);
339
+
340
+ const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
341
+
342
+ const displayedWidgets = inArray
343
+ ? allSupportedFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property) && propertyConfig.property?.dataType !== "array")
344
+ : allSupportedFields;
345
+
346
+ const deferredValues = useDeferredValue(values);
347
+ const nameFieldRef = useRef<HTMLInputElement>(null);
348
+
349
+ const lastSubmittedProperty = useRef<object>(values);
350
+
351
+ const selectedWidgetError = showErrors && getIn(errors, "selectedWidget");
352
+
353
+ useEffect(() => {
354
+ if (onPropertyChanged) {
355
+ if ((!includeIdAndTitle || deferredValues.id)) {
356
+ const {
357
+ id,
358
+ ...property
359
+ } = deferredValues;
360
+ if (!equal(deferredValues, lastSubmittedProperty.current)) {
361
+ onPropertyChanged({
362
+ id,
363
+ property,
364
+ namespace: propertyNamespace
365
+ });
366
+ lastSubmittedProperty.current = deferredValues;
367
+ }
368
+ }
369
+ }
370
+ }, [deferredValues, includeIdAndTitle, onPropertyChanged, propertyNamespace]);
371
+
372
+ useEffect(() => {
373
+ if (values?.id && onError) {
374
+ onError(values?.id, propertyNamespace, errors);
375
+ }
376
+ }, [errors, onError, propertyNamespace, values?.id]);
377
+
378
+ const onWidgetSelectChanged = (newSelectedWidgetId: PropertyConfigId) => {
379
+ setSelectedFieldConfigId(newSelectedWidgetId);
380
+ setValues(updatePropertyFromWidget(values, newSelectedWidgetId, propertyConfigs));
381
+ // Ugly hack to autofocus the name field
382
+ setTimeout(() => {
383
+ nameFieldRef.current?.focus();
384
+ }, 0);
385
+ };
386
+
387
+ let childComponent;
388
+ if (selectedFieldConfigId === "text_field" ||
389
+ selectedFieldConfigId === "multiline" ||
390
+ selectedFieldConfigId === "markdown" ||
391
+ selectedFieldConfigId === "email") {
392
+ childComponent =
393
+ <StringPropertyField widgetId={selectedFieldConfigId}
394
+ disabled={disabled}
395
+ showErrors={showErrors}/>;
396
+ } else if (selectedFieldConfigId === "url") {
397
+ childComponent =
398
+ <UrlPropertyField disabled={disabled}
399
+ showErrors={showErrors}/>;
400
+ } else if (selectedFieldConfigId === "select" ||
401
+ selectedFieldConfigId === "number_select") {
402
+ childComponent = <EnumPropertyField
403
+ multiselect={false}
404
+ allowDataInference={allowDataInference}
405
+ updateIds={!existing}
406
+ disabled={disabled}
407
+ getData={getData}
408
+ showErrors={showErrors}/>;
409
+ } else if (selectedFieldConfigId === "multi_select" ||
410
+ selectedFieldConfigId === "multi_number_select") {
411
+ childComponent = <EnumPropertyField
412
+ multiselect={true}
413
+ updateIds={!existing}
414
+ disabled={disabled}
415
+ allowDataInference={allowDataInference}
416
+ getData={getData}
417
+ showErrors={showErrors}/>;
418
+ } else if (selectedFieldConfigId === "file_upload") {
419
+ childComponent =
420
+ <StoragePropertyField existing={existing}
421
+ multiple={false}
422
+ disabled={disabled}/>;
423
+ } else if (selectedFieldConfigId === "multi_file_upload") {
424
+ childComponent =
425
+ <StoragePropertyField existing={existing}
426
+ multiple={true}
427
+ disabled={disabled}/>;
428
+ } else if (selectedFieldConfigId === "switch") {
429
+ childComponent = <BooleanPropertyField disabled={disabled}/>;
430
+ } else if (selectedFieldConfigId === "number_input") {
431
+ childComponent = <NumberPropertyField disabled={disabled}/>;
432
+ } else if (selectedFieldConfigId === "group") {
433
+ childComponent =
434
+ <MapPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference}
435
+ collectionEditable={collectionEditable}
436
+ propertyConfigs={propertyConfigs}/>;
437
+ } else if (selectedFieldConfigId === "block") {
438
+ childComponent =
439
+ <BlockPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference}
440
+ collectionEditable={collectionEditable}
441
+ propertyConfigs={propertyConfigs}/>;
442
+ } else if (selectedFieldConfigId === "reference") {
443
+ childComponent =
444
+ <ReferencePropertyField showErrors={showErrors}
445
+ existing={existing}
446
+ multiple={false}
447
+ disabled={disabled}/>;
448
+ } else if (selectedFieldConfigId === "date_time") {
449
+ childComponent = <DateTimePropertyField disabled={disabled}/>;
450
+ } else if (selectedFieldConfigId === "multi_references") {
451
+ childComponent =
452
+ <ReferencePropertyField showErrors={showErrors}
453
+ existing={existing}
454
+ multiple={true}
455
+ disabled={disabled}/>;
456
+ } else if (selectedFieldConfigId === "repeat") {
457
+ childComponent =
458
+ <RepeatPropertyField showErrors={showErrors}
459
+ existing={existing}
460
+ getData={getData}
461
+ allowDataInference={allowDataInference}
462
+ disabled={disabled}
463
+ collectionEditable={collectionEditable}
464
+ propertyConfigs={propertyConfigs}/>;
465
+ } else if (selectedFieldConfigId === "key_value") {
466
+ childComponent =
467
+ <KeyValuePropertyField disabled={disabled}/>;
468
+ } else {
469
+ childComponent = null;
470
+ }
471
+
472
+ return (
473
+ <>
474
+ {disabled && <InfoLabel mode={"warn"}>
475
+ <Typography>This property can&apos;t be edited</Typography>
476
+ <Typography variant={"caption"}>
477
+ You may not have permission to
478
+ edit it or it is defined in code with no <code>editable</code> flag
479
+ </Typography>
480
+ </InfoLabel>}
481
+
482
+ <div className="flex mt-2 justify-between">
483
+ <div className={"w-full flex flex-col gap-2"}>
484
+ <Select
485
+ // className={"w-full"}
486
+ error={Boolean(selectedWidgetError)}
487
+ value={selectedFieldConfigId ?? ""}
488
+ placeholder={"Select a property widget"}
489
+ open={selectOpen}
490
+ onOpenChange={setSelectOpen}
491
+ position={"item-aligned"}
492
+ disabled={disabled}
493
+ renderValue={(value) => {
494
+ if (!value) {
495
+ return <em>Select a property
496
+ widget</em>;
497
+ }
498
+ const key = value as PropertyConfigId;
499
+ const propertyConfig = DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key];
500
+ const baseProperty = propertyConfig.property;
501
+ const baseFieldConfig = baseProperty && !isPropertyBuilder(baseProperty) ? getFieldConfig(baseProperty, propertyConfigs) : undefined;
502
+ const optionDisabled = isPropertyBuilder(baseProperty) || (existing && baseProperty.dataType !== values?.dataType);
503
+ const computedFieldConfig = baseFieldConfig ? mergeDeep(baseFieldConfig, propertyConfig) : propertyConfig;
504
+ return <div
505
+ onClick={(e) => {
506
+ if (optionDisabled) {
507
+ e.stopPropagation();
508
+ e.preventDefault();
509
+ }
510
+ }}
511
+ className={cn(
512
+ "flex items-center",
513
+ optionDisabled ? "w-full pointer-events-none opacity-50" : "")}>
514
+ <div className={"mr-8"}>
515
+ <PropertyConfigBadge propertyConfig={computedFieldConfig}/>
516
+ </div>
517
+ <div className={"flex flex-col items-start text-base text-left"}>
518
+ <div>{computedFieldConfig.name}</div>
519
+ <Typography variant={"caption"}
520
+ color={"disabled"}>
521
+ {optionDisabled ? "You can only switch to widgets that use the same data type" : computedFieldConfig.description}
522
+ </Typography>
523
+ </div>
524
+ </div>
525
+ }}
526
+ onValueChange={(value) => {
527
+ onWidgetSelectChanged(value as PropertyConfigId);
528
+ }}>
529
+ {displayedWidgets.map(([key, propertyConfig]) => {
530
+ const baseProperty = propertyConfig.property;
531
+ const optionDisabled = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== values?.dataType;
532
+ return <PropertySelectItem
533
+ key={key}
534
+ value={key}
535
+ optionDisabled={optionDisabled}
536
+ propertyConfig={propertyConfig}
537
+ existing={existing}/>;
538
+ })}
539
+ </Select>
540
+
541
+ {selectedWidgetError &&
542
+ <Typography variant="caption"
543
+ className={"ml-3.5"}
544
+ color={"error"}>Required</Typography>}
545
+
546
+ {/*<Typography variant="caption" className={"ml-3.5"}>Define your own custom properties and*/}
547
+ {/* components</Typography>*/}
548
+
549
+ </div>
550
+
551
+ {onDelete && values?.id &&
552
+ <IconButton
553
+ variant={"ghost"}
554
+ className="m-4"
555
+ disabled={disabled}
556
+ onClick={() => setDeleteDialogOpen(true)}>
557
+ <DeleteIcon/>
558
+ </IconButton>}
559
+ </div>
560
+
561
+ <div className={"grid grid-cols-12 gap-y-12 mt-8 mb-8"}>
562
+ {includeIdAndTitle &&
563
+ <CommonPropertyFields showErrors={showErrors}
564
+ disabledId={existing}
565
+ isNewProperty={!existing}
566
+ disabled={disabled}
567
+ autoUpdateId={autoUpdateId}
568
+ ref={nameFieldRef}/>}
569
+
570
+ {childComponent}
571
+
572
+ <div className={"col-span-12"}>
573
+ <AdvancedPropertyValidation disabled={disabled}/>
574
+ </div>
575
+ </div>
576
+
577
+ {onDelete &&
578
+ <DeleteConfirmationDialog open={deleteDialogOpen}
579
+ onAccept={() => onDelete(values?.id, propertyNamespace)}
580
+ onCancel={() => setDeleteDialogOpen(false)}
581
+ title={<div>Delete this property?</div>}
582
+ body={
583
+ <div> This will <b>not delete any
584
+ data</b>, only modify the
585
+ collection.</div>
586
+ }/>}
587
+
588
+ </>
589
+ );
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
+ }