@alpaca-editor/core 1.0.3942 → 1.0.3943

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 (308) hide show
  1. package/.prettierrc +3 -0
  2. package/build.css +3 -0
  3. package/components.json +21 -0
  4. package/dist/editor/ContentTree.d.ts +2 -1
  5. package/dist/editor/ContentTree.js +23 -21
  6. package/dist/editor/ContentTree.js.map +1 -1
  7. package/dist/editor/FieldActionsOverlay.js +0 -2
  8. package/dist/editor/FieldActionsOverlay.js.map +1 -1
  9. package/dist/editor/ScrollingContentTree.js +1 -1
  10. package/dist/editor/ScrollingContentTree.js.map +1 -1
  11. package/dist/editor/Titlebar.js +1 -1
  12. package/dist/editor/Titlebar.js.map +1 -1
  13. package/dist/editor/ai/GhostWriter.js +24 -3
  14. package/dist/editor/ai/GhostWriter.js.map +1 -1
  15. package/dist/editor/client/EditorClient.js +7 -7
  16. package/dist/editor/client/EditorClient.js.map +1 -1
  17. package/dist/editor/field-types/InternalLinkFieldEditor.js +60 -10
  18. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  19. package/dist/editor/media-selector/MediaFolderBrowser.js +48 -1
  20. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  21. package/dist/editor/menubar/PageSelector.js +116 -65
  22. package/dist/editor/menubar/PageSelector.js.map +1 -1
  23. package/dist/editor/page-viewer/EditorForm.js +5 -2
  24. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  25. package/dist/editor/ui/ItemSearch.js +14 -8
  26. package/dist/editor/ui/ItemSearch.js.map +1 -1
  27. package/dist/editor/ui/PerfectTree.d.ts +4 -2
  28. package/dist/editor/ui/PerfectTree.js +78 -4
  29. package/dist/editor/ui/PerfectTree.js.map +1 -1
  30. package/dist/editor/ui/Splitter.js +1 -1
  31. package/dist/revision.d.ts +2 -2
  32. package/dist/revision.js +2 -2
  33. package/dist/styles.css +8 -2
  34. package/eslint.config.mjs +4 -0
  35. package/images/bg-shape-black.webp +0 -0
  36. package/images/wizard-bg.png +0 -0
  37. package/images/wizard-tour.png +0 -0
  38. package/images/wizard.png +0 -0
  39. package/package.json +2 -8
  40. package/src/client-components/api.ts +6 -0
  41. package/src/client-components/index.ts +19 -0
  42. package/src/components/ActionButton.tsx +50 -0
  43. package/src/components/Error.tsx +57 -0
  44. package/src/components/ui/CardConnector.tsx +56 -0
  45. package/src/components/ui/button.tsx +62 -0
  46. package/src/components/ui/card.tsx +372 -0
  47. package/src/components/ui/context-menu.tsx +250 -0
  48. package/src/config/config.tsx +917 -0
  49. package/src/config/types.ts +286 -0
  50. package/src/editor/ComponentInfo.tsx +90 -0
  51. package/src/editor/ConfirmationDialog.tsx +103 -0
  52. package/src/editor/ContentTree.tsx +733 -0
  53. package/src/editor/ContextMenu.tsx +230 -0
  54. package/src/editor/Editor.tsx +90 -0
  55. package/src/editor/EditorWarning.tsx +34 -0
  56. package/src/editor/EditorWarnings.tsx +33 -0
  57. package/src/editor/FieldActionsOverlay.tsx +296 -0
  58. package/src/editor/FieldEditorPopup.tsx +65 -0
  59. package/src/editor/FieldHistory.tsx +75 -0
  60. package/src/editor/FieldList.tsx +190 -0
  61. package/src/editor/FieldListField.tsx +391 -0
  62. package/src/editor/FieldListFieldWithFallbacks.tsx +217 -0
  63. package/src/editor/FloatingToolbar.tsx +163 -0
  64. package/src/editor/ImageEditor.tsx +128 -0
  65. package/src/editor/ItemInfo.tsx +90 -0
  66. package/src/editor/LinkEditorDialog.tsx +196 -0
  67. package/src/editor/MainLayout.tsx +95 -0
  68. package/src/editor/MobileLayout.tsx +68 -0
  69. package/src/editor/NewEditorClient.tsx +11 -0
  70. package/src/editor/PictureCropper.tsx +568 -0
  71. package/src/editor/PictureEditor.tsx +301 -0
  72. package/src/editor/PictureEditorDialog.tsx +381 -0
  73. package/src/editor/PublishDialog.ignore +74 -0
  74. package/src/editor/ScrollingContentTree.tsx +68 -0
  75. package/src/editor/Terminal.tsx +227 -0
  76. package/src/editor/Titlebar.tsx +104 -0
  77. package/src/editor/ai/AiPopup.tsx +59 -0
  78. package/src/editor/ai/AiResponseMessage.tsx +106 -0
  79. package/src/editor/ai/AiTerminal.tsx +503 -0
  80. package/src/editor/ai/AiToolCall.tsx +61 -0
  81. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  82. package/src/editor/ai/GhostWriter.tsx +480 -0
  83. package/src/editor/ai/aiPageModel.ts +108 -0
  84. package/src/editor/ai/editorAiContext.ts +18 -0
  85. package/src/editor/client/AboutDialog.tsx +44 -0
  86. package/src/editor/client/EditorClient.tsx +2241 -0
  87. package/src/editor/client/GenericDialog.tsx +50 -0
  88. package/src/editor/client/editContext.ts +416 -0
  89. package/src/editor/client/helpers.ts +44 -0
  90. package/src/editor/client/itemsRepository.ts +574 -0
  91. package/src/editor/client/operations.ts +768 -0
  92. package/src/editor/client/pageModelBuilder.ts +219 -0
  93. package/src/editor/commands/commands.ts +22 -0
  94. package/src/editor/commands/componentCommands.tsx +431 -0
  95. package/src/editor/commands/createVersionCommand.ts +33 -0
  96. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  97. package/src/editor/commands/itemCommands.tsx +351 -0
  98. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  99. package/src/editor/commands/localizeItem/LocalizeItemUtils.ts +27 -0
  100. package/src/editor/commands/undo.ts +39 -0
  101. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  102. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  103. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  104. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  105. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  106. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  107. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  108. package/src/editor/component-designer/PlaceholdersEditor.tsx +179 -0
  109. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  110. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  111. package/src/editor/component-designer/aiContext.ts +23 -0
  112. package/src/editor/componentTreeHelper.tsx +116 -0
  113. package/src/editor/context-menu/CopyMoveMenu.tsx +103 -0
  114. package/src/editor/context-menu/InsertMenu.tsx +347 -0
  115. package/src/editor/control-center/About.tsx +342 -0
  116. package/src/editor/control-center/ControlCenterMenu.tsx +76 -0
  117. package/src/editor/control-center/IndexOverview.tsx +50 -0
  118. package/src/editor/control-center/IndexSettings.tsx +266 -0
  119. package/src/editor/control-center/Info.tsx +104 -0
  120. package/src/editor/control-center/QuotaInfo.tsx +301 -0
  121. package/src/editor/control-center/Status.tsx +113 -0
  122. package/src/editor/control-center/WebSocketMessages.tsx +155 -0
  123. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  124. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  125. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  126. package/src/editor/editor-warnings/NoWriteAccess.tsx +16 -0
  127. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  128. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  129. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  130. package/src/editor/field-types/DropLinkEditor.tsx +80 -0
  131. package/src/editor/field-types/DropListEditor.tsx +84 -0
  132. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  133. package/src/editor/field-types/InternalLinkFieldEditor.tsx +188 -0
  134. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  135. package/src/editor/field-types/MultiLineText.tsx +82 -0
  136. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  137. package/src/editor/field-types/RawEditor.tsx +53 -0
  138. package/src/editor/field-types/ReactQuill.tsx +580 -0
  139. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  140. package/src/editor/field-types/RichTextEditorComponent.tsx +127 -0
  141. package/src/editor/field-types/SingleLineText.tsx +174 -0
  142. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  143. package/src/editor/fieldTypes.ts +140 -0
  144. package/src/editor/media-selector/AiImageSearch.tsx +185 -0
  145. package/src/editor/media-selector/AiImageSearchPrompt.tsx +94 -0
  146. package/src/editor/media-selector/MediaFolderBrowser.tsx +321 -0
  147. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  148. package/src/editor/media-selector/Preview.tsx +14 -0
  149. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  150. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  151. package/src/editor/media-selector/UploadZone.tsx +137 -0
  152. package/src/editor/media-selector/index.ts +8 -0
  153. package/src/editor/menubar/ActionsMenu.tsx +94 -0
  154. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  155. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  156. package/src/editor/menubar/BrowseHistory.tsx +28 -0
  157. package/src/editor/menubar/ItemLanguageVersion.tsx +76 -0
  158. package/src/editor/menubar/LanguageSelector.tsx +226 -0
  159. package/src/editor/menubar/Menu.tsx +83 -0
  160. package/src/editor/menubar/NavButtons.tsx +74 -0
  161. package/src/editor/menubar/PageSelector.tsx +278 -0
  162. package/src/editor/menubar/PageViewerControls.tsx +120 -0
  163. package/src/editor/menubar/PreviewSecondaryControls.tsx +18 -0
  164. package/src/editor/menubar/SecondaryControls.tsx +45 -0
  165. package/src/editor/menubar/Separator.tsx +12 -0
  166. package/src/editor/menubar/SiteInfo.tsx +53 -0
  167. package/src/editor/menubar/User.tsx +27 -0
  168. package/src/editor/menubar/VersionSelector.tsx +142 -0
  169. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +307 -0
  170. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  171. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +59 -0
  172. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  173. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  174. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  175. package/src/editor/page-editor-chrome/FrameMenu.tsx +338 -0
  176. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  177. package/src/editor/page-editor-chrome/InlineEditor.tsx +765 -0
  178. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  179. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  180. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +122 -0
  181. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +161 -0
  182. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +169 -0
  183. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +315 -0
  184. package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +300 -0
  185. package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +40 -0
  186. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +828 -0
  187. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  188. package/src/editor/page-viewer/EditorForm.tsx +262 -0
  189. package/src/editor/page-viewer/MiniMap.tsx +362 -0
  190. package/src/editor/page-viewer/PageViewer.tsx +169 -0
  191. package/src/editor/page-viewer/PageViewerFrame.tsx +1022 -0
  192. package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +412 -0
  193. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  194. package/src/editor/pageModel.ts +220 -0
  195. package/src/editor/picture-shared.tsx +53 -0
  196. package/src/editor/reviews/Comment.tsx +308 -0
  197. package/src/editor/reviews/Comments.tsx +125 -0
  198. package/src/editor/reviews/DiffView.tsx +109 -0
  199. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  200. package/src/editor/reviews/Reviews.tsx +280 -0
  201. package/src/editor/reviews/SuggestedEdit.tsx +316 -0
  202. package/src/editor/reviews/reviewCommands.tsx +47 -0
  203. package/src/editor/reviews/useReviews.tsx +70 -0
  204. package/src/editor/services/aiService.ts +173 -0
  205. package/src/editor/services/componentDesignerService.ts +151 -0
  206. package/src/editor/services/contentService.ts +180 -0
  207. package/src/editor/services/editService.ts +488 -0
  208. package/src/editor/services/indexService.ts +24 -0
  209. package/src/editor/services/reviewsService.ts +53 -0
  210. package/src/editor/services/serviceHelper.ts +95 -0
  211. package/src/editor/services/suggestedEditsService.ts +39 -0
  212. package/src/editor/services/systemService.ts +5 -0
  213. package/src/editor/services/translationService.ts +21 -0
  214. package/src/editor/services-server/api.ts +150 -0
  215. package/src/editor/services-server/graphQL.ts +106 -0
  216. package/src/editor/sidebar/ComponentPalette.tsx +161 -0
  217. package/src/editor/sidebar/ComponentTree.tsx +549 -0
  218. package/src/editor/sidebar/Debug.tsx +111 -0
  219. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  220. package/src/editor/sidebar/EditHistory.tsx +134 -0
  221. package/src/editor/sidebar/GraphQL.tsx +164 -0
  222. package/src/editor/sidebar/Insert.tsx +35 -0
  223. package/src/editor/sidebar/MainContentTree.tsx +102 -0
  224. package/src/editor/sidebar/Performance.tsx +53 -0
  225. package/src/editor/sidebar/Sessions.tsx +35 -0
  226. package/src/editor/sidebar/Sidebar.tsx +20 -0
  227. package/src/editor/sidebar/SidebarView.tsx +152 -0
  228. package/src/editor/sidebar/Translations.tsx +295 -0
  229. package/src/editor/sidebar/Validation.tsx +102 -0
  230. package/src/editor/sidebar/ViewSelector.tsx +60 -0
  231. package/src/editor/sidebar/Workbox.tsx +209 -0
  232. package/src/editor/ui/CenteredMessage.tsx +7 -0
  233. package/src/editor/ui/CopyMoveTargetSelectorDialog.tsx +81 -0
  234. package/src/editor/ui/CopyToClipboardButton.tsx +24 -0
  235. package/src/editor/ui/DialogButtons.tsx +11 -0
  236. package/src/editor/ui/Icons.tsx +709 -0
  237. package/src/editor/ui/ItemList.tsx +76 -0
  238. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  239. package/src/editor/ui/ItemSearch.tsx +159 -0
  240. package/src/editor/ui/PerfectTree.tsx +676 -0
  241. package/src/editor/ui/Section.tsx +35 -0
  242. package/src/editor/ui/SimpleIconButton.tsx +54 -0
  243. package/src/editor/ui/SimpleMenu.tsx +40 -0
  244. package/src/editor/ui/SimpleTable.tsx +60 -0
  245. package/src/editor/ui/SimpleTabs.tsx +60 -0
  246. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  247. package/src/editor/ui/Spinner.tsx +9 -0
  248. package/src/editor/ui/Splitter.tsx +420 -0
  249. package/src/editor/ui/StackedPanels.tsx +134 -0
  250. package/src/editor/ui/Toolbar.tsx +7 -0
  251. package/src/editor/utils/id-helper.ts +3 -0
  252. package/src/editor/utils/insertOptions.ts +69 -0
  253. package/src/editor/utils/itemutils.ts +29 -0
  254. package/src/editor/utils/useMemoDebug.ts +28 -0
  255. package/src/editor/utils.ts +486 -0
  256. package/src/editor/views/CompareView.tsx +245 -0
  257. package/src/editor/views/EditView.tsx +27 -0
  258. package/src/editor/views/ItemEditor.tsx +58 -0
  259. package/src/editor/views/MediaFolderEditView.tsx +66 -0
  260. package/src/editor/views/SingleEditView.tsx +57 -0
  261. package/src/fonts/Geist-Black.woff2 +0 -0
  262. package/src/fonts/Geist-Bold.woff2 +0 -0
  263. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  264. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  265. package/src/fonts/Geist-Light.woff2 +0 -0
  266. package/src/fonts/Geist-Medium.woff2 +0 -0
  267. package/src/fonts/Geist-Regular.woff2 +0 -0
  268. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  269. package/src/fonts/Geist-Thin.woff2 +0 -0
  270. package/src/fonts/Geist[wght].woff2 +0 -0
  271. package/src/fonts/index.ts +10 -0
  272. package/src/index.ts +23 -0
  273. package/src/lib/safelist.tsx +16 -0
  274. package/src/lib/utils.ts +6 -0
  275. package/src/page-wizard/PageWizard.tsx +139 -0
  276. package/src/page-wizard/WizardBox.tsx +4 -0
  277. package/src/page-wizard/WizardBoxConnector.tsx +56 -0
  278. package/src/page-wizard/WizardSteps.tsx +458 -0
  279. package/src/page-wizard/service.ts +35 -0
  280. package/src/page-wizard/startPageWizardCommand.ts +26 -0
  281. package/src/page-wizard/steps/BuildPageStep.tsx +259 -0
  282. package/src/page-wizard/steps/CollectStep.tsx +296 -0
  283. package/src/page-wizard/steps/ComponentTypesSelector.tsx +454 -0
  284. package/src/page-wizard/steps/Components.tsx +193 -0
  285. package/src/page-wizard/steps/ContentStep.tsx +890 -0
  286. package/src/page-wizard/steps/EditButton.tsx +34 -0
  287. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  288. package/src/page-wizard/steps/Generate.tsx +60 -0
  289. package/src/page-wizard/steps/ImagesStep.tsx +382 -0
  290. package/src/page-wizard/steps/LayoutStep.tsx +227 -0
  291. package/src/page-wizard/steps/MetaDataStep.tsx +173 -0
  292. package/src/page-wizard/steps/SelectStep.tsx +281 -0
  293. package/src/page-wizard/steps/schema.ts +180 -0
  294. package/src/page-wizard/steps/usePageCreator.ts +325 -0
  295. package/src/page-wizard/usePageWizard.ts +79 -0
  296. package/src/revision.ts +2 -0
  297. package/src/splash-screen/NewPage.tsx +294 -0
  298. package/src/splash-screen/OpenPage.tsx +113 -0
  299. package/src/splash-screen/RecentPages.tsx +123 -0
  300. package/src/splash-screen/SectionHeadline.tsx +21 -0
  301. package/src/splash-screen/SplashScreen.tsx +195 -0
  302. package/src/tour/Tour.tsx +566 -0
  303. package/src/tour/default-tour.tsx +301 -0
  304. package/src/tour/preview-tour.tsx +128 -0
  305. package/src/types.ts +335 -0
  306. package/styles.css +765 -0
  307. package/tsconfig.build.json +31 -0
  308. package/tsconfig.json +14 -0
@@ -0,0 +1,54 @@
1
+ import { classNames } from "primereact/utils";
2
+ import { MouseEventHandler } from "react";
3
+
4
+ export function SimpleIconButton({
5
+ onClick,
6
+ className,
7
+ icon,
8
+ label,
9
+ disabled,
10
+ size,
11
+ id,
12
+ selected,
13
+ dark = false,
14
+ }: {
15
+ onClick: MouseEventHandler;
16
+ className?: string;
17
+ icon?: React.ReactNode;
18
+ label: string;
19
+ disabled?: boolean;
20
+ id?: string;
21
+ size?: "large" | "small";
22
+ selected?: boolean;
23
+ dark?: boolean;
24
+ }) {
25
+ const color = disabled
26
+ ? "text-gray-300"
27
+ : dark
28
+ ? "text-gray-400"
29
+ : "text-gray-600";
30
+
31
+ return (
32
+ <button
33
+ id={id}
34
+ disabled={disabled}
35
+ className={classNames(
36
+ typeof icon === "string" ? icon + " p-[6px]" : "p-[2px]",
37
+ "rounded-full",
38
+ color,
39
+ disabled
40
+ ? "cursor-none"
41
+ : "cursor-pointer hover:bg-gray-200 hover:text-gray-600",
42
+ className,
43
+ size === "large" ? "text-lg" : "text-xs",
44
+ selected ? "bg-gray-200 text-gray-600" : "",
45
+ )}
46
+ onClick={(ev) => {
47
+ if (!disabled) onClick(ev);
48
+ }}
49
+ title={label}
50
+ >
51
+ {typeof icon !== "string" && icon}
52
+ </button>
53
+ );
54
+ }
@@ -0,0 +1,40 @@
1
+ import { ReactNode } from "react";
2
+ import { MenuItem } from "../../config/types";
3
+ export function SimpleMenu({
4
+ items,
5
+ activeItemKey,
6
+ onItemClick,
7
+ }: {
8
+ items: MenuItem[];
9
+ activeItemKey: string | null;
10
+ onItemClick: (item: MenuItem) => void;
11
+ }) {
12
+ return (
13
+ <div className="flex h-full flex-col p-1">
14
+ {items.map((item, index) => (
15
+ <div key={index} className="flex flex-col gap-1 p-2">
16
+ <div className="flex flex-row items-center gap-2 border-b border-gray-200 pb-1 pl-2">
17
+ {item.icon && item.icon}
18
+ {item.label}
19
+ </div>
20
+ {item.items?.map((subItem, subIndex) => (
21
+ <div
22
+ key={subIndex}
23
+ className={`flex cursor-pointer flex-col p-1 px-4 hover:bg-gray-100 ${
24
+ activeItemKey === subItem.id ? "bg-gray-100" : ""
25
+ }`}
26
+ onClick={() => {
27
+ onItemClick(subItem);
28
+ }}
29
+ >
30
+ <div className="flex flex-row items-center gap-2 text-sm">
31
+ {subItem.icon && subItem.icon}
32
+ {subItem.label}
33
+ </div>
34
+ </div>
35
+ ))}
36
+ </div>
37
+ ))}
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,60 @@
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="text-surface min-w-full table-auto text-left text-xs font-light">
26
+ <thead className="border-b border-neutral-200 font-medium">
27
+ <tr>
28
+ {columns.map((col, index) => (
29
+ <th key={index} className="px-1.5 py-1.5">
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 className={classNames("p-2", col.className)} key={index}>
52
+ {col.body(item)}
53
+ </td>
54
+ ))}
55
+ </tr>
56
+ ))}
57
+ </tbody>
58
+ </table>
59
+ );
60
+ }
@@ -0,0 +1,60 @@
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
+ icon?: string; // PrimeReact icon class name (e.g., "pi pi-search")
9
+ };
10
+
11
+ export function SimpleTabs({
12
+ tabs,
13
+ setActiveTab,
14
+ activeTab,
15
+ className,
16
+ }: {
17
+ tabs: Tab[];
18
+ setActiveTab: (index: number) => void;
19
+ activeTab: number;
20
+ className?: string;
21
+ }) {
22
+ if (activeTab > tabs.length - 1) {
23
+ activeTab = 0;
24
+ }
25
+
26
+ if (activeTab < 0) {
27
+ activeTab = 0;
28
+ }
29
+
30
+ if (tabs.length === 0) return null;
31
+
32
+ return (
33
+ <>
34
+ <div className={twMerge("flex gap-4 pb-2", className)}>
35
+ {tabs.map((tab, index) => (
36
+ <button
37
+ id={tab.id}
38
+ className={twMerge(
39
+ "flex cursor-pointer items-center gap-2",
40
+ activeTab === index ? "active-tab" : "",
41
+ )}
42
+ key={tab.id}
43
+ data-testid={tab.testId}
44
+ onClick={() => setActiveTab(index)}
45
+ style={{
46
+ fontWeight: index === activeTab ? "bold" : "normal",
47
+ borderBottom: index === activeTab ? "3px solid black" : "none",
48
+ marginBottom: index === activeTab ? "0" : "3px",
49
+ color: index === activeTab ? "black" : "gray",
50
+ }}
51
+ >
52
+ {tab.icon && <i className={tab.icon} />}
53
+ {tab.label}
54
+ </button>
55
+ ))}
56
+ </div>
57
+ {tabs[activeTab]?.content}
58
+ </>
59
+ );
60
+ }
@@ -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,9 @@
1
+ export function Spinner({
2
+ size = "4xl",
3
+ }: {
4
+ size?: "2xl" | "3xl" | "4xl" | "5xl";
5
+ }) {
6
+ return (
7
+ <i className={`pi pi-cog pi-spin text-${size} text-theme-secondary/40`}></i>
8
+ );
9
+ }
@@ -0,0 +1,420 @@
1
+ import React, { useState, useRef, useEffect, useCallback } from "react";
2
+ import { flushSync } from "react-dom";
3
+
4
+ export type SplitterPanel = {
5
+ defaultSize: number | "auto";
6
+ name: string;
7
+ content: React.ReactNode;
8
+ collapsible?: boolean;
9
+ hidden?: boolean;
10
+ };
11
+
12
+ interface SplitterProps {
13
+ panels: SplitterPanel[];
14
+ localStorageKey?: string;
15
+ expandLabel?: React.ReactNode;
16
+ growablePanelIndex?: number; // Index of the panel that grows/shrinks
17
+ direction?: "horizontal" | "vertical"; // New prop for direction
18
+ }
19
+
20
+ type StoredSizes = { panels: PanelSizes; lastCollapsed: boolean };
21
+ type PanelSizes = { [name: string]: number | "auto" };
22
+
23
+ export const Splitter: React.FC<SplitterProps> = ({
24
+ panels,
25
+ localStorageKey = "splitter-sizes",
26
+ expandLabel = "Expand",
27
+ direction = "horizontal", // Default to horizontal for backward compatibility
28
+ }) => {
29
+ const totalPanels = panels.length;
30
+
31
+ const [panelSizes, setPanelSizes] = useState<PanelSizes | null>(() => {
32
+ // Load sizes from local storage on initial render
33
+ const storedSizes = localStorage.getItem(localStorageKey);
34
+ if (storedSizes) {
35
+ try {
36
+ const parsedSizes = JSON.parse(storedSizes);
37
+ return (parsedSizes as StoredSizes).panels;
38
+ } catch (error) {
39
+ return null; // Or fallback to default if parsing fails
40
+ }
41
+ }
42
+ return null; // No stored sizes, initial render will handle defaults
43
+ });
44
+
45
+ const panelRefs = useRef<Array<HTMLDivElement | null>>([]);
46
+ const splitterRef = useRef<HTMLDivElement | null>(null);
47
+ const lastPanelCollapsible = panels[panels.length - 1]?.collapsible;
48
+
49
+ const [isLastPanelCollapsed, setIsLastPanelCollapsed] = useState(() => {
50
+ const storedSizes = localStorage.getItem(localStorageKey);
51
+ if (storedSizes) {
52
+ const parsedSizes = JSON.parse(storedSizes);
53
+ return (parsedSizes as StoredSizes).lastCollapsed;
54
+ }
55
+ return true;
56
+ });
57
+
58
+ const [isResizing, setIsResizing] = useState(false);
59
+
60
+ useEffect(() => {
61
+ if (!panelSizes && splitterRef.current) {
62
+ const initialSizes: PanelSizes = {};
63
+
64
+ panels.forEach((panel) => {
65
+ initialSizes[panel.name] = panel.defaultSize;
66
+ });
67
+
68
+ setPanelSizes(initialSizes);
69
+ }
70
+ }, [panels]);
71
+
72
+ useEffect(() => {
73
+ if (panelSizes) {
74
+ const storedSizes: StoredSizes = {
75
+ panels: panelSizes,
76
+ lastCollapsed: isLastPanelCollapsed ?? false,
77
+ };
78
+ localStorage.setItem(localStorageKey, JSON.stringify(storedSizes));
79
+ }
80
+ }, [panelSizes, localStorageKey, isLastPanelCollapsed]);
81
+
82
+ const toggleLastPanelCollapse = () => {
83
+ if (lastPanelCollapsible) {
84
+ document.startViewTransition(() => {
85
+ flushSync(() => {
86
+ setIsLastPanelCollapsed((prev) => {
87
+ const newCollapsed = !prev;
88
+
89
+ // When expanding, restore default sizes if needed
90
+ if (!newCollapsed && panelSizes) {
91
+ const lastPanelName = panels[panels.length - 1]?.name;
92
+ const lastPanelDefaultSize =
93
+ panels[panels.length - 1]?.defaultSize;
94
+
95
+ if (
96
+ lastPanelName &&
97
+ lastPanelDefaultSize &&
98
+ typeof lastPanelDefaultSize === "number"
99
+ ) {
100
+ // Only restore size if the panel doesn't have a stored size
101
+ if (
102
+ !panelSizes[lastPanelName] ||
103
+ panelSizes[lastPanelName] === 0
104
+ ) {
105
+ const updatedSizes: PanelSizes = { ...panelSizes };
106
+ updatedSizes[lastPanelName] = lastPanelDefaultSize;
107
+
108
+ // Adjust the previous panel to make room
109
+ const prevPanelName = panels[panels.length - 2]?.name;
110
+ if (
111
+ prevPanelName &&
112
+ typeof panelSizes[prevPanelName] === "number"
113
+ ) {
114
+ const prevSize = panelSizes[prevPanelName] as number;
115
+ updatedSizes[prevPanelName] = Math.max(
116
+ 50,
117
+ prevSize - lastPanelDefaultSize,
118
+ );
119
+ }
120
+
121
+ setPanelSizes(updatedSizes);
122
+ }
123
+ }
124
+ }
125
+
126
+ return newCollapsed;
127
+ });
128
+ });
129
+ });
130
+ }
131
+ };
132
+
133
+ const isLastResizer = (index: number) => index === panels.length - 2;
134
+
135
+ const getFlexBasis = (index: number): string => {
136
+ if (!panelSizes) return "1fr"; // Or a default like "1fr"
137
+
138
+ const panelName = panels[index]!.name;
139
+ const size =
140
+ index === panels.length - 1 &&
141
+ isLastPanelCollapsed &&
142
+ lastPanelCollapsible
143
+ ? 0
144
+ : panelSizes[panelName] || panels[index]!.defaultSize;
145
+
146
+ if (typeof size === "number") {
147
+ return `${size}px`;
148
+ } else if (size === "auto") {
149
+ return "auto";
150
+ }
151
+
152
+ return "1fr"; //default
153
+ };
154
+
155
+ const getExpandButton = () => {
156
+ const isHorizontal = direction === "horizontal";
157
+
158
+ return (
159
+ <div
160
+ onClick={!isResizing ? toggleLastPanelCollapse : undefined}
161
+ className="flex cursor-pointer items-center justify-center bg-gray-200 p-1 text-gray-500 select-none hover:bg-blue-700 hover:text-white"
162
+ style={{
163
+ writingMode: isHorizontal ? "vertical-rl" : "horizontal-tb",
164
+ userSelect: "none",
165
+ }}
166
+ >
167
+ {expandLabel}
168
+ </div>
169
+ );
170
+ };
171
+
172
+ const getSplitter = (index: number) => {
173
+ const isHorizontal = direction === "horizontal";
174
+
175
+ const handleDragStart = (clientX: number, clientY: number) => {
176
+ const isHorizontal = direction === "horizontal";
177
+ const startCoord = isHorizontal ? clientX : clientY;
178
+ const initialSizes = panelSizes ? { ...panelSizes } : {};
179
+
180
+ const panelElement = panelRefs.current[index];
181
+ const nextPanelElement = panelRefs.current[index + 1];
182
+
183
+ if (!panelElement || !nextPanelElement) {
184
+ return;
185
+ }
186
+
187
+ const panelSize = isHorizontal
188
+ ? panelElement.offsetWidth
189
+ : panelElement.offsetHeight;
190
+ const nextPanelSize = isHorizontal
191
+ ? nextPanelElement.offsetWidth
192
+ : nextPanelElement.offsetHeight;
193
+
194
+ // Disable pointer events on all iframes to prevent event loss when hovering over them
195
+ const iframes = document.querySelectorAll("iframe");
196
+ const originalPointerEvents = new Map<HTMLIFrameElement, string>();
197
+ iframes.forEach((iframe) => {
198
+ originalPointerEvents.set(iframe, iframe.style.pointerEvents || "auto");
199
+ iframe.style.pointerEvents = "none";
200
+ });
201
+
202
+ setIsResizing(true);
203
+
204
+ let isDragging = true;
205
+
206
+ const handleMove = (moveClientX: number, moveClientY: number) => {
207
+ if (!isDragging) return;
208
+
209
+ const currentCoord = isHorizontal ? moveClientX : moveClientY;
210
+ const delta = currentCoord - startCoord;
211
+ const newPanelSize = panelSize + delta;
212
+ const newNextPanelSize = nextPanelSize - delta;
213
+
214
+ const minPanelSize = 50;
215
+
216
+ // Check if the next panel (which could be the last panel) should be collapsed
217
+ const isNextPanelLast = index + 1 === panels.length - 1;
218
+ const isNextPanelCollapsible = panels[index + 1]?.collapsible;
219
+
220
+ if (
221
+ newNextPanelSize < minPanelSize &&
222
+ isNextPanelLast &&
223
+ isNextPanelCollapsible
224
+ ) {
225
+ // Auto-collapse the last panel when it reaches minimum size
226
+ // Update sizes to ensure the remaining panel gets the full space
227
+ const updatedSizes: PanelSizes = { ...initialSizes };
228
+ updatedSizes[panels[index]!.name] = panelSize + nextPanelSize;
229
+ // Don't set a size for the collapsed panel - let it be handled by the collapsed state
230
+ setPanelSizes(updatedSizes);
231
+ setIsLastPanelCollapsed(true);
232
+ return;
233
+ }
234
+
235
+ if (newPanelSize < minPanelSize || newNextPanelSize < minPanelSize) {
236
+ return;
237
+ }
238
+
239
+ const updatedSizes: PanelSizes = { ...initialSizes };
240
+ updatedSizes[panels[index]!.name] = newPanelSize;
241
+ updatedSizes[panels[index + 1]!.name] = newNextPanelSize;
242
+ setPanelSizes(updatedSizes);
243
+ };
244
+
245
+ const handleEnd = () => {
246
+ isDragging = false;
247
+ setIsResizing(false);
248
+
249
+ // Restore pointer events on all iframes
250
+ originalPointerEvents.forEach((originalValue, iframe) => {
251
+ iframe.style.pointerEvents = originalValue;
252
+ });
253
+
254
+ // Remove all event listeners
255
+ window.removeEventListener("mousemove", onMouseMove);
256
+ window.removeEventListener("mouseup", onMouseUp);
257
+ window.removeEventListener("touchmove", onTouchMove);
258
+ window.removeEventListener("touchend", onTouchEnd);
259
+ };
260
+
261
+ const onMouseMove = (moveEvent: MouseEvent) => {
262
+ handleMove(moveEvent.clientX, moveEvent.clientY);
263
+ };
264
+
265
+ const onMouseUp = () => {
266
+ handleEnd();
267
+ };
268
+
269
+ const onTouchMove = (moveEvent: TouchEvent) => {
270
+ if (moveEvent.touches.length === 1) {
271
+ const touch = moveEvent.touches[0];
272
+ if (touch) {
273
+ handleMove(touch.clientX, touch.clientY);
274
+ }
275
+ }
276
+ };
277
+
278
+ const onTouchEnd = () => {
279
+ handleEnd();
280
+ };
281
+
282
+ // Add both mouse and touch event listeners
283
+ window.addEventListener("mousemove", onMouseMove);
284
+ window.addEventListener("mouseup", onMouseUp);
285
+ window.addEventListener("touchmove", onTouchMove, { passive: false });
286
+ window.addEventListener("touchend", onTouchEnd);
287
+ };
288
+
289
+ if (lastPanelCollapsible && isLastPanelCollapsed && isLastResizer(index)) {
290
+ return getExpandButton();
291
+ }
292
+
293
+ return (
294
+ <div
295
+ className={`relative flex ${
296
+ isHorizontal
297
+ ? "h-full w-[12px] cursor-ew-resize md:w-[5px]"
298
+ : "h-[12px] w-full cursor-ns-resize md:h-[12px]"
299
+ } shrink-0 items-center justify-center bg-gray-300 select-none hover:bg-gray-400`}
300
+ style={{
301
+ touchAction: "none",
302
+ minHeight: isHorizontal ? undefined : "12px",
303
+ minWidth: isHorizontal ? "4px" : undefined,
304
+ boxSizing: "border-box",
305
+ userSelect: "none",
306
+ zIndex: 9999, // Ensure we're above any nested splitters
307
+ }}
308
+ onDoubleClick={
309
+ isLastResizer(index) ? toggleLastPanelCollapse : undefined
310
+ }
311
+ onMouseDown={(event) => {
312
+ handleDragStart(event.clientX, event.clientY);
313
+ }}
314
+ onTouchStart={(event) => {
315
+ if (event.touches.length === 1) {
316
+ const touch = event.touches[0];
317
+ if (touch) {
318
+ handleDragStart(touch.clientX, touch.clientY);
319
+ }
320
+ }
321
+ }}
322
+ >
323
+ {/* Visual indicator line for mobile splitters */}
324
+ <div
325
+ className={`${
326
+ isHorizontal
327
+ ? "h-8 w-[2px] bg-gray-500 md:hidden"
328
+ : "h-[2px] w-8 bg-gray-500 md:hidden"
329
+ }`}
330
+ />
331
+
332
+ {/* Small collapse button for collapsible panels */}
333
+ {lastPanelCollapsible &&
334
+ !isLastPanelCollapsed &&
335
+ isLastResizer(index) && (
336
+ <div
337
+ className={`absolute top-1/2 left-1/2 flex h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 cursor-pointer items-center justify-center rounded-sm text-gray-800 opacity-70 hover:bg-gray-700 hover:text-white hover:opacity-100`}
338
+ onClick={(e) => {
339
+ e.stopPropagation();
340
+ if (!isResizing) {
341
+ toggleLastPanelCollapse();
342
+ }
343
+ }}
344
+ onMouseDown={(e) => {
345
+ e.stopPropagation(); // Prevent drag from starting
346
+ }}
347
+ title="Collapse panel"
348
+ >
349
+ <svg
350
+ width="6"
351
+ height="6"
352
+ viewBox="0 0 6 6"
353
+ fill="none"
354
+ xmlns="http://www.w3.org/2000/svg"
355
+ >
356
+ {isHorizontal ? (
357
+ // Right arrow for horizontal collapse
358
+ <path
359
+ d="M1.5 0.5L4.5 3L1.5 5.5"
360
+ stroke="currentColor"
361
+ strokeWidth="0.8"
362
+ strokeLinecap="round"
363
+ strokeLinejoin="round"
364
+ />
365
+ ) : (
366
+ // Down arrow for vertical collapse
367
+ <path
368
+ d="M0.5 1.5L3 4.5L5.5 1.5"
369
+ stroke="currentColor"
370
+ strokeWidth="0.8"
371
+ strokeLinecap="round"
372
+ strokeLinejoin="round"
373
+ />
374
+ )}
375
+ </svg>
376
+ </div>
377
+ )}
378
+ </div>
379
+ );
380
+ };
381
+
382
+ return (
383
+ <div
384
+ className={`flex ${direction === "horizontal" ? "flex-row" : "flex-col"} h-full w-full overflow-hidden`}
385
+ ref={splitterRef}
386
+ >
387
+ {panels.map((panel, index) => (
388
+ <React.Fragment key={panel.name}>
389
+ <div
390
+ ref={(el) => {
391
+ panelRefs.current[index] = el;
392
+ }}
393
+ className="relative"
394
+ style={{
395
+ flex: `${panel.defaultSize === "auto" ? 1 : 0} 1 ${getFlexBasis(
396
+ index,
397
+ )}`,
398
+
399
+ minWidth: direction === "horizontal" ? 0 : undefined,
400
+ minHeight: direction === "vertical" ? 0 : undefined,
401
+ display:
402
+ (panel.collapsible &&
403
+ isLastPanelCollapsed &&
404
+ index === totalPanels - 1) ||
405
+ panel.hidden
406
+ ? "none"
407
+ : "block",
408
+ }}
409
+ >
410
+ {panel.content}
411
+ </div>
412
+ {index < panels.length - 1 &&
413
+ !panels[index]?.hidden &&
414
+ !panels[index + 1]?.hidden &&
415
+ getSplitter(index)}
416
+ </React.Fragment>
417
+ ))}
418
+ </div>
419
+ );
420
+ };