@firecms/collection_editor 3.0.0 → 3.1.0-canary.02232f4

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 (119) hide show
  1. package/dist/ConfigControllerProvider.d.ts +6 -0
  2. package/dist/api/generateCollectionApi.d.ts +71 -0
  3. package/dist/api/index.d.ts +1 -0
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.es.js +15234 -8138
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +15199 -8103
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/locales/de.d.ts +120 -0
  10. package/dist/locales/en.d.ts +120 -0
  11. package/dist/locales/es.d.ts +120 -0
  12. package/dist/locales/fr.d.ts +120 -0
  13. package/dist/locales/hi.d.ts +120 -0
  14. package/dist/locales/it.d.ts +120 -0
  15. package/dist/locales/pt.d.ts +120 -0
  16. package/dist/types/collection_editor_controller.d.ts +14 -0
  17. package/dist/types/collection_inference.d.ts +8 -2
  18. package/dist/types/config_controller.d.ts +31 -1
  19. package/dist/ui/AddKanbanColumnAction.d.ts +11 -0
  20. package/dist/ui/KanbanSetupAction.d.ts +10 -0
  21. package/dist/ui/collection_editor/AICollectionGeneratorPopover.d.ts +37 -0
  22. package/dist/ui/collection_editor/AIModifiedPathsContext.d.ts +20 -0
  23. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +2 -3
  24. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +24 -0
  25. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +4 -1
  26. package/dist/ui/collection_editor/CollectionJsonImportDialog.d.ts +7 -0
  27. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +9 -13
  28. package/dist/ui/collection_editor/DisplaySettingsForm.d.ts +3 -0
  29. package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +2 -1
  30. package/dist/ui/collection_editor/ExtendSettingsForm.d.ts +14 -0
  31. package/dist/ui/collection_editor/GeneralSettingsForm.d.ts +7 -0
  32. package/dist/ui/collection_editor/KanbanConfigSection.d.ts +4 -0
  33. package/dist/ui/collection_editor/PropertyEditView.d.ts +6 -1
  34. package/dist/ui/collection_editor/PropertyTree.d.ts +2 -1
  35. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +2 -1
  36. package/dist/ui/collection_editor/ViewModeSwitch.d.ts +6 -0
  37. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +2 -1
  38. package/dist/ui/collection_editor/properties/conditions/ConditionsEditor.d.ts +10 -0
  39. package/dist/ui/collection_editor/properties/conditions/ConditionsPanel.d.ts +2 -0
  40. package/dist/ui/collection_editor/properties/conditions/EnumConditionsEditor.d.ts +6 -0
  41. package/dist/ui/collection_editor/properties/conditions/index.d.ts +6 -0
  42. package/dist/ui/collection_editor/properties/conditions/property_paths.d.ts +19 -0
  43. package/dist/useCollectionEditorPlugin.d.ts +7 -1
  44. package/dist/utils/validateCollectionJson.d.ts +22 -0
  45. package/package.json +15 -15
  46. package/src/ConfigControllerProvider.tsx +82 -47
  47. package/src/api/generateCollectionApi.ts +119 -0
  48. package/src/api/index.ts +1 -0
  49. package/src/index.ts +28 -1
  50. package/src/locales/de.ts +125 -0
  51. package/src/locales/en.ts +145 -0
  52. package/src/locales/es.ts +125 -0
  53. package/src/locales/fr.ts +125 -0
  54. package/src/locales/hi.ts +125 -0
  55. package/src/locales/it.ts +125 -0
  56. package/src/locales/pt.ts +125 -0
  57. package/src/types/collection_editor_controller.tsx +16 -3
  58. package/src/types/collection_inference.ts +15 -2
  59. package/src/types/config_controller.tsx +37 -1
  60. package/src/ui/AddKanbanColumnAction.tsx +203 -0
  61. package/src/ui/EditorCollectionAction.tsx +3 -3
  62. package/src/ui/EditorCollectionActionStart.tsx +1 -2
  63. package/src/ui/EditorEntityAction.tsx +3 -2
  64. package/src/ui/HomePageEditorCollectionAction.tsx +41 -13
  65. package/src/ui/KanbanSetupAction.tsx +38 -0
  66. package/src/ui/MissingReferenceWidget.tsx +1 -1
  67. package/src/ui/NewCollectionButton.tsx +4 -2
  68. package/src/ui/NewCollectionCard.tsx +7 -4
  69. package/src/ui/PropertyAddColumnComponent.tsx +4 -3
  70. package/src/ui/collection_editor/AICollectionGeneratorPopover.tsx +243 -0
  71. package/src/ui/collection_editor/AIModifiedPathsContext.tsx +88 -0
  72. package/src/ui/collection_editor/CollectionDetailsForm.tsx +222 -268
  73. package/src/ui/collection_editor/CollectionEditorDialog.tsx +270 -204
  74. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +138 -71
  75. package/src/ui/collection_editor/CollectionJsonImportDialog.tsx +171 -0
  76. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +202 -101
  77. package/src/ui/collection_editor/DisplaySettingsForm.tsx +335 -0
  78. package/src/ui/collection_editor/EntityActionsEditTab.tsx +106 -97
  79. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +8 -10
  80. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +5 -7
  81. package/src/ui/collection_editor/EnumForm.tsx +153 -102
  82. package/src/ui/collection_editor/ExtendSettingsForm.tsx +94 -0
  83. package/src/ui/collection_editor/GeneralSettingsForm.tsx +335 -0
  84. package/src/ui/collection_editor/GetCodeDialog.tsx +63 -41
  85. package/src/ui/collection_editor/KanbanConfigSection.tsx +209 -0
  86. package/src/ui/collection_editor/LayoutModeSwitch.tsx +27 -43
  87. package/src/ui/collection_editor/PropertyEditView.tsx +272 -199
  88. package/src/ui/collection_editor/PropertyFieldPreview.tsx +1 -1
  89. package/src/ui/collection_editor/PropertyTree.tsx +130 -58
  90. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +169 -163
  91. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +0 -2
  92. package/src/ui/collection_editor/ViewModeSwitch.tsx +43 -0
  93. package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +6 -3
  94. package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +5 -2
  95. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +0 -2
  96. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +4 -1
  97. package/src/ui/collection_editor/properties/CommonPropertyFields.tsx +6 -4
  98. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +126 -42
  99. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +32 -24
  100. package/src/ui/collection_editor/properties/MapPropertyField.tsx +8 -9
  101. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +128 -53
  102. package/src/ui/collection_editor/properties/NumberPropertyField.tsx +3 -1
  103. package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +6 -9
  104. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +65 -49
  105. package/src/ui/collection_editor/properties/StringPropertyField.tsx +3 -1
  106. package/src/ui/collection_editor/properties/UrlPropertyField.tsx +12 -10
  107. package/src/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +23 -4
  108. package/src/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +866 -0
  109. package/src/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +28 -0
  110. package/src/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +599 -0
  111. package/src/ui/collection_editor/properties/conditions/index.ts +6 -0
  112. package/src/ui/collection_editor/properties/conditions/property_paths.ts +92 -0
  113. package/src/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +5 -2
  114. package/src/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +7 -5
  115. package/src/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +10 -7
  116. package/src/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +11 -9
  117. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +5 -2
  118. package/src/useCollectionEditorPlugin.tsx +53 -22
  119. package/src/utils/validateCollectionJson.ts +380 -0
@@ -10,10 +10,12 @@ import {
10
10
  isPropertyBuilder,
11
11
  isValidRegExp,
12
12
  mergeDeep,
13
+ Properties,
13
14
  Property,
14
15
  PropertyConfig,
15
16
  PropertyConfigBadge,
16
17
  PropertyConfigId,
18
+ useTranslation,
17
19
  } from "@firecms/core";
18
20
  import {
19
21
  Button,
@@ -45,6 +47,7 @@ import { NumberPropertyField } from "./properties/NumberPropertyField";
45
47
  import { ReferencePropertyField } from "./properties/ReferencePropertyField";
46
48
  import { DateTimePropertyField } from "./properties/DateTimePropertyField";
47
49
  import { AdvancedPropertyValidation } from "./properties/advanced/AdvancedPropertyValidation";
50
+ import { ConditionsPanel, ConditionsEditor, EnumConditionsEditor } from "./properties/conditions";
48
51
  import { editableProperty } from "../../utils/entities";
49
52
  import { KeyValuePropertyField } from "./properties/KeyValuePropertyField";
50
53
  import { updatePropertyFromWidget } from "./utils/update_property_for_widget";
@@ -85,6 +88,11 @@ export type PropertyFormProps = {
85
88
  getController?: (formex: FormexController<PropertyWithId>) => void;
86
89
  propertyConfigs: Record<string, PropertyConfig>;
87
90
  collectionEditable: boolean;
91
+ /**
92
+ * Collection properties for populating the conditions field selector.
93
+ * Includes nested map properties.
94
+ */
95
+ collectionProperties?: Properties;
88
96
  };
89
97
 
90
98
  export const PropertyForm = React.memo(
@@ -108,10 +116,11 @@ export const PropertyForm = React.memo(
108
116
  initialErrors,
109
117
  forceShowErrors,
110
118
  allowDataInference,
111
- getController,
112
119
  getData,
120
+ getController,
113
121
  propertyConfigs,
114
- collectionEditable
122
+ collectionEditable,
123
+ collectionProperties
115
124
  } = props;
116
125
 
117
126
  const initialValue: PropertyWithId = {
@@ -119,6 +128,8 @@ export const PropertyForm = React.memo(
119
128
  name: ""
120
129
  } as PropertyWithId;
121
130
 
131
+ const { t } = useTranslation();
132
+
122
133
  const disabled = (Boolean(property && !editableProperty(property)) || !collectionEditable);
123
134
 
124
135
  const lastSubmittedProperty = useRef<OnPropertyChangedParams | undefined>(property ? {
@@ -128,9 +139,9 @@ export const PropertyForm = React.memo(
128
139
  } : undefined);
129
140
 
130
141
  const doOnPropertyChanged = ({
131
- id,
132
- property
133
- }: OnPropertyChangedParams) => {
142
+ id,
143
+ property
144
+ }: OnPropertyChangedParams) => {
134
145
  const params = {
135
146
  id,
136
147
  previousId: lastSubmittedProperty.current?.id,
@@ -168,16 +179,18 @@ export const PropertyForm = React.memo(
168
179
  const errors: Record<string, any> = {};
169
180
  if (includeIdAndName) {
170
181
  if (!values.name) {
171
- errors.name = "Required";
182
+ errors.name = t("required");
172
183
  } else {
173
- const nameError = validateName(values.name);
184
+ const nameError = validateName(values.name, t);
174
185
  if (nameError)
175
186
  errors.name = nameError;
176
187
  }
177
188
  if (!values.id) {
178
- errors.id = "Required";
189
+ errors.id = t("required");
179
190
  } else {
180
- const idError = validateId(values.id, existingPropertyKeys);
191
+ // Exclude the current property key when editing to avoid false duplicate error
192
+ const keysToCheck = existingPropertyKeys?.filter(key => key !== propertyKey);
193
+ const idError = validateId(values.id, keysToCheck, t);
181
194
  if (idError)
182
195
  errors.id = idError;
183
196
  }
@@ -186,23 +199,67 @@ export const PropertyForm = React.memo(
186
199
  if (values.dataType === "string") {
187
200
  if (values.validation?.matches && !isValidRegExp(values.validation?.matches.toString())) {
188
201
  errors.validation = {
189
- matches: "Invalid regular expression"
202
+ matches: t("invalid_regular_expression")
190
203
  }
191
204
  }
192
205
  }
193
206
  if (values.dataType === "reference" && !values.path) {
194
- errors.path = "You must specify a target collection for the field";
207
+ errors.path = t("must_specify_target_collection");
195
208
  }
196
209
  if (values.propertyConfig === "repeat") {
197
210
  if (!(values as any).of) {
198
- errors.of = "You need to specify a repeat field";
211
+ errors.of = t("need_specify_repeat_field");
199
212
  }
200
213
  }
201
214
  if (values.propertyConfig === "block") {
202
215
  if (!(values as any).oneOf) {
203
- errors.oneOf = "You need to specify the properties of this block";
216
+ errors.oneOf = t("need_specify_block_properties");
204
217
  }
205
218
  }
219
+
220
+ // Validate conditions - check for incomplete condition rules
221
+ const conditions = (values as any).conditions;
222
+ if (conditions) {
223
+ const conditionErrors: Record<string, string> = {};
224
+ const conditionTypes = ["disabled", "hidden", "required", "readOnly"];
225
+
226
+ // Helper to check if a JSON Logic rule is incomplete (placeholder or empty keys)
227
+ const isIncompleteRule = (rule: any): boolean => {
228
+ if (!rule || typeof rule !== "object") return false;
229
+ const ruleStr = JSON.stringify(rule);
230
+ // Check for placeholder pattern used when field is not selected
231
+ if (ruleStr.includes("values._placeholder")) return true;
232
+ // Check for empty string keys (invalid operators)
233
+ if (Object.keys(rule).some(k => k === "")) return true;
234
+ // Recursively check nested objects
235
+ for (const key of Object.keys(rule)) {
236
+ if (typeof rule[key] === "object" && isIncompleteRule(rule[key])) return true;
237
+ }
238
+ return false;
239
+ };
240
+
241
+ for (const type of conditionTypes) {
242
+ const rule = conditions[type];
243
+ if (rule && isIncompleteRule(rule)) {
244
+ conditionErrors[type] = t("incomplete_condition");
245
+ }
246
+ }
247
+
248
+ // Validate enum conditions (allowedEnumValues, excludedEnumValues)
249
+ for (const enumType of ["allowedEnumValues", "excludedEnumValues"]) {
250
+ const rule = conditions[enumType];
251
+ if (rule && typeof rule === "object" && rule.if) {
252
+ if (isIncompleteRule(rule)) {
253
+ conditionErrors[enumType] = t("incomplete_condition");
254
+ }
255
+ }
256
+ }
257
+
258
+ if (Object.keys(conditionErrors).length > 0) {
259
+ errors.conditions = conditionErrors;
260
+ }
261
+ }
262
+
206
263
  return errors;
207
264
  }
208
265
  });
@@ -231,33 +288,37 @@ export const PropertyForm = React.memo(
231
288
  allowDataInference={allowDataInference}
232
289
  propertyConfigs={propertyConfigs}
233
290
  collectionEditable={collectionEditable}
234
- {...formexController}/>
291
+ collectionProperties={collectionProperties}
292
+ {...formexController} />
235
293
  </Formex>;
236
294
  }, (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
295
+ a.getData === b.getData &&
296
+ a.propertyKey === b.propertyKey &&
297
+ a.propertyNamespace === b.propertyNamespace &&
298
+ a.includeIdAndName === b.includeIdAndName &&
299
+ a.autoOpenTypeSelect === b.autoOpenTypeSelect &&
300
+ a.autoUpdateId === b.autoUpdateId &&
301
+ a.existingPropertyKeys === b.existingPropertyKeys &&
302
+ a.existingProperty === b.existingProperty &&
303
+ equal(a.property, b.property)
245
304
  );
246
305
 
247
306
  export function PropertyFormDialog({
248
- open,
249
- onCancel,
250
- onOkClicked,
251
- onPropertyChanged,
252
- getData,
253
- collectionEditable,
254
- ...formProps
255
- }: PropertyFormProps & {
307
+ open,
308
+ onCancel,
309
+ onOkClicked,
310
+ onPropertyChanged,
311
+ getData,
312
+ collectionEditable,
313
+ ...formProps
314
+ }: PropertyFormProps & {
256
315
  open?: boolean;
257
316
  onOkClicked?: () => void;
258
317
  onCancel?: () => void;
259
318
  }) {
260
- const formexRef = useRef<FormexController<PropertyWithId>>();
319
+ const { t } = useTranslation();
320
+
321
+ const formexRef = useRef<FormexController<PropertyWithId>>(undefined);
261
322
  const getController = (helpers: FormexController<PropertyWithId>) => {
262
323
  formexRef.current = helpers;
263
324
  };
@@ -268,25 +329,25 @@ export function PropertyFormDialog({
268
329
  fullWidth={true}
269
330
  >
270
331
  <form noValidate={true}
271
- autoComplete={"off"}
272
- onSubmit={(e) => {
273
- e.preventDefault();
274
- e.stopPropagation();
275
- formexRef.current?.handleSubmit(e)
276
- }}>
277
- <DialogTitle hidden>Property edit view</DialogTitle>
332
+ autoComplete={"off"}
333
+ onSubmit={(e) => {
334
+ e.preventDefault();
335
+ e.stopPropagation();
336
+ formexRef.current?.handleSubmit(e)
337
+ }}>
338
+ <DialogTitle hidden>{t("property_edit_view")}</DialogTitle>
278
339
  <DialogContent>
279
340
 
280
341
  <PropertyForm {...formProps}
281
- onDismiss={onCancel}
282
- onPropertyChanged={(params) => {
283
- onPropertyChanged?.(params);
284
- onOkClicked?.();
285
- }}
286
- collectionEditable={collectionEditable}
287
- onPropertyChangedImmediate={false}
288
- getController={getController}
289
- getData={getData}
342
+ onDismiss={onCancel}
343
+ onPropertyChanged={(params) => {
344
+ onPropertyChanged?.(params);
345
+ onOkClicked?.();
346
+ }}
347
+ collectionEditable={collectionEditable}
348
+ onPropertyChangedImmediate={false}
349
+ getController={getController}
350
+ getData={getData}
290
351
  />
291
352
  </DialogContent>
292
353
 
@@ -294,7 +355,6 @@ export function PropertyFormDialog({
294
355
 
295
356
  {onCancel && <Button
296
357
  variant={"text"}
297
- color={"primary"}
298
358
  onClick={() => {
299
359
  onCancel();
300
360
  formexRef.current?.resetForm();
@@ -302,9 +362,7 @@ export function PropertyFormDialog({
302
362
  Cancel
303
363
  </Button>}
304
364
 
305
- <Button variant="outlined"
306
- type={"submit"}
307
- color="primary">
365
+ <Button type={"submit"}>
308
366
  Ok
309
367
  </Button>
310
368
  </DialogActions>
@@ -314,26 +372,27 @@ export function PropertyFormDialog({
314
372
  }
315
373
 
316
374
  function PropertyEditFormFields({
317
- values,
318
- errors,
319
- setValues,
320
- existing,
321
- autoUpdateId = false,
322
- autoOpenTypeSelect,
323
- includeIdAndTitle,
324
- onPropertyChanged,
325
- onDelete,
326
- propertyNamespace,
327
- onDismiss,
328
- onError,
329
- showErrors,
330
- disabled,
331
- inArray,
332
- getData,
333
- allowDataInference,
334
- propertyConfigs,
335
- collectionEditable
336
- }: {
375
+ values,
376
+ errors,
377
+ setValues,
378
+ existing,
379
+ autoUpdateId = false,
380
+ autoOpenTypeSelect,
381
+ includeIdAndTitle,
382
+ onPropertyChanged,
383
+ onDelete,
384
+ propertyNamespace,
385
+ onDismiss,
386
+ onError,
387
+ showErrors,
388
+ disabled,
389
+ inArray,
390
+ getData,
391
+ allowDataInference,
392
+ propertyConfigs,
393
+ collectionEditable,
394
+ collectionProperties
395
+ }: {
337
396
  includeIdAndTitle?: boolean;
338
397
  existing: boolean;
339
398
  autoUpdateId?: boolean;
@@ -350,8 +409,11 @@ function PropertyEditFormFields({
350
409
  allowDataInference: boolean;
351
410
  propertyConfigs: Record<string, PropertyConfig>;
352
411
  collectionEditable: boolean;
412
+ collectionProperties?: Properties;
353
413
  } & FormexController<PropertyWithId>) {
354
414
 
415
+ const { t } = useTranslation();
416
+
355
417
  const [selectOpen, setSelectOpen] = useState(autoOpenTypeSelect);
356
418
  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
357
419
  const [selectedFieldConfigId, setSelectedFieldConfigId] = useState<string | undefined>(values?.dataType ? getFieldId(values) : undefined);
@@ -404,16 +466,16 @@ function PropertyEditFormFields({
404
466
  selectedFieldConfigId === "email") {
405
467
  childComponent =
406
468
  <StringPropertyField widgetId={selectedFieldConfigId}
407
- disabled={disabled}
408
- showErrors={showErrors}/>;
469
+ disabled={disabled}
470
+ showErrors={showErrors} />;
409
471
  } else if (selectedFieldConfigId === "url") {
410
472
  childComponent =
411
473
  <UrlPropertyField disabled={disabled}
412
- showErrors={showErrors}/>;
474
+ showErrors={showErrors} />;
413
475
  } else if (selectedFieldConfigId === "markdown") {
414
476
  childComponent =
415
477
  <MarkdownPropertyField disabled={disabled}
416
- showErrors={showErrors}/>;
478
+ showErrors={showErrors} />;
417
479
  } else if (selectedFieldConfigId === "select" ||
418
480
  selectedFieldConfigId === "number_select") {
419
481
  childComponent = <EnumPropertyField
@@ -422,7 +484,8 @@ function PropertyEditFormFields({
422
484
  updateIds={!existing}
423
485
  disabled={disabled}
424
486
  getData={getData}
425
- showErrors={showErrors}/>;
487
+ propertyNamespace={propertyNamespace}
488
+ showErrors={showErrors} />;
426
489
  } else if (selectedFieldConfigId === "multi_select" ||
427
490
  selectedFieldConfigId === "multi_number_select") {
428
491
  childComponent = <EnumPropertyField
@@ -431,64 +494,65 @@ function PropertyEditFormFields({
431
494
  disabled={disabled}
432
495
  allowDataInference={allowDataInference}
433
496
  getData={getData}
434
- showErrors={showErrors}/>;
497
+ propertyNamespace={propertyNamespace}
498
+ showErrors={showErrors} />;
435
499
  } else if (selectedFieldConfigId === "file_upload") {
436
500
  childComponent =
437
501
  <StoragePropertyField existing={existing}
438
- multiple={false}
439
- disabled={disabled}/>;
502
+ multiple={false}
503
+ disabled={disabled} />;
440
504
  } else if (selectedFieldConfigId === "multi_file_upload") {
441
505
  childComponent =
442
506
  <StoragePropertyField existing={existing}
443
- multiple={true}
444
- disabled={disabled}/>;
507
+ multiple={true}
508
+ disabled={disabled} />;
445
509
  } else if (selectedFieldConfigId === "switch") {
446
- childComponent = <BooleanPropertyField disabled={disabled}/>;
510
+ childComponent = <BooleanPropertyField disabled={disabled} />;
447
511
  } else if (selectedFieldConfigId === "number_input") {
448
- childComponent = <NumberPropertyField disabled={disabled}/>;
512
+ childComponent = <NumberPropertyField disabled={disabled} />;
449
513
  } else if (selectedFieldConfigId === "group") {
450
514
  childComponent =
451
515
  <MapPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference}
452
- collectionEditable={collectionEditable}
453
- propertyConfigs={propertyConfigs}/>;
516
+ collectionEditable={collectionEditable}
517
+ propertyConfigs={propertyConfigs} />;
454
518
  } else if (selectedFieldConfigId === "block") {
455
519
  childComponent =
456
520
  <BlockPropertyField disabled={disabled} getData={getData} allowDataInference={allowDataInference}
457
- collectionEditable={collectionEditable}
458
- propertyConfigs={propertyConfigs}/>;
521
+ collectionEditable={collectionEditable}
522
+ propertyConfigs={propertyConfigs} />;
459
523
  } else if (selectedFieldConfigId === "reference") {
460
524
  childComponent =
461
525
  <ReferencePropertyField showErrors={showErrors}
462
- existing={existing}
463
- multiple={false}
464
- disabled={disabled}/>;
526
+ existing={existing}
527
+ multiple={false}
528
+ disabled={disabled} />;
465
529
  } else if (selectedFieldConfigId === "reference_as_string") {
466
530
  childComponent =
467
531
  <ReferencePropertyField showErrors={showErrors}
468
- existing={existing}
469
- asString={true}
470
- multiple={false}
471
- disabled={disabled}/>;
532
+ existing={existing}
533
+ asString={true}
534
+ multiple={false}
535
+ disabled={disabled} />;
472
536
  } else if (selectedFieldConfigId === "date_time") {
473
- childComponent = <DateTimePropertyField disabled={disabled}/>;
537
+ childComponent = <DateTimePropertyField disabled={disabled} />;
474
538
  } else if (selectedFieldConfigId === "multi_references") {
475
539
  childComponent =
476
540
  <ReferencePropertyField showErrors={showErrors}
477
- existing={existing}
478
- multiple={true}
479
- disabled={disabled}/>;
541
+ existing={existing}
542
+ multiple={true}
543
+ disabled={disabled} />;
480
544
  } else if (selectedFieldConfigId === "repeat") {
481
545
  childComponent =
482
546
  <RepeatPropertyField showErrors={showErrors}
483
- existing={existing}
484
- getData={getData}
485
- allowDataInference={allowDataInference}
486
- disabled={disabled}
487
- collectionEditable={collectionEditable}
488
- propertyConfigs={propertyConfigs}/>;
547
+ existing={existing}
548
+ getData={getData}
549
+ allowDataInference={allowDataInference}
550
+ disabled={disabled}
551
+ collectionEditable={collectionEditable}
552
+ propertyConfigs={propertyConfigs} />;
489
553
  } else if (selectedFieldConfigId === "key_value") {
490
554
  childComponent =
491
- <KeyValuePropertyField disabled={disabled}/>;
555
+ <KeyValuePropertyField disabled={disabled} />;
492
556
  } else {
493
557
  childComponent = null;
494
558
  }
@@ -520,54 +584,59 @@ function PropertyEditFormFields({
520
584
  showError={Boolean(selectedWidgetError)}
521
585
  existing={existing}
522
586
  propertyConfigs={propertyConfigs}
523
- inArray={inArray}/>
587
+ inArray={inArray} />
524
588
 
525
589
  {selectedWidgetError &&
526
590
  <Typography variant="caption"
527
- className={"ml-3.5"}
528
- color={"error"}>Required</Typography>}
591
+ className={"ml-3.5"}
592
+ color={"error"}>{t("required")}</Typography>}
529
593
 
530
594
  {/*<Typography variant="caption" className={"ml-3.5"}>Define your own custom properties and*/}
531
595
  {/* components</Typography>*/}
532
596
 
533
597
  </div>
534
598
 
535
- {onDelete && values?.id &&
599
+ {onDelete && existing && values?.id &&
536
600
  <IconButton
537
601
  variant={"ghost"}
538
602
  className="m-4"
539
603
  disabled={disabled}
540
604
  onClick={() => setDeleteDialogOpen(true)}>
541
- <DeleteIcon/>
605
+ <DeleteIcon />
542
606
  </IconButton>}
543
607
  </div>
544
608
 
545
609
  <div className={"grid grid-cols-12 gap-y-12 mt-8 mb-8"}>
546
610
  {includeIdAndTitle &&
547
611
  <CommonPropertyFields showErrors={showErrors}
548
- disabledId={existing}
549
- isNewProperty={!existing}
550
- disabled={disabled}
551
- autoUpdateId={autoUpdateId}
552
- ref={nameFieldRef}/>}
612
+ disabledId={existing}
613
+ isNewProperty={!existing}
614
+ disabled={disabled}
615
+ autoUpdateId={autoUpdateId}
616
+ ref={nameFieldRef} />}
553
617
 
554
618
  {childComponent}
555
619
 
556
620
  <div className={"col-span-12"}>
557
- <AdvancedPropertyValidation disabled={disabled}/>
621
+ <AdvancedPropertyValidation disabled={disabled} />
622
+ </div>
623
+
624
+ <div className={"col-span-12"}>
625
+ <ConditionsPanel>
626
+ <ConditionsEditor disabled={disabled} collectionProperties={collectionProperties} />
627
+ <EnumConditionsEditor disabled={disabled} collectionProperties={collectionProperties} />
628
+ </ConditionsPanel>
558
629
  </div>
559
630
  </div>
560
631
 
561
632
  {onDelete &&
562
633
  <ConfirmationDialog open={deleteDialogOpen}
563
- onAccept={() => onDelete(values?.id, propertyNamespace)}
564
- onCancel={() => setDeleteDialogOpen(false)}
565
- title={<div>Delete this property?</div>}
566
- body={
567
- <div> This will <b>not delete any
568
- data</b>, only modify the
569
- collection.</div>
570
- }/>}
634
+ onAccept={() => onDelete(values?.id, propertyNamespace)}
635
+ onCancel={() => setDeleteDialogOpen(false)}
636
+ title={<div>{t("delete_this_property")}</div>}
637
+ body={
638
+ <div>{t("delete_property_warning")}</div>
639
+ } />}
571
640
 
572
641
  </>
573
642
  );
@@ -575,67 +644,67 @@ function PropertyEditFormFields({
575
644
 
576
645
  const idRegEx = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
577
646
 
578
- function validateId(value?: string, existingPropertyKeys?: string[]) {
647
+ function validateId(value: string | undefined, existingPropertyKeys: string[] | undefined, t: any) {
579
648
 
580
649
  let error;
581
650
  if (!value) {
582
- error = "You must specify an id for the field";
651
+ error = t("error_must_specify_id");
583
652
  }
584
653
  if (value && !value.match(idRegEx)) {
585
- error = "The id can only contain letters, numbers and underscores (_), and not start with a number";
654
+ error = t("error_id_format");
586
655
  }
587
656
  if (value && existingPropertyKeys && existingPropertyKeys.includes(value)) {
588
- error = "There is another field with this ID already";
657
+ error = t("error_id_already_exists");
589
658
  }
590
659
  return error;
591
660
  }
592
661
 
593
- function validateName(value: string) {
662
+ function validateName(value: string, t: any) {
594
663
  let error;
595
664
  if (!value) {
596
- error = "You must specify a title for the field";
665
+ error = t("error_must_specify_title");
597
666
  }
598
667
  return error;
599
668
  }
600
669
 
601
670
  const WIDGET_TYPE_MAP: Record<PropertyConfigId, string> = {
602
- text_field: "Text",
603
- multiline: "Text",
604
- markdown: "Text",
605
- url: "Text",
606
- email: "Text",
607
- switch: "Boolean",
608
- user_select: "Users",
609
- select: "Select",
610
- multi_select: "Select",
611
- number_input: "Number",
612
- number_select: "Select",
613
- multi_number_select: "Select",
614
- file_upload: "File",
615
- multi_file_upload: "File",
616
- reference: "Reference",
617
- reference_as_string: "Text",
618
- multi_references: "Reference",
619
- date_time: "Date",
620
- group: "Group",
621
- key_value: "Group",
622
- repeat: "Array",
623
- custom_array: "Array",
624
- block: "Group"
671
+ text_field: "widget_group_text",
672
+ multiline: "widget_group_text",
673
+ markdown: "widget_group_text",
674
+ url: "widget_group_text",
675
+ email: "widget_group_text",
676
+ switch: "widget_group_boolean",
677
+ user_select: "widget_group_users",
678
+ select: "widget_group_select",
679
+ multi_select: "widget_group_select",
680
+ number_input: "widget_group_number",
681
+ number_select: "widget_group_select",
682
+ multi_number_select: "widget_group_select",
683
+ file_upload: "widget_group_file",
684
+ multi_file_upload: "widget_group_file",
685
+ reference: "widget_group_reference",
686
+ reference_as_string: "widget_group_text",
687
+ multi_references: "widget_group_reference",
688
+ date_time: "widget_group_date",
689
+ group: "widget_group_group",
690
+ key_value: "widget_group_group",
691
+ repeat: "widget_group_array",
692
+ custom_array: "widget_group_array",
693
+ block: "widget_group_group"
625
694
  };
626
695
 
627
696
  function WidgetSelectView({
628
- initialProperty,
629
- value,
630
- onValueChange,
631
- open,
632
- onOpenChange,
633
- disabled,
634
- showError,
635
- existing,
636
- propertyConfigs,
637
- inArray
638
- }: {
697
+ initialProperty,
698
+ value,
699
+ onValueChange,
700
+ open,
701
+ onOpenChange,
702
+ disabled,
703
+ showError,
704
+ existing,
705
+ propertyConfigs,
706
+ inArray
707
+ }: {
639
708
  initialProperty?: PropertyWithId,
640
709
  value?: PropertyConfigId,
641
710
  onValueChange: (value: string) => void,
@@ -648,6 +717,8 @@ function WidgetSelectView({
648
717
  inArray?: boolean
649
718
  }) {
650
719
 
720
+ const { t } = useTranslation();
721
+
651
722
  const allSupportedFields = Object.entries(supportedFields).concat(Object.entries(propertyConfigs));
652
723
 
653
724
  const displayedWidgets = (inArray
@@ -670,47 +741,47 @@ function WidgetSelectView({
670
741
  const computedFieldConfig = baseFieldConfig && propertyConfig ? mergeDeep(baseFieldConfig, propertyConfig) : propertyConfig;
671
742
 
672
743
  const groups: string[] = [...new Set(Object.keys(displayedWidgets).map(key => {
673
- const group = WIDGET_TYPE_MAP[key as PropertyConfigId];
674
- if (group) {
675
- return group;
744
+ const groupKey = WIDGET_TYPE_MAP[key as PropertyConfigId];
745
+ if (groupKey) {
746
+ return t(groupKey as any);
676
747
  }
677
- return "Custom/Other"
748
+ return t("custom_or_other")
678
749
  }))];
679
750
 
680
751
  return <>
681
- <div
682
- onClick={() => {
683
- if (!disabled) {
684
- onOpenChange(!open, Boolean(value));
685
- }
686
- }}
687
- className={cls(
688
- "select-none rounded-md text-sm p-4",
689
- fieldBackgroundMixin,
690
- disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
691
- "relative flex items-center",
692
- )}>
693
- {!value && <em>Select a property widget</em>}
752
+ <div
753
+ onClick={() => {
754
+ if (!disabled) {
755
+ onOpenChange(!open, Boolean(value));
756
+ }
757
+ }}
758
+ className={cls(
759
+ "select-none rounded-md text-sm p-4",
760
+ fieldBackgroundMixin,
761
+ disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
762
+ "relative flex items-center",
763
+ )}>
764
+ {!value && <em>{t("select_property_widget")}</em>}
694
765
  {value && computedFieldConfig && <div
695
766
  className={cls(
696
767
  "flex items-center")}>
697
768
  <div className={"mr-8"}>
698
- <PropertyConfigBadge propertyConfig={computedFieldConfig}/>
769
+ <PropertyConfigBadge propertyConfig={computedFieldConfig} />
699
770
  </div>
700
771
  <div className={"flex flex-col items-start text-base text-left"}>
701
772
  <div>{computedFieldConfig.name}</div>
702
773
  <Typography variant={"caption"}
703
- color={"secondary"}>
774
+ color={"secondary"}>
704
775
  {computedFieldConfig.description}
705
776
  </Typography>
706
777
  </div>
707
778
  </div>}
708
779
  </div>
709
780
  <Dialog open={open}
710
- onOpenChange={(open) => onOpenChange(open, Boolean(value))}
711
- maxWidth={"4xl"}>
781
+ onOpenChange={(open) => onOpenChange(open, Boolean(value))}
782
+ maxWidth={"4xl"}>
712
783
  <DialogTitle>
713
- Select a property widget
784
+ {t("select_property_widget")}
714
785
  </DialogTitle>
715
786
  <DialogContent>
716
787
  <div>
@@ -720,7 +791,8 @@ function WidgetSelectView({
720
791
  <div className={"grid grid-cols-1 md:grid-cols-2 gap-x-4 gap-y-2 mt-4"}>
721
792
  {Object.entries(displayedWidgets).map(([key, propertyConfig]) => {
722
793
  const groupKey = WIDGET_TYPE_MAP[key as PropertyConfigId];
723
- if (groupKey === group) {
794
+ const translatedGroup = groupKey ? t(groupKey as any) : t("custom_or_other");
795
+ if (translatedGroup === group) {
724
796
  return <WidgetSelectViewItem
725
797
  key={key}
726
798
  initialProperty={initialProperty}
@@ -729,7 +801,7 @@ function WidgetSelectView({
729
801
  onOpenChange(false, true);
730
802
  }}
731
803
  propertyConfig={propertyConfig}
732
- existing={existing}/>;
804
+ existing={existing} />;
733
805
  }
734
806
  return null;
735
807
  })}
@@ -761,12 +833,13 @@ export interface PropertySelectItemProps {
761
833
  }
762
834
 
763
835
  export function WidgetSelectViewItem({
764
- onClick,
765
- initialProperty,
766
- // optionDisabled,
767
- propertyConfig,
768
- existing
769
- }: PropertySelectItemProps) {
836
+ onClick,
837
+ initialProperty,
838
+ // optionDisabled,
839
+ propertyConfig,
840
+ existing
841
+ }: PropertySelectItemProps) {
842
+ const { t } = useTranslation();
770
843
  const baseProperty = propertyConfig.property;
771
844
  const shouldWarnChangingDataType = existing && !isPropertyBuilder(baseProperty) && baseProperty.dataType !== initialProperty?.dataType;
772
845
 
@@ -778,13 +851,13 @@ export function WidgetSelectViewItem({
778
851
  "flex flex-row items-center text-base min-h-[48px]",
779
852
  )}>
780
853
  <div className={"mr-8"}>
781
- <PropertyConfigBadge propertyConfig={propertyConfig} disabled={shouldWarnChangingDataType}/>
854
+ <PropertyConfigBadge propertyConfig={propertyConfig} disabled={shouldWarnChangingDataType} />
782
855
  </div>
783
856
  <div>
784
857
  <div className={"flex flex-row gap-2 items-center"}>
785
858
  {shouldWarnChangingDataType && <Tooltip
786
- title={"This widget uses a different data type than the initially selected widget. This can cause errors with existing data."}>
787
- <WarningIcon size="smallest" className={"w-4"}/>
859
+ title={t("error_changing_data_type")}>
860
+ <WarningIcon size="smallest" className={"w-4"} />
788
861
  </Tooltip>}
789
862
  <Typography
790
863
  variant={"label"}
@@ -792,8 +865,8 @@ export function WidgetSelectViewItem({
792
865
  </div>
793
866
 
794
867
  <Typography variant={"caption"}
795
- color={"secondary"}
796
- className={"max-w-sm"}>
868
+ color={"secondary"}
869
+ className={"max-w-sm"}>
797
870
  {propertyConfig.description}
798
871
  </Typography>
799
872