@firecms/collection_editor 3.0.1 → 3.1.0-canary.1df3b2c

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 (90) 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 +9418 -5587
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +9413 -5582
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/collection_editor_controller.d.ts +14 -0
  10. package/dist/types/collection_inference.d.ts +8 -2
  11. package/dist/types/config_controller.d.ts +23 -2
  12. package/dist/ui/AddKanbanColumnAction.d.ts +11 -0
  13. package/dist/ui/KanbanSetupAction.d.ts +10 -0
  14. package/dist/ui/collection_editor/AICollectionGeneratorPopover.d.ts +33 -0
  15. package/dist/ui/collection_editor/AIModifiedPathsContext.d.ts +20 -0
  16. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +2 -3
  17. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +20 -0
  18. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +3 -1
  19. package/dist/ui/collection_editor/CollectionJsonImportDialog.d.ts +7 -0
  20. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +9 -13
  21. package/dist/ui/collection_editor/DisplaySettingsForm.d.ts +3 -0
  22. package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +2 -1
  23. package/dist/ui/collection_editor/ExtendSettingsForm.d.ts +14 -0
  24. package/dist/ui/collection_editor/GeneralSettingsForm.d.ts +7 -0
  25. package/dist/ui/collection_editor/KanbanConfigSection.d.ts +4 -0
  26. package/dist/ui/collection_editor/PropertyEditView.d.ts +6 -1
  27. package/dist/ui/collection_editor/PropertyTree.d.ts +2 -1
  28. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +2 -1
  29. package/dist/ui/collection_editor/ViewModeSwitch.d.ts +6 -0
  30. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +2 -1
  31. package/dist/ui/collection_editor/properties/conditions/ConditionsEditor.d.ts +10 -0
  32. package/dist/ui/collection_editor/properties/conditions/ConditionsPanel.d.ts +2 -0
  33. package/dist/ui/collection_editor/properties/conditions/EnumConditionsEditor.d.ts +6 -0
  34. package/dist/ui/collection_editor/properties/conditions/index.d.ts +6 -0
  35. package/dist/ui/collection_editor/properties/conditions/property_paths.d.ts +19 -0
  36. package/dist/useCollectionEditorPlugin.d.ts +7 -1
  37. package/dist/utils/validateCollectionJson.d.ts +22 -0
  38. package/package.json +11 -11
  39. package/src/ConfigControllerProvider.tsx +81 -47
  40. package/src/api/generateCollectionApi.ts +119 -0
  41. package/src/api/index.ts +1 -0
  42. package/src/index.ts +28 -1
  43. package/src/types/collection_editor_controller.tsx +16 -3
  44. package/src/types/collection_inference.ts +15 -2
  45. package/src/types/config_controller.tsx +27 -2
  46. package/src/ui/AddKanbanColumnAction.tsx +203 -0
  47. package/src/ui/EditorCollectionActionStart.tsx +1 -2
  48. package/src/ui/HomePageEditorCollectionAction.tsx +41 -13
  49. package/src/ui/KanbanSetupAction.tsx +38 -0
  50. package/src/ui/MissingReferenceWidget.tsx +1 -1
  51. package/src/ui/NewCollectionButton.tsx +1 -1
  52. package/src/ui/PropertyAddColumnComponent.tsx +1 -1
  53. package/src/ui/collection_editor/AICollectionGeneratorPopover.tsx +225 -0
  54. package/src/ui/collection_editor/AIModifiedPathsContext.tsx +88 -0
  55. package/src/ui/collection_editor/CollectionDetailsForm.tsx +209 -257
  56. package/src/ui/collection_editor/CollectionEditorDialog.tsx +226 -167
  57. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +130 -67
  58. package/src/ui/collection_editor/CollectionJsonImportDialog.tsx +171 -0
  59. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +190 -91
  60. package/src/ui/collection_editor/DisplaySettingsForm.tsx +333 -0
  61. package/src/ui/collection_editor/EntityActionsEditTab.tsx +106 -96
  62. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +6 -7
  63. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +1 -3
  64. package/src/ui/collection_editor/EnumForm.tsx +147 -100
  65. package/src/ui/collection_editor/ExtendSettingsForm.tsx +93 -0
  66. package/src/ui/collection_editor/GeneralSettingsForm.tsx +335 -0
  67. package/src/ui/collection_editor/GetCodeDialog.tsx +57 -36
  68. package/src/ui/collection_editor/KanbanConfigSection.tsx +207 -0
  69. package/src/ui/collection_editor/LayoutModeSwitch.tsx +22 -41
  70. package/src/ui/collection_editor/PropertyEditView.tsx +205 -141
  71. package/src/ui/collection_editor/PropertyFieldPreview.tsx +1 -1
  72. package/src/ui/collection_editor/PropertyTree.tsx +130 -58
  73. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +171 -162
  74. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +0 -2
  75. package/src/ui/collection_editor/ViewModeSwitch.tsx +41 -0
  76. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +0 -2
  77. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +1 -0
  78. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +117 -35
  79. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +28 -21
  80. package/src/ui/collection_editor/properties/MapPropertyField.tsx +0 -2
  81. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +115 -39
  82. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +1 -1
  83. package/src/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +861 -0
  84. package/src/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +28 -0
  85. package/src/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +599 -0
  86. package/src/ui/collection_editor/properties/conditions/index.ts +6 -0
  87. package/src/ui/collection_editor/properties/conditions/property_paths.ts +92 -0
  88. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +1 -1
  89. package/src/useCollectionEditorPlugin.tsx +32 -17
  90. package/src/utils/validateCollectionJson.ts +380 -0
@@ -0,0 +1,88 @@
1
+ import React, { createContext, useCallback, useContext, useState } from "react";
2
+ import { CollectionOperation } from "../../api/generateCollectionApi";
3
+
4
+ export interface AIModifiedPathsContextType {
5
+ /** Set of paths that were modified by AI */
6
+ modifiedPaths: Set<string>;
7
+ /** Counter that increments each time AI modifies the collection - use in keys to force remount */
8
+ generationCounter: number;
9
+ /** Add paths from operations */
10
+ addModifiedPaths: (operations: CollectionOperation[]) => void;
11
+ /** Clear a specific path (when user edits that field) */
12
+ clearPath: (path: string) => void;
13
+ /** Clear all paths (on save or cancel) */
14
+ clearAllPaths: () => void;
15
+ /** Check if a path is modified */
16
+ isPathModified: (path: string) => boolean;
17
+ }
18
+
19
+ const AIModifiedPathsContext = createContext<AIModifiedPathsContextType | null>(null);
20
+
21
+ export function AIModifiedPathsProvider({ children }: { children: React.ReactNode }) {
22
+ const [modifiedPaths, setModifiedPaths] = useState<Set<string>>(new Set());
23
+ const [generationCounter, setGenerationCounter] = useState(0);
24
+
25
+ const addModifiedPaths = useCallback((operations: CollectionOperation[]) => {
26
+ setModifiedPaths(prev => {
27
+ const newSet = new Set(prev);
28
+ operations.forEach(op => {
29
+ // Add the path and all parent paths for nested modifications
30
+ newSet.add(op.path);
31
+ // For properties modifications, also mark the property itself
32
+ // e.g., "properties.email.description" -> also adds "properties.email"
33
+ const parts = op.path.split(".");
34
+ if (parts[0] === "properties" && parts.length >= 2) {
35
+ newSet.add(`properties.${parts[1]}`);
36
+ }
37
+ });
38
+ return newSet;
39
+ });
40
+ // Increment counter to force property form remount
41
+ setGenerationCounter(prev => prev + 1);
42
+ }, []);
43
+
44
+ const clearPath = useCallback((path: string) => {
45
+ setModifiedPaths(prev => {
46
+ const newSet = new Set(prev);
47
+ // Remove exact path and any child paths
48
+ for (const p of newSet) {
49
+ if (p === path || p.startsWith(path + ".")) {
50
+ newSet.delete(p);
51
+ }
52
+ }
53
+ return newSet;
54
+ });
55
+ }, []);
56
+
57
+ const clearAllPaths = useCallback(() => {
58
+ setModifiedPaths(new Set());
59
+ }, []);
60
+
61
+ const isPathModified = useCallback((path: string): boolean => {
62
+ // Check if this exact path or any parent path is modified
63
+ if (modifiedPaths.has(path)) return true;
64
+ // Check if any child paths are modified
65
+ for (const p of modifiedPaths) {
66
+ if (p.startsWith(path + ".")) return true;
67
+ }
68
+ return false;
69
+ }, [modifiedPaths]);
70
+
71
+ return (
72
+ <AIModifiedPathsContext.Provider value={{
73
+ modifiedPaths,
74
+ generationCounter,
75
+ addModifiedPaths,
76
+ clearPath,
77
+ clearAllPaths,
78
+ isPathModified
79
+ }}>
80
+ {children}
81
+ </AIModifiedPathsContext.Provider>
82
+ );
83
+ }
84
+
85
+ export function useAIModifiedPaths(): AIModifiedPathsContextType | null {
86
+ return useContext(AIModifiedPathsContext);
87
+ }
88
+
@@ -1,5 +1,5 @@
1
- import React, { useEffect, useState } from "react";
2
- import { EntityCollection, FieldCaption, IconForView, SearchIconsView, singular, toSnakeCase, } from "@firecms/core";
1
+ import React, { useMemo, useState } from "react";
2
+ import { EntityCollection, FieldCaption, getFieldConfig, IconForView, Property, PropertyConfigBadge, resolveCollection, SearchIconsView, singular, toSnakeCase, unslugify, useAuthController, useCustomizationController } from "@firecms/core";
3
3
  import {
4
4
  BooleanSwitchWithLabel,
5
5
  Chip,
@@ -8,11 +8,9 @@ import {
8
8
  Container,
9
9
  DebouncedTextField,
10
10
  Dialog,
11
- ExpandablePanel,
12
11
  IconButton,
13
12
  Select,
14
13
  SelectItem,
15
- SettingsIcon,
16
14
  TextField,
17
15
  Tooltip,
18
16
  Typography,
@@ -22,16 +20,19 @@ import {
22
20
  import { Field, getIn, useFormex } from "@firecms/formex";
23
21
  import { useCollectionEditorController } from "../../useCollectionEditorController";
24
22
  import { LayoutModeSwitch } from "./LayoutModeSwitch";
23
+ import { ViewModeSwitch } from "./ViewModeSwitch";
24
+ import { KanbanConfigSection } from "./KanbanConfigSection";
25
+ import { PropertyFormDialog } from "./PropertyEditView";
25
26
 
26
27
  export function CollectionDetailsForm({
27
- isNewCollection,
28
- reservedGroups,
29
- existingPaths,
30
- existingIds,
31
- groups,
32
- parentCollection,
33
- children
34
- }: {
28
+ isNewCollection,
29
+ reservedGroups,
30
+ existingPaths,
31
+ existingIds,
32
+ groups,
33
+ parentCollection,
34
+ expandKanban
35
+ }: {
35
36
  isNewCollection: boolean,
36
37
  reservedGroups?: string[];
37
38
  existingPaths?: string[];
@@ -39,7 +40,7 @@ export function CollectionDetailsForm({
39
40
  groups: string[] | null;
40
41
  parentCollection?: EntityCollection;
41
42
  parentCollectionIds?: string[];
42
- children?: React.ReactNode;
43
+ expandKanban?: boolean;
43
44
  }) {
44
45
 
45
46
  const groupRef = React.useRef<HTMLInputElement>(null);
@@ -57,7 +58,35 @@ export function CollectionDetailsForm({
57
58
  const collectionEditor = useCollectionEditorController();
58
59
 
59
60
  const [iconDialogOpen, setIconDialogOpen] = useState(false);
60
- const [advancedPanelExpanded, setAdvancedPanelExpanded] = useState(false);
61
+ const [orderPropertyDialogOpen, setOrderPropertyDialogOpen] = useState(false);
62
+
63
+ const authController = useAuthController();
64
+ const customizationController = useCustomizationController();
65
+
66
+ // Resolve collection to get properties for order property select
67
+ const resolvedCollection = useMemo(() => resolveCollection({
68
+ collection: values,
69
+ path: values.path,
70
+ propertyConfigs: customizationController.propertyConfigs,
71
+ authController
72
+ }), [values, customizationController.propertyConfigs, authController]);
73
+
74
+ // Get number properties (for orderProperty)
75
+ const numberProperties = useMemo(() => {
76
+ const result: { key: string; label: string; property: Property; }[] = [];
77
+ if (!resolvedCollection.properties) return result;
78
+
79
+ Object.entries(resolvedCollection.properties).forEach(([key, prop]) => {
80
+ if (prop && 'dataType' in prop && prop.dataType === 'number') {
81
+ result.push({
82
+ key,
83
+ label: (prop as Property).name || key,
84
+ property: prop as Property
85
+ });
86
+ }
87
+ });
88
+ return result;
89
+ }, [resolvedCollection.properties]);
61
90
 
62
91
  const updateDatabaseId = (databaseId: string) => {
63
92
  setFieldValue("databaseId", databaseId ?? undefined);
@@ -83,13 +112,7 @@ export function CollectionDetailsForm({
83
112
 
84
113
  };
85
114
 
86
- useEffect(() => {
87
- if (errors.id) {
88
- setAdvancedPanelExpanded(true);
89
- }
90
- }, [errors.id]);
91
-
92
- const collectionIcon = <IconForView collectionOrView={values}/>;
115
+ const collectionIcon = <IconForView collectionOrView={values} />;
93
116
 
94
117
  const groupOptions = groups?.filter((group) => !reservedGroups?.includes(group));
95
118
 
@@ -103,17 +126,6 @@ export function CollectionDetailsForm({
103
126
 
104
127
  const isSubcollection = !!parentCollection;
105
128
 
106
- let customIdValue: "true" | "false" | "optional" | "code_defined" | undefined;
107
- if (typeof values.customId === "object") {
108
- customIdValue = "code_defined";
109
- } else if (values.customId === true) {
110
- customIdValue = "true";
111
- } else if (values.customId === false) {
112
- customIdValue = "false";
113
- } else if (values.customId === "optional") {
114
- customIdValue = "optional";
115
- }
116
-
117
129
  const showErrors = submitCount > 0;
118
130
 
119
131
  return (
@@ -127,10 +139,10 @@ export function CollectionDetailsForm({
127
139
  {isNewCollection ? "New collection" : `${values?.name} collection`}
128
140
  </Typography>
129
141
  <DefaultDatabaseField databaseId={values.databaseId}
130
- onDatabaseIdUpdate={updateDatabaseId}/>
142
+ onDatabaseIdUpdate={updateDatabaseId} />
131
143
 
132
144
  <Tooltip title={"Change icon"}
133
- asChild={true}>
145
+ asChild={true}>
134
146
  <IconButton
135
147
  shape={"square"}
136
148
  onClick={() => setIconDialogOpen(true)}>
@@ -155,7 +167,7 @@ export function CollectionDetailsForm({
155
167
  label={"Name"}
156
168
  autoFocus={true}
157
169
  required
158
- error={showErrors && Boolean(errors.name)}/>
170
+ error={showErrors && Boolean(errors.name)} />
159
171
  <FieldCaption error={touched.name && Boolean(errors.name)}>
160
172
  {touched.name && Boolean(errors.name) ? errors.name : "Name of this collection, usually a plural name (e.g. Products)"}
161
173
  </FieldCaption>
@@ -163,10 +175,10 @@ export function CollectionDetailsForm({
163
175
 
164
176
  <div className={cls("col-span-12 ")}>
165
177
  <Field name={"path"}
166
- as={DebouncedTextField}
167
- label={"Path"}
168
- required
169
- error={showErrors && Boolean(errors.path)}/>
178
+ as={DebouncedTextField}
179
+ label={"Path"}
180
+ required
181
+ error={showErrors && Boolean(errors.path)} />
170
182
 
171
183
  <FieldCaption error={touched.path && Boolean(errors.path)}>
172
184
  {touched.path && Boolean(errors.path)
@@ -214,7 +226,149 @@ export function CollectionDetailsForm({
214
226
  <LayoutModeSwitch
215
227
  className={"col-span-12"}
216
228
  value={values.openEntityMode ?? "side_panel"}
217
- onChange={(value) => setFieldValue("openEntityMode", value)}/>
229
+ onChange={(value) => setFieldValue("openEntityMode", value)} />
230
+
231
+ <ViewModeSwitch
232
+ className={"col-span-12"}
233
+ value={values.defaultViewMode ?? "table"}
234
+ onChange={(value) => setFieldValue("defaultViewMode", value)} />
235
+
236
+ <KanbanConfigSection className={"col-span-12"} forceExpanded={expandKanban} />
237
+
238
+ <div className={"col-span-12 mt-4"}>
239
+ {(() => {
240
+ // Check if orderProperty references a non-existent property
241
+ const orderPropertyMissing = Boolean(values.orderProperty) &&
242
+ !numberProperties.some(p => p.key === values.orderProperty);
243
+
244
+ return (
245
+ <>
246
+ <Select
247
+ key={`order-select-${numberProperties.length}`}
248
+ name="orderProperty"
249
+ label="Order Property"
250
+ size={"large"}
251
+ fullWidth={true}
252
+ position={"item-aligned"}
253
+ disabled={numberProperties.length === 0}
254
+ error={orderPropertyMissing}
255
+ value={values.orderProperty ?? ""}
256
+ onValueChange={(v) => {
257
+ setFieldValue("orderProperty", v || undefined);
258
+ }}
259
+ renderValue={(value) => {
260
+ if (orderPropertyMissing) {
261
+ return <span className="text-red-500">{value} (not found)</span>;
262
+ }
263
+ const prop = numberProperties.find(p => p.key === value);
264
+ if (!prop) return "Select a property";
265
+ const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
266
+ return (
267
+ <div className="flex items-center gap-2">
268
+ <PropertyConfigBadge propertyConfig={fieldConfig} />
269
+ <span>{prop.label}</span>
270
+ </div>
271
+ );
272
+ }}
273
+ endAdornment={values.orderProperty ? (
274
+ <IconButton
275
+ size="small"
276
+ onClick={(e) => {
277
+ e.stopPropagation();
278
+ setFieldValue("orderProperty", undefined);
279
+ }}
280
+ >
281
+ <CloseIcon size="small" />
282
+ </IconButton>
283
+ ) : undefined}
284
+ >
285
+ {numberProperties.map((prop) => {
286
+ const fieldConfig = getFieldConfig(prop.property, customizationController.propertyConfigs);
287
+ return (
288
+ <SelectItem key={prop.key} value={prop.key}>
289
+ <div className="flex items-center gap-3">
290
+ <PropertyConfigBadge propertyConfig={fieldConfig} />
291
+ <div>
292
+ <div>{prop.label}</div>
293
+ <Typography variant="caption" color="secondary">
294
+ {fieldConfig?.name || "Number"}
295
+ </Typography>
296
+ </div>
297
+ </div>
298
+ </SelectItem>
299
+ );
300
+ })}
301
+ </Select>
302
+ <FieldCaption error={orderPropertyMissing}>
303
+ {orderPropertyMissing
304
+ ? `Property "${values.orderProperty}" does not exist or is not a number property. Please select a valid property or clear the selection.`
305
+ : numberProperties.length === 0
306
+ ? "No number properties found. Add a number property to enable ordering."
307
+ : "Select a number property to persist the order of items"
308
+ }
309
+ </FieldCaption>
310
+ </>
311
+ );
312
+ })()}
313
+ {(() => {
314
+ // Check if orderProperty references a non-existent property
315
+ const orderPropertyMissing = Boolean(values.orderProperty) &&
316
+ !numberProperties.some(p => p.key === values.orderProperty);
317
+ const showCreateButton = !values.orderProperty || orderPropertyMissing;
318
+
319
+ // Pre-fill with missing property id or default "__order"
320
+ const dialogPropertyKey = orderPropertyMissing && values.orderProperty
321
+ ? values.orderProperty
322
+ : "__order";
323
+ const dialogPropertyName = orderPropertyMissing && values.orderProperty
324
+ ? unslugify(values.orderProperty)
325
+ : "Order";
326
+
327
+ if (!showCreateButton) return null;
328
+
329
+ return (
330
+ <>
331
+ <button
332
+ type="button"
333
+ className="ml-3.5 text-sm text-primary hover:text-primary-dark mt-2"
334
+ onClick={() => setOrderPropertyDialogOpen(true)}
335
+ >
336
+ + Create "{dialogPropertyKey}" property
337
+ </button>
338
+ <PropertyFormDialog
339
+ open={orderPropertyDialogOpen}
340
+ onCancel={() => setOrderPropertyDialogOpen(false)}
341
+ property={{
342
+ dataType: "number",
343
+ name: dialogPropertyName,
344
+ disabled: true,
345
+ hideFromCollection: true
346
+ }}
347
+ propertyKey={dialogPropertyKey}
348
+ existingProperty={false}
349
+ autoOpenTypeSelect={false}
350
+ autoUpdateId={false}
351
+ inArray={false}
352
+ allowDataInference={false}
353
+ propertyConfigs={customizationController.propertyConfigs}
354
+ collectionEditable={true}
355
+ existingPropertyKeys={Object.keys(values.properties ?? {})}
356
+ onPropertyChanged={({ id, property }) => {
357
+ const newProperties = {
358
+ ...values.properties,
359
+ [id!]: property
360
+ };
361
+ const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties ?? {})), id];
362
+ setFieldValue("properties", newProperties);
363
+ setFieldValue("propertiesOrder", newPropertiesOrder);
364
+ setFieldValue("orderProperty", id);
365
+ setOrderPropertyDialogOpen(false);
366
+ }}
367
+ />
368
+ </>
369
+ );
370
+ })()}
371
+ </div>
218
372
 
219
373
  <div className={"col-span-12"}>
220
374
  <BooleanSwitchWithLabel
@@ -236,214 +390,12 @@ export function CollectionDetailsForm({
236
390
 
237
391
 
238
392
  <div className={"col-span-12 mt-8"}>
239
- <ExpandablePanel
240
- expanded={advancedPanelExpanded}
241
- onExpandedChange={setAdvancedPanelExpanded}
242
- title={
243
- <div className="flex flex-row text-surface-500">
244
- <SettingsIcon/>
245
- <Typography variant={"subtitle2"}
246
- className="ml-2">
247
- Advanced
248
- </Typography>
249
- </div>}
250
- initiallyExpanded={false}>
251
- <div className={"grid grid-cols-12 gap-4 p-4"}>
252
-
253
- <div className={"col-span-12"}>
254
- <Field name={"id"}
255
- as={DebouncedTextField}
256
- disabled={!isNewCollection}
257
- label={"Collection id"}
258
- error={showErrors && Boolean(errors.id)}/>
259
- <FieldCaption error={touched.id && Boolean(errors.id)}>
260
- {touched.id && Boolean(errors.id) ? errors.id : "This id identifies this collection. Typically the same as the path."}
261
- </FieldCaption>
262
- </div>
263
-
264
- <div className={"col-span-12"}>
265
- <TextField
266
- error={showErrors && Boolean(errors.singularName)}
267
- name={"singularName"}
268
- aria-describedby={"singularName-helper"}
269
- onChange={(e) => {
270
- setFieldTouched("singularName", true);
271
- return handleChange(e);
272
- }}
273
- value={values.singularName ?? ""}
274
- label={"Singular name"}/>
275
- <FieldCaption error={showErrors && Boolean(errors.singularName)}>
276
- {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define a singular name for your entities"}
277
- </FieldCaption>
278
- </div>
279
- <div className={"col-span-12"}>
280
- <TextField
281
- error={showErrors && Boolean(errors.sideDialogWidth)}
282
- name={"sideDialogWidth"}
283
- type={"number"}
284
- aria-describedby={"sideDialogWidth-helper"}
285
- onChange={(e) => {
286
- setFieldTouched("sideDialogWidth", true);
287
- const value = e.target.value;
288
- if (!value) {
289
- setFieldValue("sideDialogWidth", null);
290
- } else if (!isNaN(Number(value))) {
291
- setFieldValue("sideDialogWidth", Number(value));
292
- }
293
- }}
294
- endAdornment={<IconButton
295
- size={"small"}
296
- onClick={() => {
297
- setFieldValue("sideDialogWidth", null);
298
- }}
299
- disabled={!values.sideDialogWidth}>
300
- <CloseIcon size={"small"}/>
301
- </IconButton>}
302
- value={values.sideDialogWidth ?? ""}
303
- label={"Side dialog width"}/>
304
- <FieldCaption error={showErrors && Boolean(errors.singularName)}>
305
- {showErrors && Boolean(errors.singularName) ? errors.singularName : "Optionally define the width (in pixels) of entities side dialog. Default is 768px"}
306
- </FieldCaption>
307
- </div>
308
- <div className={"col-span-12"}>
309
- <TextField
310
- error={showErrors && Boolean(errors.description)}
311
- name="description"
312
- value={values.description ?? ""}
313
- onChange={handleChange}
314
- multiline
315
- minRows={2}
316
- aria-describedby="description-helper-text"
317
- label="Description"
318
- />
319
- <FieldCaption error={showErrors && Boolean(errors.description)}>
320
- {showErrors && Boolean(errors.description) ? errors.description : "Description of the collection, you can use markdown"}
321
- </FieldCaption>
322
- </div>
323
-
324
- <div className={"col-span-12"}>
325
- <Select
326
- name="defaultSize"
327
- size={"large"}
328
- fullWidth={true}
329
- label="Default row size"
330
- position={"item-aligned"}
331
- onChange={handleChange}
332
- value={values.defaultSize ?? ""}
333
- renderValue={(value: any) => value.toUpperCase()}
334
- >
335
- {["xs", "s", "m", "l", "xl"].map((value) => (
336
- <SelectItem
337
- key={`size-select-${value}`}
338
- value={value}>
339
- {value.toUpperCase()}
340
- </SelectItem>
341
- ))}
342
- </Select>
343
- </div>
344
-
345
- <div className={"col-span-12"}>
346
- <BooleanSwitchWithLabel
347
- position={"start"}
348
- size={"large"}
349
- label={values.includeJsonView === undefined || values.includeJsonView ? "Include JSON view" : "Do not include JSON view"}
350
- onValueChange={(v) => setFieldValue("includeJsonView", v)}
351
- value={values.includeJsonView === undefined ? true : values.includeJsonView}
352
- />
353
- <FieldCaption>
354
- Include the JSON representation of the document.
355
- </FieldCaption>
356
- </div>
357
-
358
- <div className={"col-span-12"}>
359
- <BooleanSwitchWithLabel
360
- position={"start"}
361
- size={"large"}
362
- label={values.inlineEditing === undefined || values.inlineEditing ? "Data can be edited directly in the table view" : "Data can be edited only in the form view"}
363
- onValueChange={(v) => setFieldValue("inlineEditing", v)}
364
- value={values.inlineEditing === undefined ? true : values.inlineEditing}
365
- />
366
- <FieldCaption>
367
- Allow editing data directly in the table view, without opening the form view.
368
- </FieldCaption>
369
- </div>
370
-
371
- <div className={"col-span-12"}>
372
- <Select
373
- name="customId"
374
- label="Document IDs generation"
375
- position={"item-aligned"}
376
- size={"large"}
377
- fullWidth={true}
378
- disabled={customIdValue === "code_defined"}
379
- onValueChange={(v) => {
380
- if (v === "code_defined")
381
- throw new Error("This should not happen");
382
- setFieldValue("customId", v);
383
- }}
384
- value={customIdValue ?? ""}
385
- renderValue={(value: any) => {
386
- if (value === "code_defined")
387
- return "Code defined";
388
- else if (value === "true")
389
- return "Users must define an ID";
390
- else if (value === "optional")
391
- return "Users can define an ID, but it is not required";
392
- else
393
- return "Document ID is generated automatically";
394
- }}
395
- >
396
- <SelectItem value={"false"}>
397
- Document ID is generated automatically
398
- </SelectItem>
399
- <SelectItem value={"true"}>
400
- Users must define an ID
401
- </SelectItem>
402
- <SelectItem value={"optional"}>
403
- Users can define an ID, but it is not required
404
- </SelectItem>
405
- </Select>
406
- </div>
407
- <div className={"col-span-12 mt-4"}>
408
- <BooleanSwitchWithLabel
409
- position={"start"}
410
- size={"large"}
411
- label="Collection group"
412
- onValueChange={(v) => setFieldValue("collectionGroup", v)}
413
- value={values.collectionGroup ?? false}
414
- />
415
- <FieldCaption>
416
- A collection group consists of all collections with the same path. This allows
417
- you
418
- to query over multiple collections at once.
419
- </FieldCaption>
420
- </div>
421
- <div className={"col-span-12"}>
422
- <BooleanSwitchWithLabel
423
- position={"start"}
424
- size={"large"}
425
- label="Enable text search for this collection"
426
- onValueChange={(v) => setFieldValue("textSearchEnabled", v)}
427
- value={values.textSearchEnabled ?? false}
428
- />
429
- <FieldCaption>
430
- Allow text search for this collection. If you have not specified a text search
431
- delegate, this will use the built-in local text search. This is not recommended
432
- for large collections, as it may incur in performance and cost issues.
433
- </FieldCaption>
434
- </div>
435
-
436
-
437
- </div>
438
- </ExpandablePanel>
439
-
440
- {children}
441
393
 
442
394
  </div>
443
395
 
444
396
  </div>
445
397
 
446
- <div style={{ height: "52px" }}/>
398
+ <div style={{ height: "52px" }} />
447
399
 
448
400
  <Dialog
449
401
  open={iconDialogOpen}
@@ -453,10 +405,10 @@ export function CollectionDetailsForm({
453
405
  >
454
406
  <div className={"p-4 overflow-auto min-h-[200px]"}>
455
407
  <SearchIconsView selectedIcon={typeof values.icon === "string" ? values.icon : undefined}
456
- onIconSelected={(icon: string) => {
457
- setIconDialogOpen(false);
458
- setFieldValue("icon", icon);
459
- }}/>
408
+ onIconSelected={(icon: string) => {
409
+ setIconDialogOpen(false);
410
+ setFieldValue("icon", icon);
411
+ }} />
460
412
  </div>
461
413
 
462
414
  </Dialog>
@@ -467,18 +419,18 @@ export function CollectionDetailsForm({
467
419
  }
468
420
 
469
421
  function DefaultDatabaseField({
470
- databaseId,
471
- onDatabaseIdUpdate
472
- }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
422
+ databaseId,
423
+ onDatabaseIdUpdate
424
+ }: { databaseId?: string, onDatabaseIdUpdate: (databaseId: string) => void }) {
473
425
 
474
426
  return <Tooltip title={"Database ID"}
475
- side={"top"}
476
- align={"start"}>
427
+ side={"top"}
428
+ align={"start"}>
477
429
  <TextField size={"small"}
478
- invisible={true}
479
- inputClassName={"text-end"}
480
- value={databaseId ?? ""}
481
- onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
482
- placeholder={"(default)"}></TextField>
430
+ invisible={true}
431
+ inputClassName={"text-end"}
432
+ value={databaseId ?? ""}
433
+ onChange={(e: any) => onDatabaseIdUpdate(e.target.value)}
434
+ placeholder={"(default)"}></TextField>
483
435
  </Tooltip>
484
436
  }