@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,63 @@
1
+ import { classNames } from "primereact/utils";
2
+
3
+ export function SimpleTable<T>({
4
+ items,
5
+ columns,
6
+ onRowHover,
7
+ onRowClick,
8
+ rowClassName,
9
+ }: {
10
+ items: T[];
11
+ columns: {
12
+ header: React.ReactNode;
13
+ body: (item: T) => React.ReactNode;
14
+ className?: string;
15
+ }[];
16
+
17
+ onRowClick?: (data: {
18
+ item: T;
19
+ event: React.MouseEvent<HTMLTableRowElement>;
20
+ }) => void;
21
+ onRowHover?: (item: T | undefined) => void;
22
+ rowClassName?: (item: T) => string;
23
+ }) {
24
+ return (
25
+ <table className="table-auto min-w-full text-left text-xs font-light text-surface">
26
+ <thead className="border-b border-neutral-200 font-medium">
27
+ <tr>
28
+ {columns.map((col, index) => (
29
+ <th key={index} className="px-2 py-3">
30
+ {col.header}
31
+ </th>
32
+ ))}
33
+ </tr>
34
+ </thead>
35
+ <tbody>
36
+ {items.map((item, index) => (
37
+ <tr
38
+ className={classNames(
39
+ "border-b border-neutral-200",
40
+ onRowClick ? "cursor-pointer hover:bg-neutral-100" : "",
41
+ rowClassName ? rowClassName(item) : ""
42
+ )}
43
+ onClick={(ev) => {
44
+ if (onRowClick) onRowClick({ item, event: ev });
45
+ }}
46
+ onMouseOver={() => onRowHover?.(item)}
47
+ onMouseLeave={() => onRowHover?.(undefined)}
48
+ key={index}
49
+ >
50
+ {columns.map((col, index) => (
51
+ <td
52
+ className={classNames("px-2 py-2", col.className)}
53
+ key={index}
54
+ >
55
+ {col.body(item)}
56
+ </td>
57
+ ))}
58
+ </tr>
59
+ ))}
60
+ </tbody>
61
+ </table>
62
+ );
63
+ }
@@ -0,0 +1,55 @@
1
+ import { twMerge } from "tailwind-merge";
2
+
3
+ export type Tab = {
4
+ label: string;
5
+ content: React.ReactNode;
6
+ id: string;
7
+ testId?: string;
8
+ };
9
+
10
+ export function SimpleTabs({
11
+ tabs,
12
+ setActiveTab,
13
+ activeTab,
14
+ className,
15
+ }: {
16
+ tabs: Tab[];
17
+ setActiveTab: (index: number) => void;
18
+ activeTab: number;
19
+ className?: string;
20
+ }) {
21
+ if (activeTab > tabs.length - 1) {
22
+ activeTab = 0;
23
+ }
24
+
25
+ if (activeTab < 0) {
26
+ activeTab = 0;
27
+ }
28
+
29
+ if (tabs.length === 0) return null;
30
+
31
+ return (
32
+ <>
33
+ <div className={twMerge("flex gap-4 pb-3", className)}>
34
+ {tabs.map((tab, index) => (
35
+ <button
36
+ id={tab.id}
37
+ className={activeTab === index ? "active-tab" : "cursor-pointer"}
38
+ key={tab.id}
39
+ data-testid={tab.testId}
40
+ onClick={() => setActiveTab(index)}
41
+ style={{
42
+ fontWeight: index === activeTab ? "bold" : "normal",
43
+ borderBottom: index === activeTab ? "3px solid black" : "none",
44
+ marginBottom: index === activeTab ? "0" : "3px",
45
+ color: index === activeTab ? "black" : "gray",
46
+ }}
47
+ >
48
+ {tab.label}
49
+ </button>
50
+ ))}
51
+ </div>
52
+ {tabs[activeTab]?.content}
53
+ </>
54
+ );
55
+ }
@@ -0,0 +1,7 @@
1
+ export function SimpleToolbar({ children }: { children: React.ReactNode }) {
2
+ return (
3
+ <div className="flex gap-2 p-2 items-center bg-gray-50 rounded-md border-b border-gray-200 text-gray-500 text-sm">
4
+ {children}
5
+ </div>
6
+ );
7
+ }
@@ -0,0 +1,7 @@
1
+ export function Spinner({
2
+ size = "4xl",
3
+ }: {
4
+ size?: "2xl" | "3xl" | "4xl" | "5xl";
5
+ }) {
6
+ return <i className={`pi pi-cog pi-spin text-${size} text-gray-600`}></i>;
7
+ }
@@ -0,0 +1,247 @@
1
+ import React, { useState, useRef, useEffect, useCallback } from "react";
2
+ import { flushSync } from "react-dom";
3
+ export type SplitterPanel = {
4
+ defaultSize: number | "auto";
5
+ name: string;
6
+ content: React.ReactNode;
7
+ collapsible?: boolean;
8
+ };
9
+
10
+ interface SplitterProps {
11
+ panels: SplitterPanel[];
12
+ localStorageKey?: string;
13
+ expandLabel?: React.ReactNode;
14
+ growablePanelIndex?: number; // Index of the panel that grows/shrinks
15
+ }
16
+
17
+ type StoredSizes = { panels: PanelSizes; lastCollapsed: boolean };
18
+ type PanelSizes = { [name: string]: number | "auto" };
19
+
20
+ export const Splitter: React.FC<SplitterProps> = ({
21
+ panels,
22
+ localStorageKey = "splitter-sizes",
23
+ expandLabel = "Expand",
24
+ }) => {
25
+ const totalPanels = panels.length;
26
+
27
+ const [panelSizes, setPanelSizes] = useState<PanelSizes | null>(() => {
28
+ // Load sizes from local storage on initial render
29
+ const storedSizes = localStorage.getItem(localStorageKey);
30
+ if (storedSizes) {
31
+ try {
32
+ const parsedSizes = JSON.parse(storedSizes);
33
+ return (parsedSizes as StoredSizes).panels;
34
+ } catch (error) {
35
+ return null; // Or fallback to default if parsing fails
36
+ }
37
+ }
38
+ return null; // No stored sizes, initial render will handle defaults
39
+ });
40
+
41
+ const panelRefs = useRef<Array<HTMLDivElement | null>>([]);
42
+ const splitterRef = useRef<HTMLDivElement | null>(null);
43
+ const lastPanelCollapsible = panels[panels.length - 1]?.collapsible;
44
+
45
+ const [isLastPanelCollapsed, setIsLastPanelCollapsed] = useState(() => {
46
+ const storedSizes = localStorage.getItem(localStorageKey);
47
+ if (storedSizes) {
48
+ const parsedSizes = JSON.parse(storedSizes);
49
+ return (parsedSizes as StoredSizes).lastCollapsed;
50
+ }
51
+ return true;
52
+ });
53
+
54
+ const [isResizing, setIsResizing] = useState(false);
55
+
56
+ useEffect(() => {
57
+ if (!panelSizes && splitterRef.current) {
58
+ const initialSizes: PanelSizes = {};
59
+
60
+ panels.forEach((panel) => {
61
+ initialSizes[panel.name] = panel.defaultSize;
62
+ });
63
+
64
+ setPanelSizes(initialSizes);
65
+ }
66
+ }, [panels]);
67
+
68
+ useEffect(() => {
69
+ if (panelSizes) {
70
+ const storedSizes: StoredSizes = {
71
+ panels: panelSizes,
72
+ lastCollapsed: isLastPanelCollapsed ?? false,
73
+ };
74
+ localStorage.setItem(localStorageKey, JSON.stringify(storedSizes));
75
+ }
76
+ }, [panelSizes, localStorageKey, isLastPanelCollapsed]);
77
+
78
+ const toggleLastPanelCollapse = () => {
79
+ if (lastPanelCollapsible) {
80
+ document.startViewTransition(() => {
81
+ flushSync(() => {
82
+ setIsLastPanelCollapsed((prev) => !prev);
83
+ });
84
+ });
85
+ }
86
+ };
87
+
88
+ const handleResize = useCallback(
89
+ (index: number, event: React.MouseEvent<HTMLDivElement>) => {
90
+ event.preventDefault();
91
+
92
+ const iframes = splitterRef.current?.querySelectorAll("iframe");
93
+ iframes?.forEach((iframe) => {
94
+ iframe.classList.add("pointer-events-none");
95
+ });
96
+
97
+ const startX = event.clientX;
98
+ // create a copy with the panel name
99
+ const initialSizes = panelSizes ? { ...panelSizes } : {};
100
+
101
+ const panelElement = panelRefs.current[index];
102
+ const nextPanelElement = panelRefs.current[index + 1];
103
+
104
+ if (!panelElement || !nextPanelElement || !splitterRef.current) {
105
+ return;
106
+ }
107
+
108
+ const panelWidth = panelElement.offsetWidth;
109
+ const nextPanelWidth = nextPanelElement.offsetWidth;
110
+
111
+ setIsResizing(true);
112
+
113
+ const onMouseMove = (moveEvent: MouseEvent) => {
114
+ const delta = moveEvent.clientX - startX;
115
+ const newPanelWidth = panelWidth + delta;
116
+ const newNextPanelWidth = nextPanelWidth - delta;
117
+
118
+ const minPanelWidth = 50;
119
+ const minNextPanelWidth = 50;
120
+
121
+ if (
122
+ newNextPanelWidth < minPanelWidth &&
123
+ index == panels.length - 2 &&
124
+ lastPanelCollapsible
125
+ ) {
126
+ setIsLastPanelCollapsed(true);
127
+ setPanelSizes(initialSizes);
128
+ }
129
+
130
+ if (
131
+ newPanelWidth < minPanelWidth ||
132
+ newNextPanelWidth < minNextPanelWidth
133
+ ) {
134
+ return;
135
+ }
136
+
137
+ const updatedSizes: PanelSizes = { ...initialSizes };
138
+ updatedSizes[panels[index]!.name] = newPanelWidth;
139
+ updatedSizes[panels[index + 1]!.name] = newNextPanelWidth;
140
+ setPanelSizes(updatedSizes);
141
+ };
142
+
143
+ const onMouseUp = () => {
144
+ setTimeout(() => {
145
+ setIsResizing(false);
146
+ }, 100);
147
+ iframes?.forEach((iframe) => {
148
+ iframe.classList.remove("pointer-events-none");
149
+ });
150
+
151
+ document.removeEventListener("mousemove", onMouseMove);
152
+ document.removeEventListener("mouseup", onMouseUp, true);
153
+ };
154
+
155
+ document.addEventListener("mousemove", onMouseMove);
156
+ document.addEventListener("mouseup", onMouseUp, true);
157
+ },
158
+ [panels, panelSizes]
159
+ );
160
+
161
+ const isLastResizer = (index: number) => index === panels.length - 2;
162
+
163
+ const getFlexBasis = (index: number): string => {
164
+ if (!panelSizes) return "1fr"; // Or a default like "1fr"
165
+
166
+ const panelName = panels[index]!.name;
167
+ const size =
168
+ index === panels.length - 1 &&
169
+ isLastPanelCollapsed &&
170
+ lastPanelCollapsible
171
+ ? 0
172
+ : panelSizes[panelName] || panels[index]!.defaultSize;
173
+
174
+ if (typeof size === "number") {
175
+ return `${size}px`;
176
+ } else if (size === "auto") {
177
+ return "auto";
178
+ }
179
+
180
+ return "1fr"; //default
181
+ };
182
+
183
+ const getExpandButton = () => {
184
+ return (
185
+ <div
186
+ onClick={!isResizing ? toggleLastPanelCollapse : undefined}
187
+ className="select-none p-1 bg-gray-200 flex items-center justify-center text-gray-500 hover:bg-blue-700 hover:text-white cursor-pointer"
188
+ style={{ writingMode: "vertical-rl", userSelect: "none" }}
189
+ >
190
+ {expandLabel}
191
+ </div>
192
+ );
193
+ };
194
+
195
+ const getSplitter = (index: number) => {
196
+ if (lastPanelCollapsible && isLastPanelCollapsed && isLastResizer(index)) {
197
+ return getExpandButton();
198
+ }
199
+
200
+ return (
201
+ <div
202
+ className={
203
+ " bg-gray-200 cursor-ew-resize select-none flex items-center justify-center hover:bg-blue-700 w-[4px] relative z-1000"
204
+ }
205
+ onDoubleClick={
206
+ isLastResizer(index) ? toggleLastPanelCollapse : undefined
207
+ }
208
+ onMouseDown={(event) => {
209
+ handleResize(index, event);
210
+ }}
211
+ ></div>
212
+ );
213
+ };
214
+
215
+ return (
216
+ <div
217
+ className="flex w-full h-full overflow-hidden"
218
+ ref={splitterRef}
219
+ >
220
+ {panels.map((panel, index) => (
221
+ <React.Fragment key={panel.name}>
222
+ <div
223
+ ref={(el) => {
224
+ panelRefs.current[index] = el;
225
+ }}
226
+ className="relative"
227
+ style={{
228
+ flex: `${panel.defaultSize === "auto" ? 1 : 0} 1 ${getFlexBasis(
229
+ index
230
+ )}`,
231
+ minWidth: 0,
232
+ display:
233
+ panel.collapsible &&
234
+ isLastPanelCollapsed &&
235
+ index === totalPanels - 1
236
+ ? "none"
237
+ : "block",
238
+ }}
239
+ >
240
+ {panel.content}
241
+ </div>
242
+ {index < panels.length - 1 && getSplitter(index)}
243
+ </React.Fragment>
244
+ ))}
245
+ </div>
246
+ );
247
+ };
@@ -0,0 +1,134 @@
1
+ import "allotment/dist/style.css";
2
+ import { Allotment, AllotmentHandle } from "allotment";
3
+
4
+ import { useEffect, useRef, useState } from "react";
5
+
6
+ import { classNames } from "primereact/utils";
7
+ import { Panel } from "../../config/types";
8
+
9
+ export function StackedPanels({ panels }: { panels: Panel[] }) {
10
+ const splitter = useRef<AllotmentHandle>(null);
11
+
12
+ const [sizes, setSizes] = useState<number[]>([]);
13
+ const [preferredSizes, setPreferredSizes] = useState<number[] | undefined>(
14
+ undefined
15
+ );
16
+ const [panelsExpanded, setPanelsExpanded] = useState<boolean[]>([]);
17
+ const [sizeToSet, setSizeToSet] = useState<number[] | undefined>();
18
+
19
+ const panelHeaderSize = 30;
20
+ const toggle = (panelIndex: number) => {
21
+ const newSize = panelsExpanded[panelIndex]
22
+ ? panelHeaderSize
23
+ : (preferredSizes || sizes)[panelIndex];
24
+
25
+ resizePanel(panelIndex, newSize);
26
+
27
+ setPanelsExpanded((x) => {
28
+ const newState = [...x];
29
+ newState[panelIndex] = !newState[panelIndex];
30
+ return newState;
31
+ });
32
+ };
33
+
34
+ useEffect(() => {
35
+ setPanelsExpanded(panels.map(() => true));
36
+ setPreferredSizes(() => panels.map((x) => x.initialSize));
37
+ }, [panels]);
38
+
39
+ const resizePanel = (panelIndex: number, newSize: number) => {
40
+ const totalSize = sizes.reduce((a, b) => a + b, 0);
41
+ const totalOther = totalSize - sizes[panelIndex];
42
+ const tmpSizes = [...sizes];
43
+ tmpSizes[panelIndex] = newSize;
44
+ const percentages = tmpSizes.map((x) => x / totalOther);
45
+ percentages[panelIndex] = 0;
46
+ const remaining = totalSize - newSize;
47
+ const newSizes = percentages.map((x) => x * remaining);
48
+ newSizes[panelIndex] = newSize;
49
+ setSizes(newSizes);
50
+
51
+ setSizeToSet(newSizes);
52
+ };
53
+
54
+ useEffect(() => {
55
+ if (sizeToSet) splitter.current?.resize(sizeToSet);
56
+ }, [sizeToSet]);
57
+
58
+ const getHeader = (panel: any, index: number) => {
59
+ if (panel.header) return panel.header({ panel, index });
60
+
61
+ return (
62
+ <div
63
+ className={classNames(
64
+ "p-1.5 bg-gray-50 uppercase text-gray-500 text-xs flex justify items-center cursor-pointer",
65
+ panelsExpanded[index] ? "border-b" : ""
66
+ )}
67
+ onClick={() => toggle(index)}
68
+ >
69
+ {panel.icon &&
70
+ (typeof panel.icon === "string" ? (
71
+ <i className={classNames(panel.icon, "pi mr-1.5")}></i>
72
+ ) : (
73
+ <div className="w-4 h-4 mr-1.5">{panel.icon}</div>
74
+ ))}
75
+ <div className="mr-auto">{panel.title}</div>
76
+ <i
77
+ className={classNames(
78
+ panelsExpanded[index] ? "pi-chevron-up" : "pi-chevron-down",
79
+ "pi text-sm"
80
+ )}
81
+ ></i>
82
+ </div>
83
+ );
84
+ };
85
+
86
+ const initalSizes = panels.map((x) => x.initialSize);
87
+
88
+ return (
89
+ <Allotment
90
+ vertical={true}
91
+ snap={true}
92
+ ref={splitter}
93
+ defaultSizes={initalSizes}
94
+ onChange={(s) => {
95
+ if (sizes.join(",") != s.join(",")) setSizes(s);
96
+
97
+ const newPreferred = [...(preferredSizes || s)];
98
+ s.forEach((x, i) => {
99
+ if (x > panelHeaderSize) newPreferred[i] = x;
100
+ });
101
+
102
+ setPreferredSizes(newPreferred);
103
+ }}
104
+ >
105
+ {panels.map((panel, index) => (
106
+ <Allotment.Pane
107
+ key={index}
108
+ // preferredSize={(preferredSizes || initalSizes)[index] + "%"}
109
+ maxSize={
110
+ panelsExpanded.length <= index || panelsExpanded[index]
111
+ ? 10000
112
+ : panelHeaderSize
113
+ }
114
+ >
115
+ <div className="flex flex-col h-full">
116
+ {getHeader(panel, index)}
117
+ {panelsExpanded[index] && (
118
+ <div className="overflow-hidden relative flex-1">
119
+ <div
120
+ className={classNames(
121
+ "inset-0 absolute",
122
+ panel.noOverflow ? "" : "overflow-y-auto"
123
+ )}
124
+ >
125
+ {panel.content}
126
+ </div>
127
+ </div>
128
+ )}
129
+ </div>
130
+ </Allotment.Pane>
131
+ ))}
132
+ </Allotment>
133
+ );
134
+ }
@@ -0,0 +1,7 @@
1
+ export function Toolbar({ children }: { children: React.ReactNode }) {
2
+ return (
3
+ <div className="bg-gray-50 flex gap-1 p-0.5 border-b items-center">
4
+ {children}
5
+ </div>
6
+ );
7
+ }
@@ -0,0 +1,3 @@
1
+ export function cleanId(id?: string) {
2
+ if (id) return id.replace(/[^a-zA-Z0-9_-]/g, "").toLowerCase();
3
+ }
@@ -0,0 +1,69 @@
1
+ import { InsertOption } from "../../types";
2
+ import { EditContextType } from "../client/editContext";
3
+ import { getComponentById } from "../componentTreeHelper";
4
+ import { Page, Placeholder } from "../pageModel";
5
+
6
+ export function getInsertOptionsForCurrentSelection(
7
+ editContext: EditContextType
8
+ ): InsertOption[] {
9
+ const page = editContext.page;
10
+
11
+ if (!page) return [];
12
+
13
+ const insertingPlaceholderKeys = [editContext.selectedForInsertion]
14
+ .map((x) => getComponentById(x, editContext.page!))
15
+ .filter((x) => x)
16
+ .reduce(
17
+ (a, x) => a.concat(x!.placeholders?.map((y: Placeholder) => y.key) || []),
18
+ [] as string[]
19
+ );
20
+
21
+ const selectedPlaceholderKeys = [
22
+ //...selection.filter((x) => x.indexOf("_") > 0),
23
+ ...insertingPlaceholderKeys,
24
+ ];
25
+
26
+ function collectInsertOptions(
27
+ page: Page,
28
+ placeholderKeys: string[]
29
+ ): InsertOption[] {
30
+ const insertOptionsMap: Map<string, InsertOption> = new Map();
31
+
32
+ function traverse(placeholders: Placeholder[]) {
33
+ for (const placeholder of placeholders) {
34
+ if (
35
+ placeholder &&
36
+ (placeholderKeys.length === 0 ||
37
+ placeholderKeys.includes(placeholder.key))
38
+ ) {
39
+ for (const option of placeholder.insertOptions) {
40
+ insertOptionsMap.set(option.typeId, option);
41
+ }
42
+ }
43
+ for (const component of placeholder.components) {
44
+ if (component.placeholders) traverse(component.placeholders);
45
+ }
46
+ }
47
+ }
48
+
49
+ traverse(page.rootComponent.placeholders);
50
+
51
+ // Convert the map back into an array of objects
52
+ const insertOptionsArray: InsertOption[] = Array.from(
53
+ insertOptionsMap.values()
54
+ );
55
+
56
+ insertOptionsArray.sort((a, b) => a.name.localeCompare(b.name));
57
+
58
+ return insertOptionsArray;
59
+ }
60
+
61
+ const currentInsertOptions = page
62
+ ? collectInsertOptions(page, selectedPlaceholderKeys)
63
+ : [];
64
+
65
+
66
+ // console.log(currentInsertOptions, selectedPlaceholderKeys, page);
67
+
68
+ return currentInsertOptions;
69
+ }
@@ -0,0 +1,29 @@
1
+ import { getCreateAndSwitchToNewVersionCommand } from "../commands/createVersionCommand";
2
+ import { Language } from "../pageModel";
3
+ import { EditContextType } from "../client/editContext";
4
+
5
+ export const confirmCreateVersion = (
6
+ editContext: EditContextType,
7
+ language: Language
8
+ ) => {
9
+ editContext.confirm({
10
+ message:
11
+ "This page has no version in language " +
12
+ language.name +
13
+ " (" +
14
+ language.languageCode +
15
+ "). Do you want to create a new version?",
16
+ header: "Create Language Version?",
17
+ icon: "pi pi-exclamation-triangle",
18
+ acceptLabel: "Yes, create version",
19
+ rejectLabel: "No",
20
+ accept: async () => {
21
+ await editContext?.executeCommand({
22
+ command: getCreateAndSwitchToNewVersionCommand({
23
+ language: language.languageCode,
24
+ }),
25
+ });
26
+ },
27
+ },
28
+ );
29
+ };
@@ -0,0 +1,28 @@
1
+ import { useMemo, useRef } from "react";
2
+
3
+ // Generic type for the return value of the memoized factory
4
+ export function useMemoWithDebug<T>(factory: () => T, dependencies: any[]): T {
5
+ const previousDependenciesRef = useRef<any[]>([]);
6
+
7
+ const hasChanged = dependencies.some((dep, index) => {
8
+ return dep !== previousDependenciesRef.current[index];
9
+ });
10
+
11
+ if (hasChanged) {
12
+ dependencies.forEach((dep, index) => {
13
+ if (dep !== previousDependenciesRef.current[index]) {
14
+ console.log(
15
+ `Dependency at index ${index} changed:`,
16
+ "\nPrevious:",
17
+ previousDependenciesRef.current[index],
18
+ "\nCurrent:",
19
+ dep
20
+ );
21
+ }
22
+ });
23
+ }
24
+
25
+ previousDependenciesRef.current = dependencies;
26
+
27
+ return useMemo(factory, dependencies);
28
+ }