@alpaca-editor/core 1.0.0

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 (239) hide show
  1. package/.prettierrc +3 -0
  2. package/eslint.config.mjs +4 -0
  3. package/images/bg-shape-black.webp +0 -0
  4. package/package.json +52 -0
  5. package/src/client-components/api.ts +6 -0
  6. package/src/client-components/index.ts +19 -0
  7. package/src/components/ActionButton.tsx +43 -0
  8. package/src/components/Error.tsx +57 -0
  9. package/src/config/config.tsx +737 -0
  10. package/src/config/types.ts +263 -0
  11. package/src/editor/ComponentInfo.tsx +77 -0
  12. package/src/editor/ConfirmationDialog.tsx +103 -0
  13. package/src/editor/ContentTree.tsx +654 -0
  14. package/src/editor/ContextMenu.tsx +155 -0
  15. package/src/editor/Editor.tsx +91 -0
  16. package/src/editor/EditorWarning.tsx +34 -0
  17. package/src/editor/EditorWarnings.tsx +33 -0
  18. package/src/editor/FieldEditorPopup.tsx +65 -0
  19. package/src/editor/FieldHistory.tsx +74 -0
  20. package/src/editor/FieldList.tsx +190 -0
  21. package/src/editor/FieldListField.tsx +387 -0
  22. package/src/editor/FieldListFieldWithFallbacks.tsx +211 -0
  23. package/src/editor/FloatingToolbar.tsx +163 -0
  24. package/src/editor/ImageEditor.tsx +129 -0
  25. package/src/editor/InsertMenu.tsx +332 -0
  26. package/src/editor/ItemInfo.tsx +90 -0
  27. package/src/editor/LinkEditorDialog.tsx +192 -0
  28. package/src/editor/MainLayout.tsx +94 -0
  29. package/src/editor/NewEditorClient.tsx +11 -0
  30. package/src/editor/PictureCropper.tsx +505 -0
  31. package/src/editor/PictureEditor.tsx +206 -0
  32. package/src/editor/PictureEditorDialog.tsx +381 -0
  33. package/src/editor/PublishDialog.ignore +74 -0
  34. package/src/editor/ScrollingContentTree.tsx +47 -0
  35. package/src/editor/Terminal.tsx +215 -0
  36. package/src/editor/Titlebar.tsx +23 -0
  37. package/src/editor/ai/AiPopup.tsx +59 -0
  38. package/src/editor/ai/AiResponseMessage.tsx +82 -0
  39. package/src/editor/ai/AiTerminal.tsx +450 -0
  40. package/src/editor/ai/AiToolCall.tsx +46 -0
  41. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  42. package/src/editor/ai/editorAiContext.ts +18 -0
  43. package/src/editor/client/DialogContext.tsx +49 -0
  44. package/src/editor/client/EditorClient.tsx +1831 -0
  45. package/src/editor/client/GenericDialog.tsx +50 -0
  46. package/src/editor/client/editContext.ts +330 -0
  47. package/src/editor/client/helpers.ts +44 -0
  48. package/src/editor/client/itemsRepository.ts +391 -0
  49. package/src/editor/client/operations.ts +610 -0
  50. package/src/editor/client/pageModelBuilder.ts +182 -0
  51. package/src/editor/commands/commands.ts +23 -0
  52. package/src/editor/commands/componentCommands.tsx +408 -0
  53. package/src/editor/commands/createVersionCommand.ts +33 -0
  54. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  55. package/src/editor/commands/itemCommands.tsx +186 -0
  56. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  57. package/src/editor/commands/undo.ts +39 -0
  58. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  59. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  60. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  61. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  62. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  63. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  64. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  65. package/src/editor/component-designer/PlaceholdersEditor.tsx +183 -0
  66. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  67. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  68. package/src/editor/component-designer/aiContext.ts +23 -0
  69. package/src/editor/componentTreeHelper.tsx +114 -0
  70. package/src/editor/control-center/ControlCenterMenu.tsx +71 -0
  71. package/src/editor/control-center/IndexOverview.tsx +50 -0
  72. package/src/editor/control-center/IndexSettings.tsx +266 -0
  73. package/src/editor/control-center/Status.tsx +7 -0
  74. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  75. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  76. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  77. package/src/editor/editor-warnings/NoWriteAccess.tsx +15 -0
  78. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  79. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  80. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  81. package/src/editor/field-types/DropLinkEditor.tsx +75 -0
  82. package/src/editor/field-types/DropListEditor.tsx +84 -0
  83. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  84. package/src/editor/field-types/InternalLinkFieldEditor.tsx +112 -0
  85. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  86. package/src/editor/field-types/MultiLineText.tsx +63 -0
  87. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  88. package/src/editor/field-types/RawEditor.tsx +53 -0
  89. package/src/editor/field-types/ReactQuill.tsx +580 -0
  90. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  91. package/src/editor/field-types/RichTextEditorComponent.tsx +108 -0
  92. package/src/editor/field-types/SingleLineText.tsx +150 -0
  93. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  94. package/src/editor/fieldTypes.ts +140 -0
  95. package/src/editor/media-selector/AiImageSearch.tsx +186 -0
  96. package/src/editor/media-selector/AiImageSearchPrompt.tsx +95 -0
  97. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  98. package/src/editor/media-selector/Preview.tsx +14 -0
  99. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  100. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  101. package/src/editor/media-selector/UploadZone.tsx +137 -0
  102. package/src/editor/menubar/ActionsMenu.tsx +47 -0
  103. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  104. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  105. package/src/editor/menubar/BrowseHistory.tsx +37 -0
  106. package/src/editor/menubar/ItemLanguageVersion.tsx +52 -0
  107. package/src/editor/menubar/LanguageSelector.tsx +152 -0
  108. package/src/editor/menubar/Menu.tsx +83 -0
  109. package/src/editor/menubar/NavButtons.tsx +74 -0
  110. package/src/editor/menubar/PageSelector.tsx +139 -0
  111. package/src/editor/menubar/PageViewerControls.tsx +99 -0
  112. package/src/editor/menubar/Separator.tsx +12 -0
  113. package/src/editor/menubar/SiteInfo.tsx +53 -0
  114. package/src/editor/menubar/User.tsx +27 -0
  115. package/src/editor/menubar/VersionSelector.tsx +143 -0
  116. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +287 -0
  117. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  118. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +44 -0
  119. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  120. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  121. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  122. package/src/editor/page-editor-chrome/FrameMenu.tsx +263 -0
  123. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  124. package/src/editor/page-editor-chrome/InlineEditor.tsx +147 -0
  125. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  126. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  127. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +119 -0
  128. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +154 -0
  129. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +171 -0
  130. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +233 -0
  131. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  132. package/src/editor/page-viewer/EditorForm.tsx +247 -0
  133. package/src/editor/page-viewer/MiniMap.tsx +351 -0
  134. package/src/editor/page-viewer/PageViewer.tsx +127 -0
  135. package/src/editor/page-viewer/PageViewerFrame.tsx +1030 -0
  136. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  137. package/src/editor/pageModel.ts +191 -0
  138. package/src/editor/picture-shared.tsx +53 -0
  139. package/src/editor/reviews/Comment.tsx +265 -0
  140. package/src/editor/reviews/Comments.tsx +50 -0
  141. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  142. package/src/editor/reviews/Reviews.tsx +280 -0
  143. package/src/editor/reviews/reviewCommands.tsx +47 -0
  144. package/src/editor/reviews/useReviews.tsx +70 -0
  145. package/src/editor/services/aiService.ts +155 -0
  146. package/src/editor/services/componentDesignerService.ts +151 -0
  147. package/src/editor/services/contentService.ts +159 -0
  148. package/src/editor/services/editService.ts +462 -0
  149. package/src/editor/services/indexService.ts +24 -0
  150. package/src/editor/services/reviewsService.ts +45 -0
  151. package/src/editor/services/serviceHelper.ts +95 -0
  152. package/src/editor/services/systemService.ts +5 -0
  153. package/src/editor/services/translationService.ts +21 -0
  154. package/src/editor/services-server/api.ts +150 -0
  155. package/src/editor/services-server/graphQL.ts +106 -0
  156. package/src/editor/sidebar/ComponentPalette.tsx +146 -0
  157. package/src/editor/sidebar/ComponentTree.tsx +512 -0
  158. package/src/editor/sidebar/ComponentTree2.tsxx +490 -0
  159. package/src/editor/sidebar/Debug.tsx +105 -0
  160. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  161. package/src/editor/sidebar/EditHistory.tsx +134 -0
  162. package/src/editor/sidebar/GraphQL.tsx +164 -0
  163. package/src/editor/sidebar/Insert.tsx +35 -0
  164. package/src/editor/sidebar/MainContentTree.tsx +95 -0
  165. package/src/editor/sidebar/Performance.tsx +53 -0
  166. package/src/editor/sidebar/Sessions.tsx +35 -0
  167. package/src/editor/sidebar/Sidebar.tsx +20 -0
  168. package/src/editor/sidebar/SidebarView.tsx +150 -0
  169. package/src/editor/sidebar/Translations.tsx +276 -0
  170. package/src/editor/sidebar/Validation.tsx +102 -0
  171. package/src/editor/sidebar/ViewSelector.tsx +49 -0
  172. package/src/editor/sidebar/Workbox.tsx +209 -0
  173. package/src/editor/ui/CenteredMessage.tsx +7 -0
  174. package/src/editor/ui/CopyToClipboardButton.tsx +23 -0
  175. package/src/editor/ui/DialogButtons.tsx +11 -0
  176. package/src/editor/ui/Icons.tsx +585 -0
  177. package/src/editor/ui/ItemNameDialog.tsx +94 -0
  178. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  179. package/src/editor/ui/ItemSearch.tsx +173 -0
  180. package/src/editor/ui/PerfectTree.tsx +550 -0
  181. package/src/editor/ui/Section.tsx +35 -0
  182. package/src/editor/ui/SimpleIconButton.tsx +43 -0
  183. package/src/editor/ui/SimpleMenu.tsx +48 -0
  184. package/src/editor/ui/SimpleTable.tsx +63 -0
  185. package/src/editor/ui/SimpleTabs.tsx +55 -0
  186. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  187. package/src/editor/ui/Spinner.tsx +7 -0
  188. package/src/editor/ui/Splitter.tsx +247 -0
  189. package/src/editor/ui/StackedPanels.tsx +134 -0
  190. package/src/editor/ui/Toolbar.tsx +7 -0
  191. package/src/editor/utils/id-helper.ts +3 -0
  192. package/src/editor/utils/insertOptions.ts +69 -0
  193. package/src/editor/utils/itemutils.ts +29 -0
  194. package/src/editor/utils/useMemoDebug.ts +28 -0
  195. package/src/editor/utils.ts +435 -0
  196. package/src/editor/views/CompareView.tsx +256 -0
  197. package/src/editor/views/EditView.tsx +27 -0
  198. package/src/editor/views/ItemEditor.tsx +58 -0
  199. package/src/editor/views/SingleEditView.tsx +44 -0
  200. package/src/fonts/Geist-Black.woff2 +0 -0
  201. package/src/fonts/Geist-Bold.woff2 +0 -0
  202. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  203. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  204. package/src/fonts/Geist-Light.woff2 +0 -0
  205. package/src/fonts/Geist-Medium.woff2 +0 -0
  206. package/src/fonts/Geist-Regular.woff2 +0 -0
  207. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  208. package/src/fonts/Geist-Thin.woff2 +0 -0
  209. package/src/fonts/Geist[wght].woff2 +0 -0
  210. package/src/index.ts +7 -0
  211. package/src/page-wizard/PageWizard.tsx +163 -0
  212. package/src/page-wizard/SelectWizard.tsx +109 -0
  213. package/src/page-wizard/WizardSteps.tsx +207 -0
  214. package/src/page-wizard/service.ts +35 -0
  215. package/src/page-wizard/startPageWizardCommand.ts +27 -0
  216. package/src/page-wizard/steps/BuildPageStep.tsx +266 -0
  217. package/src/page-wizard/steps/CollectStep.tsx +233 -0
  218. package/src/page-wizard/steps/ComponentTypesSelector.tsx +443 -0
  219. package/src/page-wizard/steps/Components.tsx +193 -0
  220. package/src/page-wizard/steps/CreatePage.tsx +285 -0
  221. package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +384 -0
  222. package/src/page-wizard/steps/EditButton.tsx +34 -0
  223. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  224. package/src/page-wizard/steps/Generate.tsx +32 -0
  225. package/src/page-wizard/steps/ImagesStep.tsx +318 -0
  226. package/src/page-wizard/steps/LayoutStep.tsx +228 -0
  227. package/src/page-wizard/steps/SelectStep.tsx +256 -0
  228. package/src/page-wizard/steps/schema.ts +180 -0
  229. package/src/page-wizard/steps/usePageCreator.ts +279 -0
  230. package/src/splash-screen/NewPage.tsx +232 -0
  231. package/src/splash-screen/SectionHeadline.tsx +21 -0
  232. package/src/splash-screen/SplashScreen.tsx +156 -0
  233. package/src/tour/Tour.tsx +558 -0
  234. package/src/tour/default-tour.tsx +300 -0
  235. package/src/tour/preview-tour.tsx +127 -0
  236. package/src/types.ts +302 -0
  237. package/styles.css +476 -0
  238. package/tsconfig.build.json +21 -0
  239. package/tsconfig.json +11 -0
@@ -0,0 +1,443 @@
1
+ import { Dispatch, SetStateAction, useEffect, useState } from "react";
2
+ import { WizardStep } from "../PageWizard";
3
+ import {
4
+ PageSchema,
5
+ SchemaComponent,
6
+ SchemaPlaceholder,
7
+ WizardData,
8
+ } from "../PageWizard";
9
+
10
+ // Component Type and Placeholder Selection UI
11
+ export function ComponentTypeSelector({
12
+ selectedComponentTypes,
13
+ setSelectedComponentTypes,
14
+ schema,
15
+ data,
16
+ setData,
17
+ step,
18
+ }: {
19
+ selectedComponentTypes?: string[];
20
+ setSelectedComponentTypes: Dispatch<SetStateAction<string[]>>;
21
+ schema: PageSchema | string;
22
+ data: WizardData;
23
+ setData: (data: WizardData) => void;
24
+ step: WizardStep;
25
+ }) {
26
+ const [availableComponentTypes, setAvailableComponentTypes] = useState<
27
+ string[]
28
+ >([]);
29
+ const [isSettingsPanelCollapsed, setIsSettingsPanelCollapsed] =
30
+ useState(true);
31
+ const [preselectedTypes, setPreselectedTypes] = useState<string[]>([]);
32
+ const [searchFilter, setSearchFilter] = useState<string>("");
33
+
34
+ // Handle select all component types
35
+ const handleSelectAllComponents = () => {
36
+ setSelectedComponentTypes(availableComponentTypes);
37
+ setData({
38
+ ...data,
39
+ selectedComponentTypes: availableComponentTypes,
40
+ });
41
+ };
42
+
43
+ // Handle clear all component types
44
+ const handleClearAllComponents = () => {
45
+ setSelectedComponentTypes([]);
46
+ setData({
47
+ ...data,
48
+ selectedComponentTypes: [],
49
+ });
50
+ };
51
+
52
+ // Toggle component type selection
53
+ const toggleComponentType = (type: string) => {
54
+ setSelectedComponentTypes((prevSelected: string[]) => {
55
+ const newSelected = prevSelected.includes(type)
56
+ ? prevSelected.filter((t: string) => t !== type)
57
+ : [...prevSelected, type];
58
+
59
+ // Save selected types to wizard data
60
+ setData({
61
+ ...data,
62
+ selectedComponentTypes: newSelected,
63
+ });
64
+
65
+ return newSelected;
66
+ });
67
+ };
68
+
69
+ // Filter available components by search term
70
+ const filteredComponentTypes = availableComponentTypes.filter((type) =>
71
+ type.toLowerCase().includes(searchFilter.toLowerCase())
72
+ );
73
+
74
+ // Helper function to recursively extract component types
75
+ const extractComponentTypes = (
76
+ items: PageSchema | SchemaComponent[]
77
+ ): string[] => {
78
+ if (!items || !Array.isArray(items)) return [];
79
+
80
+ let types: string[] = [];
81
+
82
+ for (const item of items) {
83
+ // Add the current component type if it exists
84
+ if ("type" in item) {
85
+ types.push(item.type);
86
+ }
87
+
88
+ // Process components in placeholders recursively
89
+ if ("placeholders" in item && Array.isArray(item.placeholders)) {
90
+ for (const placeholder of item.placeholders) {
91
+ if (placeholder.components && Array.isArray(placeholder.components)) {
92
+ types = [
93
+ ...types,
94
+ ...extractComponentTypes(placeholder.components),
95
+ ];
96
+ }
97
+ }
98
+ }
99
+
100
+ // Process components directly (for top-level structure)
101
+ if ("components" in item && Array.isArray(item.components)) {
102
+ types = [...types, ...extractComponentTypes(item.components)];
103
+ }
104
+ }
105
+
106
+ return types;
107
+ };
108
+
109
+ // Helper function to extract placeholders from the schema
110
+ const extractPlaceholders = (
111
+ items: PageSchema | SchemaComponent[]
112
+ ): string[] => {
113
+ if (!items || !Array.isArray(items)) return [];
114
+
115
+ let placeholders: string[] = [];
116
+
117
+ for (const item of items) {
118
+ // Add the current placeholder name if it exists
119
+ if ("name" in item) {
120
+ placeholders.push(item.name);
121
+ }
122
+
123
+ // Handle SchemaComponent objects
124
+ if ("placeholders" in item && Array.isArray(item.placeholders)) {
125
+ // Extract names from each placeholder
126
+ item.placeholders.forEach((placeholder: SchemaPlaceholder) => {
127
+ placeholders.push(placeholder.name);
128
+
129
+ // Recursively process components in this placeholder
130
+ if (placeholder.components && Array.isArray(placeholder.components)) {
131
+ placeholders = [
132
+ ...placeholders,
133
+ ...extractPlaceholders(placeholder.components),
134
+ ];
135
+ }
136
+ });
137
+ }
138
+
139
+ // Handle SchemaPlaceholder objects
140
+ if ("components" in item && Array.isArray(item.components)) {
141
+ placeholders = [
142
+ ...placeholders,
143
+ ...extractPlaceholders(item.components),
144
+ ];
145
+ }
146
+ }
147
+
148
+ return placeholders;
149
+ };
150
+
151
+ // Initialize selections based on data or preselected values
152
+ const initializeSelections = (
153
+ uniqueTypes: string[],
154
+ stepPreselectedTypes: string[]
155
+ ) => {
156
+ // Initialize selected types
157
+ if (
158
+ data.selectedComponentTypes &&
159
+ Array.isArray(data.selectedComponentTypes)
160
+ ) {
161
+ // Filter out any types that are no longer available
162
+ const validSelectedTypes = data.selectedComponentTypes.filter((type) =>
163
+ uniqueTypes.includes(type)
164
+ );
165
+ setSelectedComponentTypes(
166
+ validSelectedTypes.length > 0
167
+ ? validSelectedTypes
168
+ : stepPreselectedTypes.length > 0
169
+ ? stepPreselectedTypes
170
+ : uniqueTypes
171
+ );
172
+ } else if (stepPreselectedTypes.length > 0) {
173
+ // Use preselected types from step
174
+ setSelectedComponentTypes(stepPreselectedTypes);
175
+ // Update data for persistence
176
+ setData((prev: WizardData) => ({
177
+ ...prev,
178
+ selectedComponentTypes: stepPreselectedTypes,
179
+ }));
180
+ } else {
181
+ // Default to selecting all component types
182
+ setSelectedComponentTypes(uniqueTypes);
183
+ // Update data for persistence
184
+ setData((prev: WizardData) => ({
185
+ ...prev,
186
+ selectedComponentTypes: uniqueTypes,
187
+ }));
188
+ }
189
+ };
190
+
191
+ // Extract component types and placeholders from wizard schema when available
192
+ useEffect(() => {
193
+ if (schema) {
194
+ try {
195
+ // Parse schema if it's a string
196
+ schema =
197
+ typeof schema === "string"
198
+ ? (JSON.parse(schema) as PageSchema)
199
+ : schema;
200
+
201
+ // Extract component types
202
+ const componentTypes = extractComponentTypes(schema);
203
+
204
+ // Remove duplicates and sort alphabetically
205
+ const uniqueTypes = Array.from(
206
+ new Set(componentTypes)
207
+ ).sort() as string[];
208
+
209
+ setAvailableComponentTypes(uniqueTypes);
210
+
211
+ // Parse preselected components from step if available
212
+ let stepPreselectedTypes: string[] = [];
213
+ if (step["preselectedComponents"]) {
214
+ // Split by comma or newline and trim whitespace
215
+ stepPreselectedTypes = step["preselectedComponents"]
216
+ .split(/[,\n\r]+/)
217
+ .map((type: string) => type.trim())
218
+ .filter((type: string) => type && uniqueTypes.includes(type)); // Only include valid types
219
+ }
220
+
221
+ // Set preselected types for UI differentiation
222
+ setPreselectedTypes(stepPreselectedTypes);
223
+
224
+ // Parse preselected placeholders from step if available
225
+
226
+ // Initialize component types and placeholders
227
+ initializeSelections(uniqueTypes, stepPreselectedTypes);
228
+ } catch (error) {
229
+ console.error("Error processing schema:", error);
230
+ }
231
+ }
232
+ }, [schema, step]);
233
+
234
+ return (
235
+ <div className="pb-3 mt-3">
236
+ <div className="flex justify-between items-center">
237
+ <div>
238
+ <h3 className="text-sm font-medium">
239
+ Layout Generation Settings
240
+ </h3>
241
+ <div className="text-xs text-gray-500">
242
+ {selectedComponentTypes &&
243
+ selectedComponentTypes.length > 0 &&
244
+ `${selectedComponentTypes.length} component types`}
245
+ {selectedComponentTypes &&
246
+ selectedComponentTypes.length === 0 &&
247
+ "Configure what the AI will use to generate the layout"}
248
+ </div>
249
+ </div>
250
+ <button
251
+ onClick={() => setIsSettingsPanelCollapsed(!isSettingsPanelCollapsed)}
252
+ className="bg-gray-200 text-gray-700 p-1 rounded flex items-center justify-center w-6 h-6 hover:bg-gray-300"
253
+ title={
254
+ isSettingsPanelCollapsed ? "Expand settings" : "Collapse settings"
255
+ }
256
+ aria-expanded={!isSettingsPanelCollapsed}
257
+ >
258
+ {isSettingsPanelCollapsed ? (
259
+ <span className="text-lg">↓</span>
260
+ ) : (
261
+ <span className="text-lg">↑</span>
262
+ )}
263
+ </button>
264
+ </div>
265
+
266
+ {!isSettingsPanelCollapsed && (
267
+ <>
268
+ <div className="mb-2">
269
+ <input
270
+ type="text"
271
+ value={searchFilter}
272
+ onChange={(e) => setSearchFilter(e.target.value)}
273
+ placeholder="Search components and placeholders..."
274
+ className="w-full px-3 py-1 border border-gray-300 rounded text-sm focus:outline-none focus:ring-1 focus:ring-blue-500"
275
+ />
276
+ </div>
277
+
278
+ <div className="flex flex-col gap-3 mb-3">
279
+ {/* Component Types Section */}
280
+ <div className="flex-1 min-w-0 border border-gray-200 rounded p-2 bg-white">
281
+ <div className="flex justify-between items-center mb-2">
282
+ <h4 className="text-sm font-bold">Component Types</h4>
283
+ <span className="text-xs text-gray-500 px-2 py-1 rounded-full bg-gray-200">
284
+ {selectedComponentTypes && selectedComponentTypes.length}
285
+ of {availableComponentTypes.length} selected
286
+ </span>
287
+ </div>
288
+
289
+ {/* Selected component tags */}
290
+ <div className="mb-2">
291
+ <div className="flex flex-wrap gap-1 mb-2">
292
+ {selectedComponentTypes &&
293
+ selectedComponentTypes.length > 0 ? (
294
+ selectedComponentTypes.map((type) => (
295
+ <div
296
+ key={type}
297
+ className={`px-2 py-1 text-xs rounded-full flex items-center ${
298
+ preselectedTypes.includes(type)
299
+ ? "bg-green-100 text-green-800"
300
+ : "bg-blue-100 text-blue-800"
301
+ }`}
302
+ >
303
+ {type}
304
+ <button
305
+ onClick={() => toggleComponentType(type)}
306
+ className={`ml-1 ${
307
+ preselectedTypes.includes(type)
308
+ ? "text-green-600 hover:text-green-800"
309
+ : "text-blue-600 hover:text-blue-800"
310
+ }`}
311
+ aria-label={`Remove ${type}`}
312
+ >
313
+ ×
314
+ </button>
315
+ </div>
316
+ ))
317
+ ) : (
318
+ <div className="text-xs text-gray-500 italic">
319
+ No component types selected
320
+ </div>
321
+ )}
322
+ </div>
323
+ </div>
324
+
325
+ {/* Component list */}
326
+ <div
327
+ className={`max-h-28 overflow-y-auto p-1 bg-white rounded border border-gray-200 mb-2 ${
328
+ filteredComponentTypes.length === 0
329
+ ? "flex justify-center items-center"
330
+ : ""
331
+ }`}
332
+ >
333
+ {filteredComponentTypes.length > 0 ? (
334
+ <div className="grid grid-cols-3 gap-1">
335
+ {filteredComponentTypes.map((type) => {
336
+ const isSelected =
337
+ selectedComponentTypes &&
338
+ selectedComponentTypes.includes(type);
339
+ const isPreselected = preselectedTypes.includes(type);
340
+ return (
341
+ <label
342
+ key={type}
343
+ className={`flex items-center cursor-pointer p-1 rounded-md text-sm border ${
344
+ isSelected
345
+ ? isPreselected
346
+ ? "bg-green-50 text-green-700 border-green-200"
347
+ : "bg-blue-50 text-blue-700 border-blue-200"
348
+ : isPreselected
349
+ ? "bg-green-50/30 text-green-700/70 border-green-200 hover:bg-green-50/50"
350
+ : "hover:bg-gray-100 border-transparent"
351
+ }`}
352
+ >
353
+ <input
354
+ type="checkbox"
355
+ checked={isSelected}
356
+ onChange={() => toggleComponentType(type)}
357
+ className="mr-2"
358
+ />
359
+ {type}
360
+ {isPreselected && (
361
+ <span
362
+ className="ml-1 text-xs text-green-600"
363
+ title="Recommended by template"
364
+ >
365
+
366
+ </span>
367
+ )}
368
+ </label>
369
+ );
370
+ })}
371
+ </div>
372
+ ) : (
373
+ <div className="text-gray-500 text-sm p-4">
374
+ {searchFilter
375
+ ? "No matching component types found"
376
+ : "No component types available"}
377
+ </div>
378
+ )}
379
+ </div>
380
+
381
+ {/* Component type action buttons */}
382
+ <div className="flex gap-1">
383
+ <button
384
+ onClick={handleSelectAllComponents}
385
+ disabled={
386
+ selectedComponentTypes &&
387
+ selectedComponentTypes.length ===
388
+ availableComponentTypes.length
389
+ }
390
+ className="flex-1 bg-gray-200 text-gray-700 px-2 py-1 rounded text-xs hover:bg-gray-300 disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed"
391
+ >
392
+ Select All
393
+ </button>
394
+ <button
395
+ onClick={handleClearAllComponents}
396
+ disabled={
397
+ selectedComponentTypes &&
398
+ selectedComponentTypes.length === 0
399
+ }
400
+ className="flex-1 bg-gray-200 text-gray-700 px-2 py-1 rounded text-xs hover:bg-gray-300 disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed"
401
+ >
402
+ Clear All
403
+ </button>
404
+ </div>
405
+ {/* Generate button and selection summary - always visible */}
406
+ <div className="w-full flex gap-2">
407
+ {/* Use recommended buttons - only show if not collapsed or if none are selected */}
408
+ {!isSettingsPanelCollapsed ||
409
+ (selectedComponentTypes &&
410
+ selectedComponentTypes.length === 0 &&
411
+ preselectedTypes.length > 0) || (
412
+ <div className="flex gap-1">
413
+ {preselectedTypes.length > 0 && (
414
+ <button
415
+ onClick={() => {
416
+ setSelectedComponentTypes(preselectedTypes);
417
+ setData({
418
+ ...data,
419
+ selectedComponentTypes: preselectedTypes,
420
+ });
421
+ }}
422
+ disabled={
423
+ preselectedTypes.length ===
424
+ (selectedComponentTypes?.length || 0) &&
425
+ preselectedTypes.every((type) =>
426
+ selectedComponentTypes?.includes(type)
427
+ )
428
+ }
429
+ className="flex-1 bg-green-500 text-white px-2 py-1 rounded text-xs hover:bg-green-600 disabled:bg-gray-100 disabled:text-gray-400 disabled:cursor-not-allowed"
430
+ >
431
+ Use Recommended Components
432
+ </button>
433
+ )}
434
+ </div>
435
+ )}
436
+ </div>
437
+ </div>
438
+ </div>
439
+ </>
440
+ )}
441
+ </div>
442
+ );
443
+ }
@@ -0,0 +1,193 @@
1
+ import { useEditContext } from "../../editor/client/editContext";
2
+ import { Thumbnail } from "../../editor/media-selector/Thumbnails";
3
+ import { SimpleIconButton } from "../../editor/ui/SimpleIconButton";
4
+ import {
5
+ WizardField,
6
+ WizardPageComponent,
7
+ WizardPageModel,
8
+ } from "../PageWizard";
9
+ import { FieldEditor } from "./FieldEditor";
10
+ import React from "react";
11
+
12
+ export function Components({
13
+ pageModel,
14
+ onFieldEdited,
15
+ onComponentRemoved,
16
+ thumbnails,
17
+ setInternalState,
18
+ }: {
19
+ pageModel?: WizardPageModel;
20
+ onFieldEdited: () => void;
21
+ onComponentRemoved: (component: WizardPageComponent) => void;
22
+ thumbnails: Thumbnail[];
23
+ setInternalState: (state: any) => void;
24
+ }) {
25
+ const editContext = useEditContext();
26
+ if (!pageModel) return <div>No layout generated yet</div>;
27
+
28
+ // Simplified component to only render component hierarchy
29
+ const SimpleLayoutItem = ({
30
+ components,
31
+ depth = 0,
32
+ }: {
33
+ components: WizardPageComponent[];
34
+ depth?: number;
35
+ }) => {
36
+ if (!components) return null;
37
+
38
+ // Generate a random pastel background color based on component type
39
+ const getColorForType = (type: string) => {
40
+ // Simple hash function to generate consistent colors for the same type
41
+ const hash = type
42
+ .split("")
43
+ .reduce((acc, char) => acc + char.charCodeAt(0), 0);
44
+ const hue = hash % 360;
45
+ return `hsl(${hue}, 70%, 90%)`;
46
+ };
47
+
48
+ const getThumbnailUrl = (mediaId: string) => {
49
+ if (!thumbnails) return;
50
+ return thumbnails.find((thumbnail) => thumbnail.id === mediaId)?.thumbUrl;
51
+ };
52
+
53
+ const getImage = (field: WizardField) => {
54
+ const url = getThumbnailUrl(field.value);
55
+ if (url) {
56
+ return <img src={url} alt={field.name} className="mb-2" />;
57
+ }
58
+ return field.value;
59
+ };
60
+
61
+ // Handle component with placeholders
62
+ const renderComponent = (
63
+ component: WizardPageComponent,
64
+ collection: WizardPageComponent[]
65
+ ) => {
66
+ const indent = depth * 20;
67
+ const componentType = component.type || "Unknown";
68
+ const borderColor = component.type
69
+ ? getColorForType(componentType)
70
+ : "#f0f0f0";
71
+
72
+ return (
73
+ <div
74
+ key={component.id}
75
+ className="relative"
76
+ style={{
77
+ marginBottom: "8px",
78
+ marginLeft: indent,
79
+ }}
80
+ >
81
+ <div className="absolute top-0 right-0">
82
+ <SimpleIconButton
83
+ icon="pi pi-times"
84
+ label="Remove"
85
+ onClick={() => {
86
+ // Remove component from its collection
87
+ const index = collection.findIndex(
88
+ (c) => c.id === component.id
89
+ );
90
+ if (index !== -1) {
91
+ collection.splice(index, 1);
92
+ }
93
+ onComponentRemoved(component);
94
+ }}
95
+ />
96
+ </div>
97
+ <div
98
+ className="mb-2 px-2 py-1 rounded-md bg-white border-2"
99
+ style={{
100
+ borderColor,
101
+ }}
102
+ >
103
+ <div className="mb-2">
104
+ <span className="text-gray-600">{component.type}</span>:{" "}
105
+ {component.name}
106
+ </div>
107
+ <div className="text-xs text-gray-500">
108
+ {component.fields?.map((field, index) =>
109
+ field.type === "Picture" || field.type === "Image" ? (
110
+ <div
111
+ key={index}
112
+ className="cursor-pointer"
113
+ onClick={async () => {
114
+ const currentImage =
115
+ await editContext?.itemsRepository.getItem({
116
+ id: field.value,
117
+ language: "en",
118
+ version: 1,
119
+ });
120
+ const selectedImageId = await editContext?.selectMedia({
121
+ selectedIdPath: currentImage?.idPath || "",
122
+ mode: "images",
123
+ });
124
+ if (selectedImageId) {
125
+ const newImage =
126
+ await editContext?.itemsRepository.getItem({
127
+ id: selectedImageId,
128
+ language: "en",
129
+ version: 1,
130
+ });
131
+ const newThumb: Thumbnail = {
132
+ id: newImage?.id || "",
133
+ name: newImage?.name || "",
134
+ thumbUrl: newImage?.thumbnail || "",
135
+ previewUrl: newImage?.thumbnail || "",
136
+ };
137
+ field.value = selectedImageId;
138
+ setInternalState((state: any) => ({
139
+ ...state,
140
+ thumbnails: [...(state.thumbnails || []), newThumb],
141
+ }));
142
+ onFieldEdited();
143
+ }
144
+ }}
145
+ >
146
+ <div className="font-bold text-gray-900">
147
+ {field.name}:
148
+ </div>
149
+ <div>{getImage(field)}</div>
150
+ </div>
151
+ ) : (
152
+ <FieldEditor
153
+ key={index}
154
+ field={field}
155
+ onFieldEdited={onFieldEdited}
156
+ />
157
+ )
158
+ )}
159
+ </div>
160
+ </div>
161
+
162
+ {/* Recursively render child components in placeholders */}
163
+ {component.children && (
164
+ <SimpleLayoutItem
165
+ components={component.children}
166
+ depth={depth + 1}
167
+ />
168
+ )}
169
+ </div>
170
+ );
171
+ };
172
+
173
+ return components.map((component, index) => (
174
+ <React.Fragment key={component.id || index}>
175
+ {renderComponent(component, components)}
176
+ </React.Fragment>
177
+ ));
178
+ };
179
+
180
+ return (
181
+ <>
182
+ <div className="relative flex-1 w-full">
183
+ <div className="overflow-y-auto inset-0 absolute">
184
+ {pageModel ? (
185
+ <SimpleLayoutItem components={pageModel.components} />
186
+ ) : (
187
+ <div>No layout generated yet</div>
188
+ )}
189
+ </div>
190
+ </div>
191
+ </>
192
+ );
193
+ }