@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
@@ -1,10 +1,63 @@
1
1
  import React from "react";
2
2
  import { getIn, useFormex } from "@firecms/formex";
3
- import { FieldCaption, NumberProperty, StringProperty } from "@firecms/core";
3
+ import { FieldCaption, NumberProperty, StringProperty, useTranslation } from "@firecms/core";
4
4
  import { Select, SelectItem } from "@firecms/ui";
5
5
  import { GeneralPropertyValidation } from "./validation/GeneralPropertyValidation";
6
6
  import { ValidationPanel } from "./validation/ValidationPanel";
7
7
 
8
+ // Common IANA timezones with human-readable labels
9
+ const TIMEZONES = [
10
+ // UTC
11
+ { value: "UTC", label: "UTC (Coordinated Universal Time)" },
12
+ // Americas
13
+ { value: "America/New_York", label: "New York (US Eastern)" },
14
+ { value: "America/Chicago", label: "Chicago (US Central)" },
15
+ { value: "America/Denver", label: "Denver (US Mountain)" },
16
+ { value: "America/Los_Angeles", label: "Los Angeles (US Pacific)" },
17
+ { value: "America/Anchorage", label: "Anchorage (Alaska)" },
18
+ { value: "America/Toronto", label: "Toronto (Canada Eastern)" },
19
+ { value: "America/Vancouver", label: "Vancouver (Canada Pacific)" },
20
+ { value: "America/Mexico_City", label: "Mexico City" },
21
+ { value: "America/Sao_Paulo", label: "São Paulo (Brazil)" },
22
+ { value: "America/Buenos_Aires", label: "Buenos Aires (Argentina)" },
23
+ // Europe
24
+ { value: "Europe/London", label: "London (UK)" },
25
+ { value: "Europe/Paris", label: "Paris (France)" },
26
+ { value: "Europe/Berlin", label: "Berlin (Germany)" },
27
+ { value: "Europe/Madrid", label: "Madrid (Spain)" },
28
+ { value: "Europe/Rome", label: "Rome (Italy)" },
29
+ { value: "Europe/Amsterdam", label: "Amsterdam (Netherlands)" },
30
+ { value: "Europe/Brussels", label: "Brussels (Belgium)" },
31
+ { value: "Europe/Zurich", label: "Zurich (Switzerland)" },
32
+ { value: "Europe/Stockholm", label: "Stockholm (Sweden)" },
33
+ { value: "Europe/Vienna", label: "Vienna (Austria)" },
34
+ { value: "Europe/Warsaw", label: "Warsaw (Poland)" },
35
+ { value: "Europe/Prague", label: "Prague (Czech Republic)" },
36
+ { value: "Europe/Athens", label: "Athens (Greece)" },
37
+ { value: "Europe/Moscow", label: "Moscow (Russia)" },
38
+ { value: "Europe/Istanbul", label: "Istanbul (Turkey)" },
39
+ // Asia
40
+ { value: "Asia/Dubai", label: "Dubai (UAE)" },
41
+ { value: "Asia/Kolkata", label: "Mumbai / Delhi (India)" },
42
+ { value: "Asia/Bangkok", label: "Bangkok (Thailand)" },
43
+ { value: "Asia/Singapore", label: "Singapore" },
44
+ { value: "Asia/Hong_Kong", label: "Hong Kong" },
45
+ { value: "Asia/Shanghai", label: "Shanghai (China)" },
46
+ { value: "Asia/Tokyo", label: "Tokyo (Japan)" },
47
+ { value: "Asia/Seoul", label: "Seoul (South Korea)" },
48
+ { value: "Asia/Jakarta", label: "Jakarta (Indonesia)" },
49
+ // Oceania
50
+ { value: "Australia/Sydney", label: "Sydney (Australia Eastern)" },
51
+ { value: "Australia/Melbourne", label: "Melbourne (Australia)" },
52
+ { value: "Australia/Brisbane", label: "Brisbane (Australia)" },
53
+ { value: "Australia/Perth", label: "Perth (Australia Western)" },
54
+ { value: "Pacific/Auckland", label: "Auckland (New Zealand)" },
55
+ // Africa
56
+ { value: "Africa/Cairo", label: "Cairo (Egypt)" },
57
+ { value: "Africa/Johannesburg", label: "Johannesburg (South Africa)" },
58
+ { value: "Africa/Lagos", label: "Lagos (Nigeria)" },
59
+ ];
60
+
8
61
  export function DateTimePropertyField({ disabled }: {
9
62
  disabled: boolean;
10
63
  }) {
@@ -16,6 +69,8 @@ export function DateTimePropertyField({ disabled }: {
16
69
  setFieldValue
17
70
  } = useFormex<StringProperty | NumberProperty>();
18
71
 
72
+ const { t } = useTranslation();
73
+
19
74
  const modePath = "mode";
20
75
  const modeValue: string | undefined = getIn(values, modePath);
21
76
  const modeError: string | undefined = getIn(touched, modePath) && getIn(errors, modePath);
@@ -24,30 +79,34 @@ export function DateTimePropertyField({ disabled }: {
24
79
  const autoValueValue: string | undefined = getIn(values, autoValuePath);
25
80
  const autoValueError: string | undefined = getIn(touched, autoValuePath) && getIn(errors, autoValuePath);
26
81
 
82
+ const timezonePath = "timezone";
83
+ const timezoneValue: string | undefined = getIn(values, timezonePath);
84
+ const timezoneError: string | undefined = getIn(touched, timezonePath) && getIn(errors, timezonePath);
85
+
27
86
  return (
28
87
  <>
29
88
  <div className={"flex flex-col col-span-12 gap-2"}>
30
89
  <div>
31
90
  <Select name={modePath}
32
- value={modeValue ?? "date"}
33
- error={Boolean(modeError)}
34
- size={"large"}
35
- onValueChange={(v) => setFieldValue(modePath, v)}
36
- label={"Mode"}
37
- fullWidth={true}
38
- renderValue={(v) => {
39
- switch (v) {
40
- case "date_time":
41
- return "Date/Time";
42
- case "date":
43
- return "Date";
44
- default:
45
- return "";
46
- }
47
- }}
48
- disabled={disabled}>
49
- <SelectItem value={"date_time"}> Date/Time </SelectItem>
50
- <SelectItem value={"date"}> Date </SelectItem>
91
+ value={modeValue ?? "date"}
92
+ error={Boolean(modeError)}
93
+ size={"large"}
94
+ onValueChange={(v) => setFieldValue(modePath, v)}
95
+ label={t("mode_label")}
96
+ fullWidth={true}
97
+ renderValue={(v) => {
98
+ switch (v) {
99
+ case "date_time":
100
+ return t("date_time_mode");
101
+ case "date":
102
+ return t("date_mode");
103
+ default:
104
+ return "";
105
+ }
106
+ }}
107
+ disabled={disabled}>
108
+ <SelectItem value={"date_time"}> {t("date_time_mode")} </SelectItem>
109
+ <SelectItem value={"date"}> {t("date_mode")} </SelectItem>
51
110
  </Select>
52
111
  <FieldCaption error={Boolean(modeError)}>
53
112
  {modeError}
@@ -55,29 +114,54 @@ export function DateTimePropertyField({ disabled }: {
55
114
  </div>
56
115
  <div>
57
116
  <Select name={autoValuePath}
58
- disabled={disabled}
59
- size={"large"}
60
- fullWidth={true}
61
- value={autoValueValue ?? ""}
62
- onValueChange={(v) => setFieldValue(autoValuePath, v === "none" ? null : v)}
63
- renderValue={(v) => {
64
- switch (v) {
65
- case "on_create":
66
- return "On create";
67
- case "on_update":
68
- return "On any update";
69
- default:
70
- return "None";
71
- }
72
- }}
73
- error={Boolean(autoValueError)}
74
- label={"Automatic value"}>
75
- <SelectItem value={"none"}> None </SelectItem>
76
- <SelectItem value={"on_create"}> On create </SelectItem>
77
- <SelectItem value={"on_update"}> On any update </SelectItem>
117
+ disabled={disabled}
118
+ size={"large"}
119
+ fullWidth={true}
120
+ value={autoValueValue ?? ""}
121
+ onValueChange={(v) => setFieldValue(autoValuePath, v === "none" ? null : v)}
122
+ renderValue={(v) => {
123
+ switch (v) {
124
+ case "on_create":
125
+ return t("auto_on_create");
126
+ case "on_update":
127
+ return t("auto_on_update");
128
+ default:
129
+ return t("auto_none");
130
+ }
131
+ }}
132
+ error={Boolean(autoValueError)}
133
+ label={t("automatic_value")}>
134
+ <SelectItem value={"none"}> {t("auto_none")} </SelectItem>
135
+ <SelectItem value={"on_create"}> {t("auto_on_create")} </SelectItem>
136
+ <SelectItem value={"on_update"}> {t("auto_on_update")} </SelectItem>
78
137
  </Select>
79
138
  <FieldCaption error={Boolean(autoValueError)}>
80
- {autoValueError ?? "Update this field automatically when creating or updating the entity"}
139
+ {autoValueError ?? t("auto_value_description")}
140
+ </FieldCaption>
141
+ </div>
142
+ <div>
143
+ <Select name={timezonePath}
144
+ disabled={disabled}
145
+ size={"large"}
146
+ fullWidth={true}
147
+ value={timezoneValue ?? "__local__"}
148
+ onValueChange={(v) => setFieldValue(timezonePath, v === "__local__" ? undefined : v)}
149
+ renderValue={(v) => {
150
+ if (!v || v === "__local__") return t("local_timezone");
151
+ const tz = TIMEZONES.find(t => t.value === v);
152
+ return tz?.label ?? v;
153
+ }}
154
+ error={Boolean(timezoneError)}
155
+ label={t("timezone_label")}>
156
+ <SelectItem value={"__local__"}> {t("local_timezone")} </SelectItem>
157
+ {TIMEZONES.map((tz) => (
158
+ <SelectItem key={tz.value} value={tz.value}>
159
+ {tz.label}
160
+ </SelectItem>
161
+ ))}
162
+ </Select>
163
+ <FieldCaption error={Boolean(timezoneError)}>
164
+ {timezoneError ?? t("timezone_description")}
81
165
  </FieldCaption>
82
166
  </div>
83
167
 
@@ -85,7 +169,7 @@ export function DateTimePropertyField({ disabled }: {
85
169
 
86
170
  <div className={"col-span-12"}>
87
171
  <ValidationPanel>
88
- <GeneralPropertyValidation disabled={disabled}/>
172
+ <GeneralPropertyValidation disabled={disabled} />
89
173
  </ValidationPanel>
90
174
  </div>
91
175
  </>
@@ -1,6 +1,6 @@
1
1
  import React, { useMemo } from "react";
2
2
  import { getIn, useFormex } from "@firecms/formex";
3
- import { EnumValueConfig, resolveEnumValues, useSnackbarController } from "@firecms/core";
3
+ import { EnumValueConfig, resolveEnumValues, useSnackbarController, useTranslation } from "@firecms/core";
4
4
  import { Select, SelectItem } from "@firecms/ui";
5
5
  import { EnumForm } from "../EnumForm";
6
6
  import { StringPropertyValidation } from "./validation/StringPropertyValidation";
@@ -9,19 +9,21 @@ import { ValidationPanel } from "./validation/ValidationPanel";
9
9
  import { PropertyWithId } from "../PropertyEditView";
10
10
 
11
11
  export function EnumPropertyField({
12
- multiselect,
13
- updateIds,
14
- disabled,
15
- showErrors,
16
- allowDataInference,
17
- getData
18
- }: {
12
+ multiselect,
13
+ updateIds,
14
+ disabled,
15
+ showErrors,
16
+ allowDataInference,
17
+ getData,
18
+ propertyNamespace
19
+ }: {
19
20
  multiselect: boolean;
20
21
  updateIds: boolean;
21
22
  disabled: boolean;
22
23
  showErrors: boolean;
23
24
  allowDataInference?: boolean;
24
25
  getData?: () => Promise<object[]>;
26
+ propertyNamespace?: string;
25
27
  }) {
26
28
 
27
29
  const {
@@ -30,6 +32,7 @@ export function EnumPropertyField({
30
32
  setFieldValue
31
33
  } = useFormex<PropertyWithId>();
32
34
 
35
+ const { t } = useTranslation();
33
36
  const snackbarContext = useSnackbarController();
34
37
 
35
38
  const enumValuesPath = multiselect ? "of.enumValues" : "enumValues";
@@ -53,27 +56,32 @@ export function EnumPropertyField({
53
56
  setFieldValue("defaultValue", undefined);
54
57
  snackbarContext.open({
55
58
  type: "warning",
56
- message: "Default value was cleared"
59
+ message: t("default_value_was_cleared")
57
60
  })
58
61
  }
59
62
  }
60
63
  };
61
64
 
65
+ // Build full path including namespace for nested properties (e.g., "address.status")
66
+ const fullPropertyPath = values.id
67
+ ? (propertyNamespace ? `${propertyNamespace}.${values.id}` : values.id)
68
+ : undefined;
69
+
62
70
  return (
63
71
  <>
64
72
  <div className={"col-span-12"}>
65
73
  <EnumForm enumValues={enumValues}
66
- updateIds={updateIds}
67
- disabled={disabled}
68
- allowDataInference={allowDataInference}
69
- onError={(hasError) => {
70
- setFieldError(enumValuesPath, hasError ? "This enum property is missing some values" : undefined);
71
- }}
72
- getData={getData
73
- ? () => getData()
74
- .then(res => res.map(entry => values.id && getIn(entry, values.id)).filter(Boolean))
75
- : undefined}
76
- onValuesChanged={onValuesChanged}/>
74
+ updateIds={updateIds}
75
+ disabled={disabled}
76
+ allowDataInference={allowDataInference}
77
+ onError={(hasError) => {
78
+ setFieldError(enumValuesPath, hasError ? t("enum_missing_values") : undefined);
79
+ }}
80
+ getData={getData && fullPropertyPath
81
+ ? () => getData()
82
+ .then(res => res.map(entry => getIn(entry, fullPropertyPath)).filter(Boolean))
83
+ : undefined}
84
+ onValuesChanged={onValuesChanged} />
77
85
  </div>
78
86
 
79
87
  <div className={"col-span-12"}>
@@ -81,9 +89,9 @@ export function EnumPropertyField({
81
89
  <ValidationPanel>
82
90
  {!multiselect &&
83
91
  <StringPropertyValidation disabled={disabled}
84
- showErrors={showErrors}/>}
92
+ showErrors={showErrors} />}
85
93
  {multiselect &&
86
- <ArrayPropertyValidation disabled={disabled}/>}
94
+ <ArrayPropertyValidation disabled={disabled} />}
87
95
  </ValidationPanel>
88
96
 
89
97
  </div>
@@ -98,13 +106,13 @@ export function EnumPropertyField({
98
106
  setFieldValue("defaultValue", value);
99
107
  }}
100
108
  size={"large"}
101
- label={"Default value"}
109
+ label={t("default_value")}
102
110
  value={defaultValue ?? ""}>
103
111
  {enumValues
104
112
  .filter((enumValue) => Boolean(enumValue?.id))
105
113
  .map((enumValue) => (
106
114
  <SelectItem key={enumValue.id}
107
- value={enumValue.id?.toString()}>
115
+ value={enumValue.id?.toString()}>
108
116
  {enumValue.label}
109
117
  </SelectItem>
110
118
  ))}
@@ -1,5 +1,6 @@
1
1
  import React, { useState } from "react";
2
- import { FieldCaption, MapProperty, Property, PropertyConfig, } from "@firecms/core";
2
+ import { FieldCaption, MapProperty, Property, PropertyConfig, useTranslation
3
+ } from "@firecms/core";
3
4
  import { AddIcon, BooleanSwitchWithLabel, Button, Paper, Typography } from "@firecms/ui";
4
5
  import { PropertyFormDialog } from "../PropertyEditView";
5
6
  import { getIn, useFormex } from "@firecms/formex";
@@ -13,6 +14,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
13
14
  propertyConfigs: Record<string, PropertyConfig>,
14
15
  collectionEditable: boolean;
15
16
  }) {
17
+ const { t } = useTranslation();
16
18
 
17
19
  const {
18
20
  values,
@@ -73,14 +75,12 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
73
75
  <>
74
76
  <div className={"col-span-12"}>
75
77
  <div className="flex justify-between items-end my-4">
76
- <Typography variant={"subtitle2"}>Properties in this group</Typography>
78
+ <Typography variant={"subtitle2"}>{t("properties_in_this_group")}</Typography>
77
79
  <Button
78
- color="primary"
79
- variant={"outlined"}
80
80
  onClick={() => setPropertyDialogOpen(true)}
81
81
  startIcon={<AddIcon/>}
82
82
  >
83
- Add property to {values.name ?? "this group"}
83
+ {t("add_property_to", { name: values.name ?? t("properties_in_this_group") })}
84
84
  </Button>
85
85
  </div>
86
86
  <Paper className="p-2 pl-8">
@@ -99,7 +99,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
99
99
  {empty &&
100
100
  <Typography variant={"label"}
101
101
  className="h-full flex items-center justify-center p-4">
102
- Add the first property to this group
102
+ {t("add_first_property_to_group")}
103
103
  </Typography>}
104
104
  </Paper>
105
105
  </div>
@@ -108,13 +108,12 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
108
108
  <BooleanSwitchWithLabel
109
109
  position={"start"}
110
110
  size={"medium"}
111
- label="Spread children as columns"
111
+ label={t("spread_children_as_columns")}
112
112
  onValueChange={(v) => setFieldValue("spreadChildren", v)}
113
113
  value={values.spreadChildren ?? false}
114
114
  />
115
115
  <FieldCaption>
116
- Set this flag to true if you want to display the children of this group as individual columns. This
117
- will only work for top level groups.
116
+ {t("spread_children_description")}
118
117
  </FieldCaption>
119
118
  </div>
120
119
 
@@ -1,14 +1,23 @@
1
- import React from "react";
1
+ import React, { useCallback } from "react";
2
2
  import { StringPropertyValidation } from "./validation/StringPropertyValidation";
3
3
  import { ValidationPanel } from "./validation/ValidationPanel";
4
4
  import { Field, getIn, useFormex } from "@firecms/formex";
5
5
 
6
- import { CloudUploadIcon, DebouncedTextField, ExpandablePanel, TextField, Typography } from "@firecms/ui";
6
+ import {
7
+ BooleanSwitchWithLabel,
8
+ CloudUploadIcon,
9
+ DebouncedTextField,
10
+ ExpandablePanel,
11
+ SettingsIcon,
12
+ TextField,
13
+ Typography
14
+ } from "@firecms/ui";
15
+ import { useTranslation } from "@firecms/core";
7
16
 
8
17
  export function MarkdownPropertyField({
9
- disabled,
10
- showErrors
11
- }: {
18
+ disabled,
19
+ showErrors
20
+ }: {
12
21
  disabled: boolean;
13
22
  showErrors: boolean;
14
23
  }) {
@@ -18,20 +27,52 @@ export function MarkdownPropertyField({
18
27
  setFieldValue
19
28
  } = useFormex();
20
29
 
30
+ const { t } = useTranslation();
31
+
21
32
  const baseStoragePath = "storage";
33
+ const baseMarkdownPath = "markdown";
22
34
 
23
35
  const metadata = `${baseStoragePath}.metadata`;
24
36
  const fileName = `${baseStoragePath}.fileName`;
25
37
  const maxSize = `${baseStoragePath}.maxSize`;
26
38
  const storagePath = `${baseStoragePath}.storagePath`;
27
39
 
40
+ // Markdown config paths
41
+ const htmlPath = `${baseMarkdownPath}.html`;
42
+ const transformPastedTextPath = `${baseMarkdownPath}.transformPastedText`;
43
+
28
44
  const fileNameValue = getIn(values, fileName) ?? "{rand}_{file}";
29
45
  const storagePathValue = getIn(values, storagePath) ?? "/";
30
46
  const maxSizeValue = getIn(values, maxSize);
31
47
 
48
+ // Markdown config values - check if markdown is an object or boolean
49
+ const markdownValue = getIn(values, "markdown");
50
+ const isMarkdownObject = typeof markdownValue === "object" && markdownValue !== null;
51
+ const htmlValue = isMarkdownObject ? (markdownValue.html ?? true) : true;
52
+ const transformPastedTextValue = isMarkdownObject ? (markdownValue.transformPastedText ?? false) : false;
53
+
32
54
  const hasFilenameCallback = typeof fileNameValue === "function";
33
55
  const hasStoragePathCallback = typeof storagePathValue === "function";
34
56
 
57
+ // Handler to update markdown config - converts from boolean to object if needed
58
+ const updateMarkdownConfig = useCallback((key: "html" | "transformPastedText", value: boolean) => {
59
+ const currentMarkdown = getIn(values, "markdown");
60
+ if (typeof currentMarkdown === "object" && currentMarkdown !== null) {
61
+ // Already an object, update the key
62
+ setFieldValue(`markdown.${key}`, value);
63
+ } else {
64
+ // Convert from boolean to object
65
+ const defaultConfig = {
66
+ html: true,
67
+ transformPastedText: false
68
+ };
69
+ setFieldValue("markdown", {
70
+ ...defaultConfig,
71
+ [key]: value
72
+ });
73
+ }
74
+ }, [values, setFieldValue]);
75
+
35
76
  return (
36
77
  <>
37
78
  <div className={"col-span-12"}>
@@ -39,13 +80,13 @@ export function MarkdownPropertyField({
39
80
  <ValidationPanel>
40
81
 
41
82
  <StringPropertyValidation disabled={disabled}
42
- length={true}
43
- lowercase={true}
44
- max={true}
45
- min={true}
46
- trim={true}
47
- uppercase={true}
48
- showErrors={showErrors}/>
83
+ length={true}
84
+ lowercase={true}
85
+ max={true}
86
+ min={true}
87
+ trim={true}
88
+ uppercase={true}
89
+ showErrors={showErrors} />
49
90
 
50
91
  </ValidationPanel>
51
92
 
@@ -55,10 +96,48 @@ export function MarkdownPropertyField({
55
96
  <ExpandablePanel
56
97
  title={
57
98
  <div className="flex flex-row text-surface-500">
58
- <CloudUploadIcon/>
99
+ <SettingsIcon />
100
+ <Typography variant={"subtitle2"}
101
+ className="ml-4">
102
+ {t("paste_behavior")}
103
+ </Typography>
104
+ </div>
105
+ }>
106
+
107
+ <div className={"flex flex-col gap-2 p-4"}>
108
+ <BooleanSwitchWithLabel
109
+ size={"small"}
110
+ disabled={disabled}
111
+ value={!htmlValue}
112
+ onValueChange={(value) => updateMarkdownConfig("html", !value)}
113
+ label={t("strip_html_on_paste")}
114
+ />
115
+ <Typography variant={"caption"} className={"ml-3.5 mt-1 mb-2"}>
116
+ {t("strip_html_description")}
117
+ </Typography>
118
+
119
+ <BooleanSwitchWithLabel
120
+ size={"small"}
121
+ disabled={disabled}
122
+ value={transformPastedTextValue}
123
+ onValueChange={(value) => updateMarkdownConfig("transformPastedText", value)}
124
+ label={t("convert_pasted_to_markdown")}
125
+ />
126
+ <Typography variant={"caption"} className={"ml-3.5 mt-1 mb-2"}>
127
+ {t("convert_pasted_description")}
128
+ </Typography>
129
+ </div>
130
+ </ExpandablePanel>
131
+ </div>
132
+
133
+ <div className={"col-span-12"}>
134
+ <ExpandablePanel
135
+ title={
136
+ <div className="flex flex-row text-surface-500">
137
+ <CloudUploadIcon />
59
138
  <Typography variant={"subtitle2"}
60
- className="ml-4">
61
- File upload config
139
+ className="ml-4">
140
+ {t("file_upload_config")}
62
141
  </Typography>
63
142
  </div>
64
143
  }>
@@ -68,54 +147,50 @@ export function MarkdownPropertyField({
68
147
 
69
148
  <div className={"col-span-12"}>
70
149
  <Field name={fileName}
71
- as={DebouncedTextField}
72
- label={"File name"}
73
- size={"small"}
74
- disabled={hasFilenameCallback || disabled}
75
- value={hasFilenameCallback ? "-" : fileNameValue}
150
+ as={DebouncedTextField}
151
+ label={t("file_name_label")}
152
+ size={"small"}
153
+ disabled={hasFilenameCallback || disabled}
154
+ value={hasFilenameCallback ? "-" : fileNameValue}
76
155
  />
77
156
  </div>
78
157
  <div className={"col-span-12"}>
79
158
  <Field name={storagePath}
80
- as={DebouncedTextField}
81
- label={"Storage path"}
82
- disabled={hasStoragePathCallback || disabled}
83
- size={"small"}
84
- value={hasStoragePathCallback ? "-" : storagePathValue}
159
+ as={DebouncedTextField}
160
+ label={t("storage_path_label")}
161
+ disabled={hasStoragePathCallback || disabled}
162
+ size={"small"}
163
+ value={hasStoragePathCallback ? "-" : storagePathValue}
85
164
  />
86
165
  <Typography variant={"caption"} className={"ml-3.5 mt-1 mb-2"}>
87
- <p>You can use the following placeholders in
88
- the file name
89
- and storage path values:</p>
166
+ <p>{t("storage_placeholders_description")}</p>
90
167
  <ul>
91
- <li>{"{file} - Full name of the uploaded file"}</li>
92
- <li>{"{file.name} - Name of the uploaded file without extension"}</li>
93
- <li>{"{file.ext} - Extension of the uploaded file"}</li>
94
- <li>{"{entityId} - ID of the entity"}</li>
95
- <li>{"{propertyKey} - ID of this field"}</li>
96
- <li>{"{path} - Path of this entity"}</li>
97
- <li>{"{rand} - Random value used to avoid name collisions"}</li>
168
+ <li>{t("storage_placeholder_file")}</li>
169
+ <li>{t("storage_placeholder_file_name")}</li>
170
+ <li>{t("storage_placeholder_file_ext")}</li>
171
+ <li>{t("storage_placeholder_entity_id")}</li>
172
+ <li>{t("storage_placeholder_property_key")}</li>
173
+ <li>{t("storage_placeholder_path")}</li>
174
+ <li>{t("storage_placeholder_rand")}</li>
98
175
  </ul>
99
176
  </Typography>
100
177
 
101
178
  <Typography variant={"caption"} className={"ml-3.5 mt-1 mb-2"}>
102
- When using Markdown, the URL of the uploaded files are always saved in the text value
103
- (not
104
- the path).
179
+ {t("markdown_url_note")}
105
180
  </Typography>
106
181
  </div>
107
182
 
108
183
  <div className={"col-span-12"}>
109
184
  <DebouncedTextField name={maxSize}
110
- type={"number"}
111
- label={"Max size (in bytes)"}
112
- size={"small"}
113
- value={maxSizeValue !== undefined && maxSizeValue !== null ? maxSizeValue.toString() : ""}
114
- onChange={(e) => {
115
- const value = e.target.value;
116
- if (value === "") setFieldValue(maxSize, undefined);
117
- else setFieldValue(maxSize, parseInt(value));
118
- }}
185
+ type={"number"}
186
+ label={t("max_size_bytes")}
187
+ size={"small"}
188
+ value={maxSizeValue !== undefined && maxSizeValue !== null ? maxSizeValue.toString() : ""}
189
+ onChange={(e) => {
190
+ const value = e.target.value;
191
+ if (value === "") setFieldValue(maxSize, undefined);
192
+ else setFieldValue(maxSize, parseInt(value));
193
+ }}
119
194
  />
120
195
  </div>
121
196
 
@@ -126,12 +201,12 @@ export function MarkdownPropertyField({
126
201
  <div className={"col-span-12"}>
127
202
 
128
203
  <TextField name={"defaultValue"}
129
- disabled={disabled}
130
- onChange={(e: any) => {
131
- setFieldValue("defaultValue", e.target.value === "" ? undefined : e.target.value);
132
- }}
133
- label={"Default value"}
134
- value={getIn(values, "defaultValue") ?? ""}/>
204
+ disabled={disabled}
205
+ onChange={(e: any) => {
206
+ setFieldValue("defaultValue", e.target.value === "" ? undefined : e.target.value);
207
+ }}
208
+ label={t("default_value")}
209
+ value={getIn(values, "defaultValue") ?? ""} />
135
210
 
136
211
  </div>
137
212
  </>