@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
@@ -0,0 +1,243 @@
1
+ import React, { useState } from "react";
2
+ import { EntityCollection, useNavigationController, useSnackbarController, AIIcon, useTranslation } from "@firecms/core";
3
+ import {
4
+ Button,
5
+ CircularProgress,
6
+ IconButton,
7
+ Menu,
8
+ SendIcon,
9
+ TextField,
10
+ Typography
11
+ } from "@firecms/ui";
12
+ import {
13
+ CollectionGenerationCallback,
14
+ CollectionGenerationApiError,
15
+ CollectionOperation
16
+ } from "../../api/generateCollectionApi";
17
+ import { PersistedCollection } from "../../types/persisted_collection";
18
+
19
+ export interface AICollectionGeneratorPopoverProps {
20
+ /**
21
+ * Current collection being edited (if modifying an existing collection)
22
+ */
23
+ existingCollection?: PersistedCollection;
24
+
25
+ /**
26
+ * Callback when a collection is generated or modified.
27
+ * Includes the collection and optionally the operations that were applied.
28
+ */
29
+ onGenerated: (collection: EntityCollection, operations?: CollectionOperation[]) => void;
30
+
31
+ /**
32
+ * Callback function for generating/modifying collections.
33
+ * The plugin is API-agnostic - the consumer provides the implementation.
34
+ */
35
+ generateCollection: CollectionGenerationCallback;
36
+
37
+ /**
38
+ * Optional custom trigger button. If not provided, a default AI button is used.
39
+ */
40
+ trigger?: React.ReactNode;
41
+
42
+ /**
43
+ * Size of the button
44
+ */
45
+ size?: "small" | "medium" | "large";
46
+
47
+ /**
48
+ * Whether to show the label text
49
+ */
50
+ showLabel?: boolean;
51
+
52
+ /**
53
+ * Optional analytics callback
54
+ */
55
+ onAnalyticsEvent?: (event: string, params?: object) => void;
56
+ }
57
+
58
+ export function AICollectionGeneratorPopover({
59
+ existingCollection,
60
+ onGenerated,
61
+ generateCollection,
62
+ trigger,
63
+ size = "small",
64
+ showLabel = true,
65
+ onAnalyticsEvent
66
+ }: AICollectionGeneratorPopoverProps) {
67
+ const [menuOpen, setMenuOpen] = useState(false);
68
+ const [prompt, setPrompt] = useState("");
69
+ const [loading, setLoading] = useState(false);
70
+ const [error, setError] = useState<string | null>(null);
71
+
72
+ const { t } = useTranslation();
73
+ const navigation = useNavigationController();
74
+ const snackbarController = useSnackbarController();
75
+
76
+ const existingCollections = navigation.collections ?? [];
77
+
78
+ const handleGenerate = async () => {
79
+ if (!prompt.trim()) return;
80
+
81
+ setLoading(true);
82
+ setError(null);
83
+
84
+ const mode = existingCollection ? "modify" : "create";
85
+ onAnalyticsEvent?.("ai_collection_generate_start", { mode });
86
+
87
+ try {
88
+ const collectionsContext = existingCollections.map(c => ({
89
+ path: c.path,
90
+ id: c.id,
91
+ name: c.name,
92
+ properties: c.properties,
93
+ propertiesOrder: c.propertiesOrder
94
+ }));
95
+
96
+ const result = await generateCollection({
97
+ prompt: prompt.trim(),
98
+ existingCollections: collectionsContext.slice(0, 30),
99
+ ...(existingCollection && {
100
+ existingCollection: {
101
+ path: existingCollection.path,
102
+ id: existingCollection.id,
103
+ name: existingCollection.name,
104
+ properties: existingCollection.properties,
105
+ propertiesOrder: existingCollection.propertiesOrder
106
+ }
107
+ })
108
+ });
109
+
110
+ onGenerated(result.collection, result.operations);
111
+ setMenuOpen(false);
112
+ setPrompt("");
113
+ onAnalyticsEvent?.("ai_collection_generate_success", {
114
+ mode,
115
+ operationsCount: result.operations?.length
116
+ });
117
+ snackbarController.open({
118
+ type: "success",
119
+ message: existingCollection
120
+ ? "Collection updated with AI suggestions"
121
+ : "Collection generated successfully"
122
+ });
123
+ } catch (e) {
124
+ console.error("Error generating collection:", e);
125
+ const errorMessage = e instanceof CollectionGenerationApiError
126
+ ? e.message
127
+ : "Failed to generate collection. Please try again.";
128
+ setError(errorMessage);
129
+ onAnalyticsEvent?.("ai_collection_generate_error", {
130
+ mode,
131
+ error: errorMessage
132
+ });
133
+ snackbarController.open({
134
+ type: "error",
135
+ message: errorMessage
136
+ });
137
+ } finally {
138
+ setLoading(false);
139
+ }
140
+ };
141
+
142
+ const handleKeyDown = (e: React.KeyboardEvent) => {
143
+ if (e.key === "Enter" && !e.shiftKey) {
144
+ e.preventDefault();
145
+ handleGenerate();
146
+ }
147
+ };
148
+
149
+ const defaultTrigger = showLabel ? (
150
+ <Button
151
+ variant="text"
152
+ size={size}
153
+ disabled={loading}
154
+ startIcon={loading
155
+ ? <CircularProgress size="smallest" />
156
+ : <AIIcon size="small" />
157
+ }
158
+ >
159
+ {t("ai_assist")}
160
+ </Button>
161
+ ) : (
162
+ <IconButton
163
+ size={size}
164
+ disabled={loading}
165
+ aria-label={t("ai_assist")}
166
+ >
167
+ {loading
168
+ ? <CircularProgress size="smallest" />
169
+ : <AIIcon size="small" />
170
+ }
171
+ </IconButton>
172
+ );
173
+
174
+ return (
175
+ <Menu
176
+ open={menuOpen}
177
+ onOpenChange={(open) => {
178
+ setMenuOpen(open);
179
+ if (!open) {
180
+ setError(null);
181
+ }
182
+ }}
183
+ trigger={trigger ?? defaultTrigger}
184
+ >
185
+ <div className="p-4 flex flex-col gap-3 min-w-[360px] max-w-[480px]">
186
+ <div className="flex items-center gap-2">
187
+ <AIIcon size="small" />
188
+ <Typography variant="subtitle2">
189
+ {existingCollection ? t("modify_collection_with_ai") : t("generate_collection_with_ai")}
190
+ </Typography>
191
+ </div>
192
+
193
+ <Typography variant="caption" color="secondary">
194
+ {existingCollection
195
+ ? t("describe_changes_to_make")
196
+ : t("describe_collection_to_create")
197
+ }
198
+ </Typography>
199
+
200
+ <TextField
201
+ size="small"
202
+ multiline
203
+ autoFocus
204
+ className="w-full text-text-primary dark:text-text-primary-dark"
205
+ value={prompt}
206
+ onChange={(e) => setPrompt(e.target.value)}
207
+ onKeyDown={handleKeyDown}
208
+ placeholder={existingCollection
209
+ ? t("ai_placeholder_modify")
210
+ : t("ai_placeholder_create")
211
+ }
212
+ disabled={loading}
213
+ />
214
+
215
+ {error && (
216
+ <Typography variant="caption" className="text-red-500">
217
+ {error}
218
+ </Typography>
219
+ )}
220
+
221
+ <div className="flex justify-end gap-2">
222
+ <Button
223
+ variant="text"
224
+ size="small"
225
+ onClick={() => setMenuOpen(false)}
226
+ disabled={loading}
227
+ >
228
+ {t("cancel")}
229
+ </Button>
230
+ <Button
231
+ variant="filled"
232
+ size="small"
233
+ onClick={handleGenerate}
234
+ disabled={!prompt.trim() || loading}
235
+ startIcon={loading ? <CircularProgress size="smallest" /> : undefined}
236
+ >
237
+ {loading ? t("generating") : t("generate_with_ai")}
238
+ </Button>
239
+ </div>
240
+ </div>
241
+ </Menu>
242
+ );
243
+ }
@@ -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
+