@firecms/collection_editor 3.0.1 → 3.1.0-canary.9e89e98

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
@@ -1,6 +1,7 @@
1
- import React from "react";
2
- import { EntityCollection, prettifyIdentifier, } from "@firecms/core";
3
- import { Card, Chip, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
1
+ import React, { useState } from "react";
2
+ import { AIIcon, EntityCollection, prettifyIdentifier, } from "@firecms/core";
3
+ import { Button, Card, Chip, cls, CodeIcon, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
4
+ import { CollectionJsonImportDialog } from "./CollectionJsonImportDialog";
4
5
 
5
6
  import { productsCollectionTemplate } from "./templates/products_template";
6
7
  import { blogCollectionTemplate } from "./templates/blog_template";
@@ -9,17 +10,21 @@ import { ImportFileUpload } from "@firecms/data_import";
9
10
  import { pagesCollectionTemplate } from "./templates/pages_template";
10
11
  import { useFormex } from "@firecms/formex";
11
12
  import { useCollectionEditorController } from "../../useCollectionEditorController";
13
+ import { AICollectionGeneratorPopover } from "./AICollectionGeneratorPopover";
14
+ import { CollectionGenerationCallback } from "../../api/generateCollectionApi";
12
15
 
13
16
  export function CollectionEditorWelcomeView({
14
- path,
15
- parentCollection,
16
- onContinue,
17
- existingCollectionPaths
18
- }: {
17
+ path,
18
+ parentCollection,
19
+ onContinue,
20
+ existingCollectionPaths,
21
+ generateCollection
22
+ }: {
19
23
  path: string;
20
24
  parentCollection?: EntityCollection;
21
25
  onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
22
26
  existingCollectionPaths?: string[];
27
+ generateCollection?: CollectionGenerationCallback;
23
28
  }) {
24
29
 
25
30
  const { pathSuggestions } = useCollectionEditorController();
@@ -33,6 +38,8 @@ export function CollectionEditorWelcomeView({
33
38
  submitCount
34
39
  } = useFormex<EntityCollection>();
35
40
 
41
+ const [jsonImportOpen, setJsonImportOpen] = useState(false);
42
+
36
43
  return (
37
44
  <div className={"overflow-auto my-auto"}>
38
45
  <Container maxWidth={"4xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
@@ -53,22 +60,22 @@ export function CollectionEditorWelcomeView({
53
60
  {(filteredSuggestions ?? []).length > 0 && <div className={"my-2"}>
54
61
 
55
62
  <Typography variant={"caption"}
56
- color={"secondary"}>
63
+ color={"secondary"}>
57
64
  ● Use one of the existing paths in your database:
58
65
  </Typography>
59
66
  <div className={"flex flex-wrap gap-x-2 gap-y-1 items-center my-2 min-h-7"}>
60
67
 
61
68
  {filteredSuggestions?.map((suggestion, index) => (
62
69
  <Chip key={suggestion}
63
- colorScheme={"cyanLighter"}
64
- onClick={() => {
65
- setFieldValue("name", prettifyIdentifier(suggestion));
66
- setFieldValue("id", suggestion);
67
- setFieldValue("path", suggestion);
68
- setFieldValue("properties", undefined);
69
- onContinue();
70
- }}
71
- size="small">
70
+ colorScheme={"cyanLighter"}
71
+ onClick={() => {
72
+ setFieldValue("name", prettifyIdentifier(suggestion));
73
+ setFieldValue("id", suggestion);
74
+ setFieldValue("path", suggestion);
75
+ setFieldValue("properties", undefined);
76
+ onContinue();
77
+ }}
78
+ size="small">
72
79
  {suggestion}
73
80
  </Chip>
74
81
  ))}
@@ -76,61 +83,117 @@ export function CollectionEditorWelcomeView({
76
83
  </div>
77
84
 
78
85
  </div>}
86
+ <div className="flex flex-row gap-8">
79
87
 
80
- <div className={"my-2"}>
81
- <Typography variant={"caption"}
82
- color={"secondary"}>
83
- ● Select a template:
84
- </Typography>
85
-
86
- <div className={"flex gap-4"}>
87
- <TemplateButton title={"Products"}
88
- subtitle={"A collection of products with images, prices and stock"}
89
- icon={<Icon size={"small"}
90
- iconKey={productsCollectionTemplate.icon! as string}/>}
91
- onClick={() => {
92
- setValues(productsCollectionTemplate);
93
- onContinue();
94
- }}/>
95
- <TemplateButton title={"Users"}
96
- subtitle={"A collection of users with emails, names and roles"}
97
- icon={<Icon size={"small"} iconKey={usersCollectionTemplate.icon! as string}/>}
98
- onClick={() => {
99
- setValues(usersCollectionTemplate);
100
- onContinue();
101
- }}/>
102
- <TemplateButton title={"Blog posts"}
103
- subtitle={"A collection of blog posts with images, authors and complex content"}
104
- icon={<Icon size={"small"} iconKey={blogCollectionTemplate.icon! as string}/>}
105
- onClick={() => {
106
- setValues(blogCollectionTemplate);
107
- onContinue();
108
- }}/>
109
- <TemplateButton title={"Pages"}
110
- subtitle={"A collection of pages with images, authors and complex content"}
111
- icon={<Icon size={"small"} iconKey={pagesCollectionTemplate.icon! as string}/>}
112
- onClick={() => {
113
- setValues(pagesCollectionTemplate);
114
- onContinue();
115
- }}/>
88
+ {generateCollection && (
89
+ <div className={"my-2"}>
90
+ <Typography variant={"caption"}
91
+ color={"secondary"}
92
+ className={"mb-2"}>
93
+ ● Describe your collection to AI:
94
+ </Typography>
95
+
96
+ <AICollectionGeneratorPopover
97
+ onGenerated={(generatedCollection) => {
98
+ setValues(generatedCollection);
99
+ onContinue();
100
+ }}
101
+ generateCollection={generateCollection}
102
+ trigger={
103
+ <Button
104
+ variant="outlined"
105
+ startIcon={<AIIcon size="small" />}
106
+ >
107
+ Generate with AI
108
+ </Button>
109
+ }
110
+ />
111
+ </div>
112
+ )}
113
+
114
+ <div className={"my-2"}>
115
+ <Typography variant={"caption"}
116
+ color={"secondary"}
117
+ className={"mb-2"}>
118
+ Create from JSON configuration:
119
+ </Typography>
120
+
121
+ <Button
122
+ variant={"outlined"}
123
+ onClick={() => setJsonImportOpen(true)}
124
+ startIcon={<CodeIcon size="small" />}
125
+ >
126
+ Paste JSON Configuration
127
+ </Button>
128
+
129
+ <CollectionJsonImportDialog
130
+ open={jsonImportOpen}
131
+ onClose={() => setJsonImportOpen(false)}
132
+ onImport={(collection) => {
133
+ setValues(collection);
134
+ onContinue();
135
+ }}
136
+ />
116
137
  </div>
117
138
 
139
+
140
+
118
141
  </div>
119
142
 
143
+
120
144
  {!parentCollection && <div>
121
145
 
122
146
  <Typography variant={"caption"}
123
- color={"secondary"}
124
- className={"mb-2"}>
147
+ color={"secondary"}
148
+ className={"mb-2"}>
125
149
  ● Create a collection from a file (csv, json, xls, xslx...)
126
150
  </Typography>
127
151
 
128
- <ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
152
+ <ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)} />
129
153
 
130
154
  </div>}
131
155
 
156
+ <div className={"my-2"}>
157
+ <Typography variant={"caption"}
158
+ color={"secondary"}>
159
+ ● Select a template:
160
+ </Typography>
161
+
162
+ <div className={"flex gap-2"}>
163
+ <TemplateButton title={"Products"}
164
+ subtitle={"A collection of products with images, prices and stock"}
165
+ icon={<Icon size={"small"}
166
+ iconKey={productsCollectionTemplate.icon! as string} />}
167
+ onClick={() => {
168
+ setValues(productsCollectionTemplate);
169
+ onContinue();
170
+ }} />
171
+ <TemplateButton title={"Users"}
172
+ subtitle={"A collection of users with emails, names and roles"}
173
+ icon={<Icon size={"small"} iconKey={usersCollectionTemplate.icon! as string} />}
174
+ onClick={() => {
175
+ setValues(usersCollectionTemplate);
176
+ onContinue();
177
+ }} />
178
+ <TemplateButton title={"Blog posts"}
179
+ subtitle={"A collection of blog posts with images, authors and complex content"}
180
+ icon={<Icon size={"small"} iconKey={blogCollectionTemplate.icon! as string} />}
181
+ onClick={() => {
182
+ setValues(blogCollectionTemplate);
183
+ onContinue();
184
+ }} />
185
+ <TemplateButton title={"Pages"}
186
+ subtitle={"A collection of pages with images, authors and complex content"}
187
+ icon={<Icon size={"small"} iconKey={pagesCollectionTemplate.icon! as string} />}
188
+ onClick={() => {
189
+ setValues(pagesCollectionTemplate);
190
+ onContinue();
191
+ }} />
192
+ </div>
193
+
194
+ </div>
195
+
132
196
 
133
- {/*<div style={{ height: "52px" }}/>*/}
134
197
 
135
198
  </Container>
136
199
  </div>
@@ -138,11 +201,11 @@ export function CollectionEditorWelcomeView({
138
201
  }
139
202
 
140
203
  export function TemplateButton({
141
- title,
142
- subtitle,
143
- icon,
144
- onClick
145
- }: {
204
+ title,
205
+ subtitle,
206
+ icon,
207
+ onClick
208
+ }: {
146
209
  title: string,
147
210
  icon: React.ReactNode,
148
211
  subtitle: string,
@@ -151,12 +214,12 @@ export function TemplateButton({
151
214
 
152
215
  return (
153
216
  <Tooltip title={subtitle}
154
- asChild={true}>
217
+ asChild={true}>
155
218
  <Card
156
219
  onClick={onClick}
157
220
  className={cls(
158
- "my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
159
- "text-surface-700 dark:text-surface-accent-300",
221
+ "my-2 rounded-md border px-4 py-3 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
222
+ "text-text-secondary dark:text-text-secondary-dark",
160
223
  "hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
161
224
  "border-surface-400 dark:border-surface-600 "
162
225
  )}
@@ -164,7 +227,7 @@ export function TemplateButton({
164
227
  {icon}
165
228
  <div className={"flex flex-col items-start"}>
166
229
 
167
- <Typography variant={"subtitle1"}>
230
+ <Typography variant={"subtitle2"}>
168
231
  {title}
169
232
  </Typography>
170
233
 
@@ -0,0 +1,171 @@
1
+ import React, { useCallback, useState } from "react";
2
+ import {
3
+ Button,
4
+ cls,
5
+ CodeIcon,
6
+ Dialog,
7
+ DialogActions,
8
+ DialogContent,
9
+ DialogTitle,
10
+ Typography
11
+ } from "@firecms/ui";
12
+ import { EntityCollection } from "@firecms/core";
13
+ import { validateCollectionJson, CollectionValidationError } from "../../utils/validateCollectionJson";
14
+
15
+ const EXAMPLE_JSON = `{
16
+ "id": "products",
17
+ "name": "Products",
18
+ "path": "products",
19
+ "icon": "shopping_cart",
20
+ "properties": {
21
+ "name": {
22
+ "dataType": "string",
23
+ "name": "Name",
24
+ "validation": { "required": true }
25
+ },
26
+ "price": {
27
+ "dataType": "number",
28
+ "name": "Price"
29
+ },
30
+ "available": {
31
+ "dataType": "boolean",
32
+ "name": "Available"
33
+ }
34
+ }
35
+ }`;
36
+
37
+ export interface CollectionJsonImportDialogProps {
38
+ open: boolean;
39
+ onClose: () => void;
40
+ onImport: (collection: EntityCollection) => void;
41
+ }
42
+
43
+ export function CollectionJsonImportDialog({
44
+ open,
45
+ onClose,
46
+ onImport
47
+ }: CollectionJsonImportDialogProps) {
48
+ const [jsonValue, setJsonValue] = useState<string>("");
49
+ const [errors, setErrors] = useState<CollectionValidationError[]>([]);
50
+ const [touched, setTouched] = useState(false);
51
+
52
+ const handleJsonChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
53
+ const value = e.target.value;
54
+ setJsonValue(value);
55
+ setTouched(true);
56
+
57
+ if (!value.trim()) {
58
+ setErrors([]);
59
+ return;
60
+ }
61
+
62
+ const result = validateCollectionJson(value);
63
+ setErrors(result.errors);
64
+ }, []);
65
+
66
+ const handleImport = useCallback(() => {
67
+ const result = validateCollectionJson(jsonValue);
68
+ if (result.valid && result.collection) {
69
+ onImport(result.collection);
70
+ setJsonValue("");
71
+ setErrors([]);
72
+ setTouched(false);
73
+ onClose();
74
+ }
75
+ }, [jsonValue, onImport, onClose]);
76
+
77
+ const handleClose = useCallback(() => {
78
+ setJsonValue("");
79
+ setErrors([]);
80
+ setTouched(false);
81
+ onClose();
82
+ }, [onClose]);
83
+
84
+ const isValid = touched && jsonValue.trim() && errors.length === 0;
85
+
86
+ return (
87
+ <Dialog
88
+ open={open}
89
+ onOpenChange={(open) => !open && handleClose()}
90
+ maxWidth="2xl"
91
+ >
92
+ <DialogTitle className="flex items-center gap-2">
93
+ <CodeIcon size="small" />
94
+ Import Collection from JSON
95
+ </DialogTitle>
96
+ <DialogContent className="flex flex-col gap-4">
97
+ <Typography variant="body2" color="secondary">
98
+ Paste a JSON object representing your collection configuration.
99
+ The JSON must include <code className="bg-surface-200 dark:bg-surface-700 px-1 rounded">id</code>,
100
+ <code className="bg-surface-200 dark:bg-surface-700 px-1 rounded">name</code>,
101
+ <code className="bg-surface-200 dark:bg-surface-700 px-1 rounded">path</code>, and
102
+ <code className="bg-surface-200 dark:bg-surface-700 px-1 rounded">properties</code>.
103
+ </Typography>
104
+
105
+ <textarea
106
+ value={jsonValue}
107
+ onChange={handleJsonChange}
108
+ placeholder={EXAMPLE_JSON}
109
+ rows={12}
110
+ className={cls(
111
+ "w-full p-3 font-mono text-sm rounded-md border resize-none overflow-y-auto",
112
+ "bg-surface-50 dark:bg-surface-900",
113
+ "focus:outline-none focus:ring-2 focus:ring-primary",
114
+ "h-[300px]",
115
+ errors.length > 0 && touched
116
+ ? "border-red-500 dark:border-red-400"
117
+ : "border-surface-300 dark:border-surface-600"
118
+ )}
119
+ />
120
+
121
+ {errors.length > 0 && touched && (
122
+ <div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-md border border-red-200 dark:border-red-800">
123
+ <Typography variant="body2" className="font-medium text-red-700 dark:text-red-400 mb-2">
124
+ Validation errors:
125
+ </Typography>
126
+ <ul className="list-disc list-inside space-y-1">
127
+ {errors.map((error, index) => (
128
+ <li key={index} className="text-sm text-red-600 dark:text-red-400">
129
+ {error.path ? (
130
+ <>
131
+ <code className="bg-red-100 dark:bg-red-900/40 px-1 rounded">
132
+ {error.path}
133
+ </code>
134
+ : {error.message}
135
+ </>
136
+ ) : (
137
+ error.message
138
+ )}
139
+ </li>
140
+ ))}
141
+ </ul>
142
+ </div>
143
+ )}
144
+
145
+ {isValid && (
146
+ <div className="p-3 bg-green-50 dark:bg-green-900/20 rounded-md border border-green-200 dark:border-green-800">
147
+ <Typography variant="body2" className="text-green-700 dark:text-green-400">
148
+ ✓ JSON is valid and ready to import
149
+ </Typography>
150
+ </div>
151
+ )}
152
+ </DialogContent>
153
+ <DialogActions>
154
+ <Button
155
+ variant="text"
156
+ onClick={handleClose}
157
+ >
158
+ Cancel
159
+ </Button>
160
+ <Button
161
+ variant="filled"
162
+ color="primary"
163
+ onClick={handleImport}
164
+ disabled={!isValid}
165
+ >
166
+ Import Collection
167
+ </Button>
168
+ </DialogActions>
169
+ </Dialog>
170
+ );
171
+ }