@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.
- package/.prettierrc +3 -0
- package/eslint.config.mjs +4 -0
- package/images/bg-shape-black.webp +0 -0
- package/package.json +52 -0
- package/src/client-components/api.ts +6 -0
- package/src/client-components/index.ts +19 -0
- package/src/components/ActionButton.tsx +43 -0
- package/src/components/Error.tsx +57 -0
- package/src/config/config.tsx +737 -0
- package/src/config/types.ts +263 -0
- package/src/editor/ComponentInfo.tsx +77 -0
- package/src/editor/ConfirmationDialog.tsx +103 -0
- package/src/editor/ContentTree.tsx +654 -0
- package/src/editor/ContextMenu.tsx +155 -0
- package/src/editor/Editor.tsx +91 -0
- package/src/editor/EditorWarning.tsx +34 -0
- package/src/editor/EditorWarnings.tsx +33 -0
- package/src/editor/FieldEditorPopup.tsx +65 -0
- package/src/editor/FieldHistory.tsx +74 -0
- package/src/editor/FieldList.tsx +190 -0
- package/src/editor/FieldListField.tsx +387 -0
- package/src/editor/FieldListFieldWithFallbacks.tsx +211 -0
- package/src/editor/FloatingToolbar.tsx +163 -0
- package/src/editor/ImageEditor.tsx +129 -0
- package/src/editor/InsertMenu.tsx +332 -0
- package/src/editor/ItemInfo.tsx +90 -0
- package/src/editor/LinkEditorDialog.tsx +192 -0
- package/src/editor/MainLayout.tsx +94 -0
- package/src/editor/NewEditorClient.tsx +11 -0
- package/src/editor/PictureCropper.tsx +505 -0
- package/src/editor/PictureEditor.tsx +206 -0
- package/src/editor/PictureEditorDialog.tsx +381 -0
- package/src/editor/PublishDialog.ignore +74 -0
- package/src/editor/ScrollingContentTree.tsx +47 -0
- package/src/editor/Terminal.tsx +215 -0
- package/src/editor/Titlebar.tsx +23 -0
- package/src/editor/ai/AiPopup.tsx +59 -0
- package/src/editor/ai/AiResponseMessage.tsx +82 -0
- package/src/editor/ai/AiTerminal.tsx +450 -0
- package/src/editor/ai/AiToolCall.tsx +46 -0
- package/src/editor/ai/EditorAiTerminal.tsx +20 -0
- package/src/editor/ai/editorAiContext.ts +18 -0
- package/src/editor/client/DialogContext.tsx +49 -0
- package/src/editor/client/EditorClient.tsx +1831 -0
- package/src/editor/client/GenericDialog.tsx +50 -0
- package/src/editor/client/editContext.ts +330 -0
- package/src/editor/client/helpers.ts +44 -0
- package/src/editor/client/itemsRepository.ts +391 -0
- package/src/editor/client/operations.ts +610 -0
- package/src/editor/client/pageModelBuilder.ts +182 -0
- package/src/editor/commands/commands.ts +23 -0
- package/src/editor/commands/componentCommands.tsx +408 -0
- package/src/editor/commands/createVersionCommand.ts +33 -0
- package/src/editor/commands/deleteVersionCommand.ts +71 -0
- package/src/editor/commands/itemCommands.tsx +186 -0
- package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
- package/src/editor/commands/undo.ts +39 -0
- package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
- package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
- package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
- package/src/editor/component-designer/ComponentEditor.tsx +97 -0
- package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
- package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
- package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
- package/src/editor/component-designer/PlaceholdersEditor.tsx +183 -0
- package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
- package/src/editor/component-designer/TemplateEditor.tsx +236 -0
- package/src/editor/component-designer/aiContext.ts +23 -0
- package/src/editor/componentTreeHelper.tsx +114 -0
- package/src/editor/control-center/ControlCenterMenu.tsx +71 -0
- package/src/editor/control-center/IndexOverview.tsx +50 -0
- package/src/editor/control-center/IndexSettings.tsx +266 -0
- package/src/editor/control-center/Status.tsx +7 -0
- package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
- package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
- package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
- package/src/editor/editor-warnings/NoWriteAccess.tsx +15 -0
- package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
- package/src/editor/field-types/AttachmentEditor.tsx +9 -0
- package/src/editor/field-types/CheckboxEditor.tsx +47 -0
- package/src/editor/field-types/DropLinkEditor.tsx +75 -0
- package/src/editor/field-types/DropListEditor.tsx +84 -0
- package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
- package/src/editor/field-types/InternalLinkFieldEditor.tsx +112 -0
- package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
- package/src/editor/field-types/MultiLineText.tsx +63 -0
- package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
- package/src/editor/field-types/RawEditor.tsx +53 -0
- package/src/editor/field-types/ReactQuill.tsx +580 -0
- package/src/editor/field-types/RichTextEditor.tsx +22 -0
- package/src/editor/field-types/RichTextEditorComponent.tsx +108 -0
- package/src/editor/field-types/SingleLineText.tsx +150 -0
- package/src/editor/field-types/TreeListEditor.tsx +261 -0
- package/src/editor/fieldTypes.ts +140 -0
- package/src/editor/media-selector/AiImageSearch.tsx +186 -0
- package/src/editor/media-selector/AiImageSearchPrompt.tsx +95 -0
- package/src/editor/media-selector/MediaSelector.tsx +42 -0
- package/src/editor/media-selector/Preview.tsx +14 -0
- package/src/editor/media-selector/Thumbnails.tsx +48 -0
- package/src/editor/media-selector/TreeSelector.tsx +292 -0
- package/src/editor/media-selector/UploadZone.tsx +137 -0
- package/src/editor/menubar/ActionsMenu.tsx +47 -0
- package/src/editor/menubar/ActiveUsers.tsx +17 -0
- package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
- package/src/editor/menubar/BrowseHistory.tsx +37 -0
- package/src/editor/menubar/ItemLanguageVersion.tsx +52 -0
- package/src/editor/menubar/LanguageSelector.tsx +152 -0
- package/src/editor/menubar/Menu.tsx +83 -0
- package/src/editor/menubar/NavButtons.tsx +74 -0
- package/src/editor/menubar/PageSelector.tsx +139 -0
- package/src/editor/menubar/PageViewerControls.tsx +99 -0
- package/src/editor/menubar/Separator.tsx +12 -0
- package/src/editor/menubar/SiteInfo.tsx +53 -0
- package/src/editor/menubar/User.tsx +27 -0
- package/src/editor/menubar/VersionSelector.tsx +143 -0
- package/src/editor/page-editor-chrome/CommentHighlighting.tsx +287 -0
- package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
- package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +44 -0
- package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
- package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
- package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
- package/src/editor/page-editor-chrome/FrameMenu.tsx +263 -0
- package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
- package/src/editor/page-editor-chrome/InlineEditor.tsx +147 -0
- package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
- package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
- package/src/editor/page-editor-chrome/PageEditorChrome.tsx +119 -0
- package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +154 -0
- package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +171 -0
- package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +233 -0
- package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
- package/src/editor/page-viewer/EditorForm.tsx +247 -0
- package/src/editor/page-viewer/MiniMap.tsx +351 -0
- package/src/editor/page-viewer/PageViewer.tsx +127 -0
- package/src/editor/page-viewer/PageViewerFrame.tsx +1030 -0
- package/src/editor/page-viewer/pageViewContext.ts +186 -0
- package/src/editor/pageModel.ts +191 -0
- package/src/editor/picture-shared.tsx +53 -0
- package/src/editor/reviews/Comment.tsx +265 -0
- package/src/editor/reviews/Comments.tsx +50 -0
- package/src/editor/reviews/PreviewInfo.tsx +35 -0
- package/src/editor/reviews/Reviews.tsx +280 -0
- package/src/editor/reviews/reviewCommands.tsx +47 -0
- package/src/editor/reviews/useReviews.tsx +70 -0
- package/src/editor/services/aiService.ts +155 -0
- package/src/editor/services/componentDesignerService.ts +151 -0
- package/src/editor/services/contentService.ts +159 -0
- package/src/editor/services/editService.ts +462 -0
- package/src/editor/services/indexService.ts +24 -0
- package/src/editor/services/reviewsService.ts +45 -0
- package/src/editor/services/serviceHelper.ts +95 -0
- package/src/editor/services/systemService.ts +5 -0
- package/src/editor/services/translationService.ts +21 -0
- package/src/editor/services-server/api.ts +150 -0
- package/src/editor/services-server/graphQL.ts +106 -0
- package/src/editor/sidebar/ComponentPalette.tsx +146 -0
- package/src/editor/sidebar/ComponentTree.tsx +512 -0
- package/src/editor/sidebar/ComponentTree2.tsxx +490 -0
- package/src/editor/sidebar/Debug.tsx +105 -0
- package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
- package/src/editor/sidebar/EditHistory.tsx +134 -0
- package/src/editor/sidebar/GraphQL.tsx +164 -0
- package/src/editor/sidebar/Insert.tsx +35 -0
- package/src/editor/sidebar/MainContentTree.tsx +95 -0
- package/src/editor/sidebar/Performance.tsx +53 -0
- package/src/editor/sidebar/Sessions.tsx +35 -0
- package/src/editor/sidebar/Sidebar.tsx +20 -0
- package/src/editor/sidebar/SidebarView.tsx +150 -0
- package/src/editor/sidebar/Translations.tsx +276 -0
- package/src/editor/sidebar/Validation.tsx +102 -0
- package/src/editor/sidebar/ViewSelector.tsx +49 -0
- package/src/editor/sidebar/Workbox.tsx +209 -0
- package/src/editor/ui/CenteredMessage.tsx +7 -0
- package/src/editor/ui/CopyToClipboardButton.tsx +23 -0
- package/src/editor/ui/DialogButtons.tsx +11 -0
- package/src/editor/ui/Icons.tsx +585 -0
- package/src/editor/ui/ItemNameDialog.tsx +94 -0
- package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
- package/src/editor/ui/ItemSearch.tsx +173 -0
- package/src/editor/ui/PerfectTree.tsx +550 -0
- package/src/editor/ui/Section.tsx +35 -0
- package/src/editor/ui/SimpleIconButton.tsx +43 -0
- package/src/editor/ui/SimpleMenu.tsx +48 -0
- package/src/editor/ui/SimpleTable.tsx +63 -0
- package/src/editor/ui/SimpleTabs.tsx +55 -0
- package/src/editor/ui/SimpleToolbar.tsx +7 -0
- package/src/editor/ui/Spinner.tsx +7 -0
- package/src/editor/ui/Splitter.tsx +247 -0
- package/src/editor/ui/StackedPanels.tsx +134 -0
- package/src/editor/ui/Toolbar.tsx +7 -0
- package/src/editor/utils/id-helper.ts +3 -0
- package/src/editor/utils/insertOptions.ts +69 -0
- package/src/editor/utils/itemutils.ts +29 -0
- package/src/editor/utils/useMemoDebug.ts +28 -0
- package/src/editor/utils.ts +435 -0
- package/src/editor/views/CompareView.tsx +256 -0
- package/src/editor/views/EditView.tsx +27 -0
- package/src/editor/views/ItemEditor.tsx +58 -0
- package/src/editor/views/SingleEditView.tsx +44 -0
- package/src/fonts/Geist-Black.woff2 +0 -0
- package/src/fonts/Geist-Bold.woff2 +0 -0
- package/src/fonts/Geist-ExtraBold.woff2 +0 -0
- package/src/fonts/Geist-ExtraLight.woff2 +0 -0
- package/src/fonts/Geist-Light.woff2 +0 -0
- package/src/fonts/Geist-Medium.woff2 +0 -0
- package/src/fonts/Geist-Regular.woff2 +0 -0
- package/src/fonts/Geist-SemiBold.woff2 +0 -0
- package/src/fonts/Geist-Thin.woff2 +0 -0
- package/src/fonts/Geist[wght].woff2 +0 -0
- package/src/index.ts +7 -0
- package/src/page-wizard/PageWizard.tsx +163 -0
- package/src/page-wizard/SelectWizard.tsx +109 -0
- package/src/page-wizard/WizardSteps.tsx +207 -0
- package/src/page-wizard/service.ts +35 -0
- package/src/page-wizard/startPageWizardCommand.ts +27 -0
- package/src/page-wizard/steps/BuildPageStep.tsx +266 -0
- package/src/page-wizard/steps/CollectStep.tsx +233 -0
- package/src/page-wizard/steps/ComponentTypesSelector.tsx +443 -0
- package/src/page-wizard/steps/Components.tsx +193 -0
- package/src/page-wizard/steps/CreatePage.tsx +285 -0
- package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +384 -0
- package/src/page-wizard/steps/EditButton.tsx +34 -0
- package/src/page-wizard/steps/FieldEditor.tsx +102 -0
- package/src/page-wizard/steps/Generate.tsx +32 -0
- package/src/page-wizard/steps/ImagesStep.tsx +318 -0
- package/src/page-wizard/steps/LayoutStep.tsx +228 -0
- package/src/page-wizard/steps/SelectStep.tsx +256 -0
- package/src/page-wizard/steps/schema.ts +180 -0
- package/src/page-wizard/steps/usePageCreator.ts +279 -0
- package/src/splash-screen/NewPage.tsx +232 -0
- package/src/splash-screen/SectionHeadline.tsx +21 -0
- package/src/splash-screen/SplashScreen.tsx +156 -0
- package/src/tour/Tour.tsx +558 -0
- package/src/tour/default-tour.tsx +300 -0
- package/src/tour/preview-tour.tsx +127 -0
- package/src/types.ts +302 -0
- package/styles.css +476 -0
- package/tsconfig.build.json +21 -0
- 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
|
+
}
|