@firecms/collection_editor 3.0.0-alpha.9 → 3.0.0-beta.10

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 (149) hide show
  1. package/LICENSE +114 -21
  2. package/dist/ConfigControllerProvider.d.ts +13 -3
  3. package/dist/index.d.ts +4 -2
  4. package/dist/index.es.js +5587 -4976
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/index.umd.js +6829 -1
  7. package/dist/index.umd.js.map +1 -1
  8. package/dist/types/collection_editor_controller.d.ts +36 -8
  9. package/dist/types/collection_inference.d.ts +1 -1
  10. package/dist/types/config_controller.d.ts +33 -6
  11. package/dist/types/persisted_collection.d.ts +4 -2
  12. package/dist/ui/CollectionViewHeaderAction.d.ts +11 -0
  13. package/dist/{components → ui}/EditorCollectionAction.d.ts +1 -1
  14. package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
  15. package/dist/ui/MissingReferenceWidget.d.ts +3 -0
  16. package/dist/ui/NewCollectionButton.d.ts +1 -0
  17. package/dist/ui/PropertyAddColumnComponent.d.ts +8 -0
  18. package/dist/{components → ui}/collection_editor/CollectionDetailsForm.d.ts +3 -2
  19. package/dist/{components → ui}/collection_editor/CollectionEditorDialog.d.ts +15 -11
  20. package/dist/{components → ui}/collection_editor/CollectionEditorWelcomeView.d.ts +3 -3
  21. package/dist/{components → ui}/collection_editor/CollectionPropertiesEditorForm.d.ts +8 -6
  22. package/dist/{components → ui}/collection_editor/CollectionYupValidation.d.ts +3 -0
  23. package/dist/ui/collection_editor/EntityCustomViewsSelectDialog.d.ts +4 -0
  24. package/dist/{components → ui}/collection_editor/EnumForm.d.ts +1 -2
  25. package/dist/ui/collection_editor/GetCodeDialog.d.ts +5 -0
  26. package/dist/{components → ui}/collection_editor/PropertyEditView.d.ts +21 -11
  27. package/dist/{components → ui}/collection_editor/PropertyFieldPreview.d.ts +4 -3
  28. package/dist/{components → ui}/collection_editor/PropertyTree.d.ts +11 -7
  29. package/dist/{components → ui}/collection_editor/SubcollectionsEditTab.d.ts +3 -3
  30. package/dist/ui/collection_editor/SwitchControl.d.ts +8 -0
  31. package/dist/{components → ui}/collection_editor/import/CollectionEditorImportDataPreview.d.ts +1 -1
  32. package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +14 -0
  33. package/dist/{components → ui}/collection_editor/import/clean_import_data.d.ts +1 -1
  34. package/dist/{components → ui}/collection_editor/properties/BlockPropertyField.d.ts +4 -1
  35. package/dist/{components → ui}/collection_editor/properties/CommonPropertyFields.d.ts +1 -1
  36. package/dist/{components → ui}/collection_editor/properties/MapPropertyField.d.ts +4 -1
  37. package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
  38. package/dist/{components → ui}/collection_editor/properties/RepeatPropertyField.d.ts +4 -1
  39. package/dist/{components → ui}/collection_editor/properties/StringPropertyField.d.ts +1 -1
  40. package/dist/ui/collection_editor/properties/UrlPropertyField.d.ts +4 -0
  41. package/dist/ui/collection_editor/templates/blog_template.d.ts +2 -0
  42. package/dist/ui/collection_editor/templates/pages_template.d.ts +2 -0
  43. package/dist/ui/collection_editor/templates/products_template.d.ts +2 -0
  44. package/dist/ui/collection_editor/templates/users_template.d.ts +2 -0
  45. package/dist/{components → ui}/collection_editor/util.d.ts +1 -0
  46. package/dist/ui/collection_editor/utils/strings.d.ts +1 -0
  47. package/dist/ui/collection_editor/utils/supported_fields.d.ts +3 -0
  48. package/dist/ui/collection_editor/utils/update_property_for_widget.d.ts +2 -0
  49. package/dist/useCollectionEditorPlugin.d.ts +17 -6
  50. package/dist/utils/collections.d.ts +6 -0
  51. package/dist/utils/entities.d.ts +3 -4
  52. package/package.json +35 -37
  53. package/src/ConfigControllerProvider.tsx +350 -0
  54. package/src/index.ts +36 -0
  55. package/src/types/collection_editor_controller.tsx +53 -0
  56. package/src/types/collection_inference.ts +3 -0
  57. package/src/types/config_controller.tsx +60 -0
  58. package/src/types/config_permissions.ts +20 -0
  59. package/src/types/persisted_collection.ts +9 -0
  60. package/src/ui/CollectionViewHeaderAction.tsx +48 -0
  61. package/src/ui/EditorCollectionAction.tsx +56 -0
  62. package/src/ui/EditorCollectionActionStart.tsx +88 -0
  63. package/src/ui/HomePageEditorCollectionAction.tsx +89 -0
  64. package/src/ui/MissingReferenceWidget.tsx +37 -0
  65. package/src/ui/NewCollectionButton.tsx +18 -0
  66. package/src/ui/NewCollectionCard.tsx +48 -0
  67. package/src/ui/PropertyAddColumnComponent.tsx +47 -0
  68. package/src/ui/collection_editor/CollectionDetailsForm.tsx +426 -0
  69. package/src/ui/collection_editor/CollectionEditorDialog.tsx +826 -0
  70. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +214 -0
  71. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +513 -0
  72. package/src/ui/collection_editor/CollectionYupValidation.tsx +7 -0
  73. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +37 -0
  74. package/src/ui/collection_editor/EnumForm.tsx +357 -0
  75. package/src/ui/collection_editor/GetCodeDialog.tsx +122 -0
  76. package/src/ui/collection_editor/PropertyEditView.tsx +789 -0
  77. package/src/ui/collection_editor/PropertyFieldPreview.tsx +204 -0
  78. package/src/ui/collection_editor/PropertyTree.tsx +254 -0
  79. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +269 -0
  80. package/src/ui/collection_editor/SwitchControl.tsx +39 -0
  81. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +47 -0
  82. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +53 -0
  83. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +299 -0
  84. package/src/ui/collection_editor/import/clean_import_data.ts +53 -0
  85. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +144 -0
  86. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +40 -0
  87. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +110 -0
  88. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +89 -0
  89. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +114 -0
  90. package/src/ui/collection_editor/properties/KeyValuePropertyField.tsx +20 -0
  91. package/src/ui/collection_editor/properties/MapPropertyField.tsx +150 -0
  92. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
  93. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +38 -0
  94. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +160 -0
  95. package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +108 -0
  96. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +215 -0
  97. package/src/ui/collection_editor/properties/StringPropertyField.tsx +70 -0
  98. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +89 -0
  99. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +45 -0
  100. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +50 -0
  101. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +61 -0
  102. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +115 -0
  103. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +150 -0
  104. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +28 -0
  105. package/src/ui/collection_editor/templates/blog_template.ts +115 -0
  106. package/src/ui/collection_editor/templates/pages_template.ts +183 -0
  107. package/src/ui/collection_editor/templates/products_template.ts +88 -0
  108. package/src/ui/collection_editor/templates/users_template.ts +42 -0
  109. package/src/ui/collection_editor/util.ts +28 -0
  110. package/src/ui/collection_editor/utils/strings.ts +9 -0
  111. package/src/ui/collection_editor/utils/supported_fields.tsx +29 -0
  112. package/src/ui/collection_editor/utils/update_property_for_widget.ts +271 -0
  113. package/src/ui/collection_editor/utils/useTraceUpdate.tsx +23 -0
  114. package/src/useCollectionEditorController.tsx +9 -0
  115. package/src/useCollectionEditorPlugin.tsx +164 -0
  116. package/src/useCollectionsConfigController.tsx +9 -0
  117. package/src/utils/arrays.ts +3 -0
  118. package/src/utils/collections.ts +30 -0
  119. package/src/utils/entities.ts +38 -0
  120. package/src/vite-env.d.ts +1 -0
  121. package/dist/components/collection_editor/PropertySelectItem.d.ts +0 -8
  122. package/dist/components/collection_editor/SelectIcons.d.ts +0 -6
  123. package/dist/components/collection_editor/import/CollectionEditorImportMapping.d.ts +0 -4
  124. package/dist/components/collection_editor/properties/FieldHelperView.d.ts +0 -4
  125. package/dist/components/collection_editor/templates/blog_template.d.ts +0 -10
  126. package/dist/components/collection_editor/templates/products_template.d.ts +0 -12
  127. package/dist/components/collection_editor/templates/users_template.d.ts +0 -7
  128. package/dist/components/collection_editor/utils/supported_fields.d.ts +0 -3
  129. package/dist/components/collection_editor/utils/update_property_for_widget.d.ts +0 -3
  130. package/dist/types/editable_properties.d.ts +0 -10
  131. package/dist/utils/icons.d.ts +0 -2
  132. package/dist/utils/synonyms.d.ts +0 -1951
  133. /package/dist/{components → ui}/HomePageEditorCollectionAction.d.ts +0 -0
  134. /package/dist/{components → ui}/NewCollectionCard.d.ts +0 -0
  135. /package/dist/{components → ui}/collection_editor/UnsavedChangesDialog.d.ts +0 -0
  136. /package/dist/{components → ui}/collection_editor/properties/BooleanPropertyField.d.ts +0 -0
  137. /package/dist/{components → ui}/collection_editor/properties/DateTimePropertyField.d.ts +0 -0
  138. /package/dist/{components → ui}/collection_editor/properties/EnumPropertyField.d.ts +0 -0
  139. /package/dist/{components → ui}/collection_editor/properties/KeyValuePropertyField.d.ts +0 -0
  140. /package/dist/{components → ui}/collection_editor/properties/NumberPropertyField.d.ts +0 -0
  141. /package/dist/{components → ui}/collection_editor/properties/ReferencePropertyField.d.ts +0 -0
  142. /package/dist/{components → ui}/collection_editor/properties/StoragePropertyField.d.ts +0 -0
  143. /package/dist/{components → ui}/collection_editor/properties/advanced/AdvancedPropertyValidation.d.ts +0 -0
  144. /package/dist/{components → ui}/collection_editor/properties/validation/ArrayPropertyValidation.d.ts +0 -0
  145. /package/dist/{components → ui}/collection_editor/properties/validation/GeneralPropertyValidation.d.ts +0 -0
  146. /package/dist/{components → ui}/collection_editor/properties/validation/NumberPropertyValidation.d.ts +0 -0
  147. /package/dist/{components → ui}/collection_editor/properties/validation/StringPropertyValidation.d.ts +0 -0
  148. /package/dist/{components → ui}/collection_editor/properties/validation/ValidationPanel.d.ts +0 -0
  149. /package/dist/{components → ui}/collection_editor/utils/useTraceUpdate.d.ts +0 -0
@@ -0,0 +1,789 @@
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
+ ConfirmationDialog,
7
+ DEFAULT_FIELD_CONFIGS,
8
+ getFieldConfig,
9
+ getFieldId,
10
+ isPropertyBuilder,
11
+ isValidRegExp,
12
+ mergeDeep,
13
+ Property,
14
+ PropertyConfig,
15
+ PropertyConfigBadge,
16
+ PropertyConfigId,
17
+ } from "@firecms/core";
18
+ import {
19
+ Button,
20
+ Card,
21
+ cls,
22
+ DeleteIcon,
23
+ Dialog,
24
+ DialogActions,
25
+ DialogContent,
26
+ DialogTitle,
27
+ fieldBackgroundDisabledMixin,
28
+ fieldBackgroundHoverMixin,
29
+ fieldBackgroundMixin,
30
+ IconButton,
31
+ InfoLabel,
32
+ Tooltip,
33
+ Typography,
34
+ WarningOffIcon
35
+ } from "@firecms/ui";
36
+ import { EnumPropertyField } from "./properties/EnumPropertyField";
37
+ import { StoragePropertyField } from "./properties/StoragePropertyField";
38
+ import { MapPropertyField } from "./properties/MapPropertyField";
39
+ import { RepeatPropertyField } from "./properties/RepeatPropertyField";
40
+ import { CommonPropertyFields } from "./properties/CommonPropertyFields";
41
+ import { StringPropertyField } from "./properties/StringPropertyField";
42
+ import { BooleanPropertyField } from "./properties/BooleanPropertyField";
43
+ import { BlockPropertyField } from "./properties/BlockPropertyField";
44
+ import { NumberPropertyField } from "./properties/NumberPropertyField";
45
+ import { ReferencePropertyField } from "./properties/ReferencePropertyField";
46
+ import { DateTimePropertyField } from "./properties/DateTimePropertyField";
47
+ import { AdvancedPropertyValidation } from "./properties/advanced/AdvancedPropertyValidation";
48
+ import { editableProperty } from "../../utils/entities";
49
+ import { KeyValuePropertyField } from "./properties/KeyValuePropertyField";
50
+ import { updatePropertyFromWidget } from "./utils/update_property_for_widget";
51
+ import { UrlPropertyField } from "./properties/UrlPropertyField";
52
+ import { supportedFields } from "./utils/supported_fields";
53
+ import { MarkdownPropertyField } from "./properties/MarkdownPropertyField";
54
+
55
+ export type PropertyWithId = Property & {
56
+ id?: string
57
+ };
58
+
59
+ export type OnPropertyChangedParams = {
60
+ id?: string,
61
+ property: Property,
62
+ namespace?: string,
63
+ previousId?: string
64
+ };
65
+
66
+ export type PropertyFormProps = {
67
+ includeIdAndName?: boolean;
68
+ existingProperty: boolean;
69
+ autoUpdateId?: boolean;
70
+ autoOpenTypeSelect: boolean;
71
+ inArray: boolean;
72
+ propertyKey?: string;
73
+ propertyNamespace?: string;
74
+ property?: Property;
75
+ onPropertyChanged?: (params: OnPropertyChangedParams) => void;
76
+ onPropertyChangedImmediate?: boolean;
77
+ onDismiss?: () => void;
78
+ onDelete?: (id?: string, namespace?: string) => void;
79
+ onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
80
+ initialErrors?: Record<string, any>;
81
+ existingPropertyKeys?: string[];
82
+ forceShowErrors?: boolean;
83
+ allowDataInference: boolean;
84
+ getData?: () => Promise<object[]>;
85
+ getController?: (formex: FormexController<PropertyWithId>) => void;
86
+ propertyConfigs: Record<string, PropertyConfig>;
87
+ collectionEditable: boolean;
88
+ };
89
+
90
+ export const PropertyForm = React.memo(
91
+ function PropertyForm(props: PropertyFormProps) {
92
+
93
+ const {
94
+ includeIdAndName = true,
95
+ autoOpenTypeSelect,
96
+ existingProperty,
97
+ autoUpdateId,
98
+ inArray,
99
+ propertyKey,
100
+ existingPropertyKeys,
101
+ propertyNamespace,
102
+ property,
103
+ onPropertyChanged,
104
+ onPropertyChangedImmediate = true,
105
+ onDismiss,
106
+ onDelete,
107
+ onError,
108
+ initialErrors,
109
+ forceShowErrors,
110
+ allowDataInference,
111
+ getController,
112
+ getData,
113
+ propertyConfigs,
114
+ collectionEditable
115
+ } = props;
116
+
117
+ const initialValue: PropertyWithId = {
118
+ id: "",
119
+ name: ""
120
+ } as PropertyWithId;
121
+
122
+ const disabled = (Boolean(property && !editableProperty(property)) && !collectionEditable);
123
+
124
+ const lastSubmittedProperty = useRef<OnPropertyChangedParams | undefined>(property ? {
125
+ id: propertyKey,
126
+ previousId: propertyKey,
127
+ property
128
+ } : undefined);
129
+
130
+ const doOnPropertyChanged = ({
131
+ id,
132
+ property
133
+ }: OnPropertyChangedParams) => {
134
+ const params = {
135
+ id,
136
+ previousId: lastSubmittedProperty.current?.id,
137
+ property,
138
+ namespace: propertyNamespace
139
+ };
140
+ lastSubmittedProperty.current = params;
141
+ onPropertyChanged?.(params);
142
+ };
143
+
144
+ const formexController = useCreateFormex<PropertyWithId>({
145
+ initialValues: property
146
+ ? { id: propertyKey, ...property } as PropertyWithId
147
+ : initialValue,
148
+ initialErrors,
149
+ validateOnChange: true,
150
+ validateOnInitialRender: true,
151
+ onSubmit: (newPropertyWithId, controller) => {
152
+ console.debug("onSubmit", newPropertyWithId);
153
+ const {
154
+ id,
155
+ ...property
156
+ } = newPropertyWithId;
157
+ doOnPropertyChanged({
158
+ id,
159
+ property: {
160
+ ...property,
161
+ editable: property.editable ?? true
162
+ }
163
+ });
164
+ if (!existingProperty)
165
+ controller.resetForm({ values: initialValue });
166
+ },
167
+ validation: (values) => {
168
+ const errors: Record<string, any> = {};
169
+ if (includeIdAndName) {
170
+ if (!values.name) {
171
+ errors.name = "Required";
172
+ } else {
173
+ const nameError = validateName(values.name);
174
+ if (nameError)
175
+ errors.name = nameError;
176
+ }
177
+ if (!values.id) {
178
+ errors.id = "Required";
179
+ } else {
180
+ const idError = validateId(values.id, existingPropertyKeys);
181
+ if (idError)
182
+ errors.id = idError;
183
+ }
184
+ }
185
+
186
+ if (values.dataType === "string") {
187
+ if (values.validation?.matches && !isValidRegExp(values.validation?.matches.toString())) {
188
+ errors.validation = {
189
+ matches: "Invalid regular expression"
190
+ }
191
+ }
192
+ }
193
+ if (values.dataType === "reference" && !values.path) {
194
+ errors.path = "You must specify a target collection for the field";
195
+ }
196
+ if (values.propertyConfig === "repeat") {
197
+ if (!(values as any).of) {
198
+ errors.of = "You need to specify a repeat field";
199
+ }
200
+ }
201
+ if (values.propertyConfig === "block") {
202
+ if (!(values as any).oneOf) {
203
+ errors.oneOf = "You need to specify the properties of this block";
204
+ }
205
+ }
206
+ return errors;
207
+ }
208
+ });
209
+
210
+ useEffect(() => {
211
+ getController?.(formexController);
212
+ }, [formexController, getController]);
213
+
214
+ return <Formex value={formexController}>
215
+ <PropertyEditFormFields
216
+ onPropertyChanged={onPropertyChangedImmediate
217
+ ? doOnPropertyChanged
218
+ : undefined}
219
+ onDelete={onDelete}
220
+ includeIdAndTitle={includeIdAndName}
221
+ propertyNamespace={propertyNamespace}
222
+ onError={onError}
223
+ onDismiss={onDismiss}
224
+ showErrors={forceShowErrors || formexController.submitCount > 0}
225
+ existing={existingProperty}
226
+ autoUpdateId={autoUpdateId}
227
+ inArray={inArray}
228
+ autoOpenTypeSelect={autoOpenTypeSelect}
229
+ disabled={disabled}
230
+ getData={getData}
231
+ allowDataInference={allowDataInference}
232
+ propertyConfigs={propertyConfigs}
233
+ collectionEditable={collectionEditable}
234
+ {...formexController}/>
235
+ </Formex>;
236
+ }, (a, b) =>
237
+ a.getData === b.getData &&
238
+ a.propertyKey === b.propertyKey &&
239
+ a.propertyNamespace === b.propertyNamespace &&
240
+ a.includeIdAndName === b.includeIdAndName &&
241
+ a.autoOpenTypeSelect === b.autoOpenTypeSelect &&
242
+ a.autoUpdateId === b.autoUpdateId &&
243
+ a.existingPropertyKeys === b.existingPropertyKeys &&
244
+ a.existingProperty === b.existingProperty
245
+ );
246
+
247
+ export function PropertyFormDialog({
248
+ open,
249
+ onCancel,
250
+ onOkClicked,
251
+ onPropertyChanged,
252
+ getData,
253
+ collectionEditable,
254
+ ...formProps
255
+ }: PropertyFormProps & {
256
+ open?: boolean;
257
+ onOkClicked?: () => void;
258
+ onCancel?: () => void;
259
+ }) {
260
+ const formexRef = useRef<FormexController<PropertyWithId>>();
261
+ const getController = (helpers: FormexController<PropertyWithId>) => {
262
+ formexRef.current = helpers;
263
+ };
264
+
265
+ return <Dialog
266
+ open={open ?? false}
267
+ maxWidth={"xl"}
268
+ fullWidth={true}
269
+ >
270
+ <form noValidate={true}
271
+ autoComplete={"off"}
272
+ onSubmit={(e) => {
273
+ e.preventDefault();
274
+ e.stopPropagation();
275
+ formexRef.current?.handleSubmit(e)
276
+ }}>
277
+ <DialogContent>
278
+ <PropertyForm {...formProps}
279
+ onDismiss={onCancel}
280
+ onPropertyChanged={(params) => {
281
+ onPropertyChanged?.(params);
282
+ onOkClicked?.();
283
+ }}
284
+ collectionEditable={collectionEditable}
285
+ onPropertyChangedImmediate={false}
286
+ getController={getController}
287
+ getData={getData}
288
+ />
289
+ </DialogContent>
290
+
291
+ <DialogActions>
292
+
293
+ {onCancel && <Button
294
+ variant={"text"}
295
+ onClick={() => {
296
+ onCancel();
297
+ formexRef.current?.resetForm();
298
+ }}>
299
+ Cancel
300
+ </Button>}
301
+
302
+ <Button variant="outlined"
303
+ type={"submit"}
304
+ color="primary">
305
+ Ok
306
+ </Button>
307
+ </DialogActions>
308
+ </form>
309
+ </Dialog>;
310
+
311
+ }
312
+
313
+ function PropertyEditFormFields({
314
+ values,
315
+ errors,
316
+ setValues,
317
+ existing,
318
+ autoUpdateId = false,
319
+ autoOpenTypeSelect,
320
+ includeIdAndTitle,
321
+ onPropertyChanged,
322
+ onDelete,
323
+ propertyNamespace,
324
+ onDismiss,
325
+ onError,
326
+ showErrors,
327
+ disabled,
328
+ inArray,
329
+ getData,
330
+ allowDataInference,
331
+ propertyConfigs,
332
+ collectionEditable
333
+ }: {
334
+ includeIdAndTitle?: boolean;
335
+ existing: boolean;
336
+ autoUpdateId?: boolean;
337
+ autoOpenTypeSelect: boolean;
338
+ propertyNamespace?: string;
339
+ onDismiss?: () => void;
340
+ onPropertyChanged?: (params: OnPropertyChangedParams) => void;
341
+ onDelete?: (id?: string, namespace?: string) => void;
342
+ onError?: (id: string, namespace?: string, error?: Record<string, any>) => void;
343
+ showErrors: boolean;
344
+ inArray: boolean;
345
+ disabled: boolean;
346
+ getData?: () => Promise<object[]>;
347
+ allowDataInference: boolean;
348
+ propertyConfigs: Record<string, PropertyConfig>;
349
+ collectionEditable: boolean;
350
+ } & FormexController<PropertyWithId>) {
351
+
352
+ const [selectOpen, setSelectOpen] = useState(autoOpenTypeSelect);
353
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
354
+ const [selectedFieldConfigId, setSelectedFieldConfigId] = useState<string | undefined>(values?.dataType ? getFieldId(values) : undefined);
355
+
356
+ const deferredValues = useDeferredValue(values);
357
+ const nameFieldRef = useRef<HTMLInputElement>(null);
358
+
359
+ const lastSubmittedProperty = useRef<object>(values);
360
+
361
+ const selectedWidgetError = showErrors && getIn(errors, "selectedWidget");
362
+
363
+ useEffect(() => {
364
+ if (onPropertyChanged) {
365
+ if ((!includeIdAndTitle || deferredValues.id)) {
366
+ const {
367
+ id,
368
+ ...property
369
+ } = deferredValues;
370
+ if (!equal(deferredValues, lastSubmittedProperty.current)) {
371
+ onPropertyChanged({
372
+ id,
373
+ property,
374
+ namespace: propertyNamespace
375
+ });
376
+ lastSubmittedProperty.current = deferredValues;
377
+ }
378
+ }
379
+ }
380
+ }, [deferredValues, includeIdAndTitle, propertyNamespace]);
381
+
382
+ useEffect(() => {
383
+ if (values?.id && onError) {
384
+ onError(values?.id, propertyNamespace, errors);
385
+ }
386
+ }, [errors, propertyNamespace, values?.id]);
387
+
388
+ const onWidgetSelectChanged = (newSelectedWidgetId: PropertyConfigId) => {
389
+ setSelectedFieldConfigId(newSelectedWidgetId);
390
+ setValues(updatePropertyFromWidget(values, newSelectedWidgetId, propertyConfigs));
391
+ // Ugly hack to autofocus the name field
392
+ setTimeout(() => {
393
+ nameFieldRef.current?.focus();
394
+ }, 0);
395
+ };
396
+
397
+ let childComponent;
398
+ if (selectedFieldConfigId === "text_field" ||
399
+ selectedFieldConfigId === "multiline" ||
400
+ selectedFieldConfigId === "email") {
401
+ childComponent =
402
+ <StringPropertyField widgetId={selectedFieldConfigId}
403
+ disabled={disabled}
404
+ showErrors={showErrors}/>;
405
+ } else if (selectedFieldConfigId === "url") {
406
+ childComponent =
407
+ <UrlPropertyField disabled={disabled}
408
+ showErrors={showErrors}/>;
409
+ } else if (selectedFieldConfigId === "markdown") {
410
+ childComponent =
411
+ <MarkdownPropertyField disabled={disabled}
412
+ showErrors={showErrors}/>;
413
+ } else if (selectedFieldConfigId === "select" ||
414
+ selectedFieldConfigId === "number_select") {
415
+ childComponent = <EnumPropertyField
416
+ multiselect={false}
417
+ allowDataInference={allowDataInference}
418
+ updateIds={!existing}
419
+ disabled={disabled}
420
+ getData={getData}
421
+ showErrors={showErrors}/>;
422
+ } else if (selectedFieldConfigId === "multi_select" ||
423
+ selectedFieldConfigId === "multi_number_select") {
424
+ childComponent = <EnumPropertyField
425
+ multiselect={true}
426
+ updateIds={!existing}
427
+ disabled={disabled}
428
+ allowDataInference={allowDataInference}
429
+ getData={getData}
430
+ showErrors={showErrors}/>;
431
+ } else if (selectedFieldConfigId === "file_upload") {
432
+ childComponent =
433
+ <StoragePropertyField existing={existing}
434
+ multiple={false}
435
+ disabled={disabled}/>;
436
+ } else if (selectedFieldConfigId === "multi_file_upload") {
437
+ childComponent =
438
+ <StoragePropertyField existing={existing}
439
+ multiple={true}
440
+ disabled={disabled}/>;
441
+ } else if (selectedFieldConfigId === "switch") {
442
+ childComponent = <BooleanPropertyField disabled={disabled}/>;
443
+ } else if (selectedFieldConfigId === "number_input") {
444
+ childComponent = <NumberPropertyField disabled={disabled}/>;
445
+ } else if (selectedFieldConfigId === "group") {
446
+ childComponent =
447
+ <MapPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference}
448
+ collectionEditable={collectionEditable}
449
+ propertyConfigs={propertyConfigs}/>;
450
+ } else if (selectedFieldConfigId === "block") {
451
+ childComponent =
452
+ <BlockPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference}
453
+ collectionEditable={collectionEditable}
454
+ propertyConfigs={propertyConfigs}/>;
455
+ } else if (selectedFieldConfigId === "reference") {
456
+ childComponent =
457
+ <ReferencePropertyField showErrors={showErrors}
458
+ existing={existing}
459
+ multiple={false}
460
+ disabled={disabled}/>;
461
+ } else if (selectedFieldConfigId === "date_time") {
462
+ childComponent = <DateTimePropertyField disabled={disabled}/>;
463
+ } else if (selectedFieldConfigId === "multi_references") {
464
+ childComponent =
465
+ <ReferencePropertyField showErrors={showErrors}
466
+ existing={existing}
467
+ multiple={true}
468
+ disabled={disabled}/>;
469
+ } else if (selectedFieldConfigId === "repeat") {
470
+ childComponent =
471
+ <RepeatPropertyField showErrors={showErrors}
472
+ existing={existing}
473
+ getData={getData}
474
+ allowDataInference={allowDataInference}
475
+ disabled={disabled}
476
+ collectionEditable={collectionEditable}
477
+ propertyConfigs={propertyConfigs}/>;
478
+ } else if (selectedFieldConfigId === "key_value") {
479
+ childComponent =
480
+ <KeyValuePropertyField disabled={disabled}/>;
481
+ } else {
482
+ childComponent = null;
483
+ }
484
+
485
+ return (
486
+ <>
487
+ {disabled && <InfoLabel mode={"warn"}>
488
+ <Typography>This property can&apos;t be edited</Typography>
489
+ <Typography variant={"caption"}>
490
+ You may not have permission to
491
+ edit it or it is defined in code with no <code>editable</code> flag
492
+ </Typography>
493
+ </InfoLabel>}
494
+
495
+ <div className="flex mt-2 justify-between">
496
+ <div className={"w-full flex flex-col gap-2"}>
497
+ <WidgetSelectView
498
+ initialProperty={values}
499
+ value={selectedFieldConfigId as PropertyConfigId}
500
+ onValueChange={(value) => onWidgetSelectChanged(value as PropertyConfigId)}
501
+ open={selectOpen}
502
+ onOpenChange={(open, hasValue) => {
503
+ if (!hasValue) {
504
+ onDismiss?.();
505
+ }
506
+ setSelectOpen(open);
507
+ }}
508
+ disabled={disabled}
509
+ showError={Boolean(selectedWidgetError)}
510
+ existing={existing}
511
+ propertyConfigs={propertyConfigs}
512
+ inArray={inArray}/>
513
+
514
+ {selectedWidgetError &&
515
+ <Typography variant="caption"
516
+ className={"ml-3.5"}
517
+ color={"error"}>Required</Typography>}
518
+
519
+ {/*<Typography variant="caption" className={"ml-3.5"}>Define your own custom properties and*/}
520
+ {/* components</Typography>*/}
521
+
522
+ </div>
523
+
524
+ {onDelete && values?.id &&
525
+ <IconButton
526
+ variant={"ghost"}
527
+ className="m-4"
528
+ disabled={disabled}
529
+ onClick={() => setDeleteDialogOpen(true)}>
530
+ <DeleteIcon/>
531
+ </IconButton>}
532
+ </div>
533
+
534
+ <div className={"grid grid-cols-12 gap-y-12 mt-8 mb-8"}>
535
+ {includeIdAndTitle &&
536
+ <CommonPropertyFields showErrors={showErrors}
537
+ disabledId={existing}
538
+ isNewProperty={!existing}
539
+ disabled={disabled}
540
+ autoUpdateId={autoUpdateId}
541
+ ref={nameFieldRef}/>}
542
+
543
+ {childComponent}
544
+
545
+ <div className={"col-span-12"}>
546
+ <AdvancedPropertyValidation disabled={disabled}/>
547
+ </div>
548
+ </div>
549
+
550
+ {onDelete &&
551
+ <ConfirmationDialog open={deleteDialogOpen}
552
+ onAccept={() => onDelete(values?.id, propertyNamespace)}
553
+ onCancel={() => setDeleteDialogOpen(false)}
554
+ title={<div>Delete this property?</div>}
555
+ body={
556
+ <div> This will <b>not delete any
557
+ data</b>, only modify the
558
+ collection.</div>
559
+ }/>}
560
+
561
+ </>
562
+ );
563
+ }
564
+
565
+ const idRegEx = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
566
+
567
+ function validateId(value?: string, existingPropertyKeys?: string[]) {
568
+
569
+ let error;
570
+ if (!value) {
571
+ error = "You must specify an id for the field";
572
+ }
573
+ if (value && !value.match(idRegEx)) {
574
+ error = "The id can only contain letters, numbers and underscores (_), and not start with a number";
575
+ }
576
+ if (value && existingPropertyKeys && existingPropertyKeys.includes(value)) {
577
+ error = "There is another field with this ID already";
578
+ }
579
+ return error;
580
+ }
581
+
582
+ function validateName(value: string) {
583
+ let error;
584
+ if (!value) {
585
+ error = "You must specify a title for the field";
586
+ }
587
+ return error;
588
+ }
589
+
590
+ const WIDGET_TYPE_MAP: Record<PropertyConfigId, string> = {
591
+ text_field: "Text",
592
+ multiline: "Text",
593
+ markdown: "Text",
594
+ url: "Text",
595
+ email: "Text",
596
+ switch: "Boolean",
597
+ select: "Select",
598
+ multi_select: "Select",
599
+ number_input: "Number",
600
+ number_select: "Select",
601
+ multi_number_select: "Select",
602
+ file_upload: "File",
603
+ multi_file_upload: "File",
604
+ reference: "Reference",
605
+ multi_references: "Reference",
606
+ date_time: "Date",
607
+ group: "Group",
608
+ key_value: "Group",
609
+ repeat: "Array",
610
+ custom_array: "Array",
611
+ block: "Group"
612
+ };
613
+
614
+ function WidgetSelectView({
615
+ initialProperty,
616
+ value,
617
+ onValueChange,
618
+ open,
619
+ onOpenChange,
620
+ disabled,
621
+ showError,
622
+ existing,
623
+ propertyConfigs,
624
+ inArray
625
+ }: {
626
+ initialProperty?: PropertyWithId,
627
+ value?: PropertyConfigId,
628
+ onValueChange: (value: string) => void,
629
+ showError: boolean,
630
+ open: boolean,
631
+ onOpenChange: (open: boolean, hasValue: boolean) => void,
632
+ disabled: boolean,
633
+ existing: boolean,
634
+ propertyConfigs: Record<string, PropertyConfig>,
635
+ inArray?: boolean
636
+ }) {
637
+
638
+ const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
639
+
640
+ const displayedWidgets = (inArray
641
+ ? allSupportedFields.filter(([_, propertyConfig]) => !isPropertyBuilder(propertyConfig.property) && propertyConfig.property?.dataType !== "array")
642
+ : allSupportedFields)
643
+ .map(([key, propertyConfig]) => ({
644
+ [key]: propertyConfig
645
+ }))
646
+ .reduce((a, b) => {
647
+ return {
648
+ ...a,
649
+ ...b
650
+ }
651
+ }, {});
652
+
653
+ const key = value;
654
+ const propertyConfig = key ? (DEFAULT_FIELD_CONFIGS[key] ?? propertyConfigs[key]) : undefined;
655
+ const baseProperty = propertyConfig?.property;
656
+ const baseFieldConfig = baseProperty && !isPropertyBuilder(baseProperty) ? getFieldConfig(baseProperty, propertyConfigs) : undefined;
657
+ const computedFieldConfig = baseFieldConfig && propertyConfig ? mergeDeep(baseFieldConfig, propertyConfig) : propertyConfig;
658
+
659
+ const groups: string[] = [...new Set(Object.keys(displayedWidgets).map(key => {
660
+ const group = WIDGET_TYPE_MAP[key as PropertyConfigId];
661
+ if (group) {
662
+ return group;
663
+ }
664
+ return "Custom/Other"
665
+ }))];
666
+
667
+ return <>
668
+ <div
669
+ onClick={() => {
670
+ if (!disabled) {
671
+ onOpenChange(!open, Boolean(value));
672
+ }
673
+ }}
674
+ className={cls(
675
+ "select-none rounded-md text-sm p-4",
676
+ fieldBackgroundMixin,
677
+ disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
678
+ "relative flex items-center",
679
+ )}>
680
+ {!value && <em>Select a property widget</em>}
681
+ {value && computedFieldConfig && <div
682
+ className={cls(
683
+ "flex items-center")}>
684
+ <div className={"mr-8"}>
685
+ <PropertyConfigBadge propertyConfig={computedFieldConfig}/>
686
+ </div>
687
+ <div className={"flex flex-col items-start text-base text-left"}>
688
+ <div>{computedFieldConfig.name}</div>
689
+ <Typography variant={"caption"}
690
+ color={"secondary"}>
691
+ {computedFieldConfig.description}
692
+ </Typography>
693
+ </div>
694
+ </div>}
695
+ </div>
696
+ <Dialog open={open}
697
+ onOpenChange={(open) => onOpenChange(open, Boolean(value))}
698
+ maxWidth={"4xl"}>
699
+ <DialogTitle>
700
+ Select a property widget
701
+ </DialogTitle>
702
+ <DialogContent>
703
+ <div>
704
+ {groups.map(group => {
705
+ return <div key={group} className={"mt-4"}>
706
+ <Typography variant={"label"}>{group}</Typography>
707
+ <div className={"grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2 mt-4"}>
708
+ {Object.entries(displayedWidgets).map(([key, propertyConfig]) => {
709
+ const groupKey = WIDGET_TYPE_MAP[key as PropertyConfigId];
710
+ if (groupKey === group) {
711
+ return <WidgetSelectViewItem
712
+ key={key}
713
+ initialProperty={initialProperty}
714
+ onClick={() => {
715
+ onValueChange(key);
716
+ onOpenChange(false, true);
717
+ }}
718
+ propertyConfig={propertyConfig}
719
+ existing={existing}/>;
720
+ }
721
+ return null;
722
+ })}
723
+ </div>
724
+ </div>;
725
+ })}
726
+ {/*{displayedWidgets.map(([key, propertyConfig]) => {*/}
727
+ {/* return <WidgetSelectViewItem*/}
728
+ {/* key={key}*/}
729
+ {/* initialProperty={initialProperty}*/}
730
+ {/* onClick={() => {*/}
731
+ {/* onValueChange(key);*/}
732
+ {/* onOpenChange(false);*/}
733
+ {/* }}*/}
734
+ {/* propertyConfig={propertyConfig}*/}
735
+ {/* existing={existing}/>;*/}
736
+ {/*})}*/}
737
+ </div>
738
+ </DialogContent>
739
+ </Dialog>
740
+ </>;
741
+ }
742
+
743
+ export interface PropertySelectItemProps {
744
+ onClick?: () => void;
745
+ initialProperty?: PropertyWithId;
746
+ propertyConfig: PropertyConfig;
747
+ existing: boolean;
748
+ }
749
+
750
+ export function WidgetSelectViewItem({
751
+ onClick,
752
+ initialProperty,
753
+ // optionDisabled,
754
+ propertyConfig,
755
+ existing
756
+ }: PropertySelectItemProps) {
757
+ const baseProperty = propertyConfig.property;
758
+ const shouldWarnChangingDataType = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== initialProperty?.dataType;
759
+
760
+ return <Card
761
+ onClick={onClick}
762
+ className={"flex flex-row items-center px-4 py-2"}>
763
+ <div
764
+ className={cls(
765
+ "flex flex-row items-center text-base min-h-[48px]",
766
+ )}>
767
+ <div className={"mr-8"}>
768
+ <PropertyConfigBadge propertyConfig={propertyConfig} disabled={shouldWarnChangingDataType}/>
769
+ </div>
770
+ <div>
771
+ <div className={"flex flex-row gap-2 items-center"}>
772
+ {shouldWarnChangingDataType && <Tooltip
773
+ title={"This widget uses a different data type than the initially selected widget. This can cause errors with existing data."}>
774
+ <WarningOffIcon size="smallest" className={"w-4"}/>
775
+ </Tooltip>}
776
+ <Typography
777
+ color={shouldWarnChangingDataType ? "secondary" : undefined}>{propertyConfig.name}</Typography>
778
+ </div>
779
+
780
+ <Typography variant={"caption"}
781
+ color={"secondary"}
782
+ className={"max-w-sm"}>
783
+ {propertyConfig.description}
784
+ </Typography>
785
+
786
+ </div>
787
+ </div>
788
+ </Card>
789
+ }