From 05cfbe033782e96ccbddc1130d568c140e3d6fbf Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 29 May 2025 20:39:39 +0530 Subject: [PATCH 01/33] Fix dropping widget width while dragging --- .../src/AppBuilder/AppCanvas/AppCanvas.jsx | 2 + .../src/AppBuilder/AppCanvas/GhostWidgets.jsx | 15 +++++-- .../src/AppBuilder/AppCanvas/Grid/Grid.css | 1 - .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 41 +++++++++++++++---- .../AppBuilder/AppCanvas/Grid/gridUtils.js | 34 +++++++++++++++ .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 1 - .../AppBuilder/AppCanvas/appCanvasUtils.js | 2 +- .../ComponentsManagerTab/DragLayer.jsx | 16 +++++--- 8 files changed, 92 insertions(+), 20 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx index 365c3bbc59..18e063ec46 100644 --- a/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx +++ b/frontend/src/AppBuilder/AppCanvas/AppCanvas.jsx @@ -17,6 +17,7 @@ import useAppDarkMode from '@/_hooks/useAppDarkMode'; import useAppCanvasMaxWidth from './useAppCanvasMaxWidth'; import { DeleteWidgetConfirmation } from './DeleteWidgetConfirmation'; import useSidebarMargin from './useSidebarMargin'; +import { DragGhostWidget } from './GhostWidgets'; export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { const canvasContainerRef = useRef(); @@ -116,6 +117,7 @@ export const AppCanvas = ({ moduleId, appId, isViewerSidebarPinned }) => { isViewerSidebarPinned={isViewerSidebarPinned} pageSidebarStyle={pageSidebarStyle} /> +
)} diff --git a/frontend/src/AppBuilder/AppCanvas/GhostWidgets.jsx b/frontend/src/AppBuilder/AppCanvas/GhostWidgets.jsx index 1061521a66..55c4802781 100644 --- a/frontend/src/AppBuilder/AppCanvas/GhostWidgets.jsx +++ b/frontend/src/AppBuilder/AppCanvas/GhostWidgets.jsx @@ -1,17 +1,24 @@ import React from 'react'; +import useStore from '@/AppBuilder/_stores/store'; + +export const DragGhostWidget = () => { + const draggingComponentId = useStore((state) => state.draggingComponentId); + + if (!draggingComponentId) return null; -export const DragGhostWidget = ({ isDragging }) => { - if (!isDragging) return ''; return (
+ /> ); }; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css index e1c6bb3baa..c0be54e3e4 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css @@ -173,4 +173,3 @@ } - diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 5ece3710de..6dae1ad972 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -24,11 +24,14 @@ import { handleActivateNonDraggingComponents, computeScrollDelta, computeScrollDeltaOnDrag, + getDraggingWidgetWidth, + positionDragGhostWidget, } from './gridUtils'; import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; import { NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; +import { snapToGrid } from '../appCanvasUtils'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { @@ -119,6 +122,7 @@ export default function Grid({ gridWidth, currentLayout }) { top: widget?.layouts?.[currentLayout]?.top, width: widget?.layouts?.[currentLayout]?.width, parent: widget?.component?.parent, + componentType: widget?.component?.component, component: widget?.component, }; }) @@ -566,6 +570,28 @@ export default function Grid({ gridWidth, currentLayout }) { } }, [draggingComponentId, resizingComponentId, isGroupDragging, selectedComponents]); + useEffect(() => { + const parentCanvasId = boxList.find((box) => box.id === groupedTargets?.[0]?.replace('.ele-', ''))?.parent; + const containerId = `canvas-${parentCanvasId}`; + const canvasContainer = document.getElementById(containerId); + + const handleScroll = () => { + if (groupedTargets.length > 1 && moveableRef.current) { + moveableRef.current.updateRect(); + } + }; + + if (canvasContainer) { + canvasContainer.addEventListener('scroll', handleScroll, { passive: true }); + } + + return () => { + if (canvasContainer) { + canvasContainer.removeEventListener('scroll', handleScroll); + } + }; + }, [groupedTargets.length, boxList]); + if (mode !== 'edit') return null; return ( @@ -588,11 +614,11 @@ export default function Grid({ gridWidth, currentLayout }) { keepRatio={false} individualGroupableProps={individualGroupableProps} onResize={(e) => { - if(resizingComponentId !== e.target.id) { + if (resizingComponentId !== e.target.id) { useGridStore.getState().actions.setResizingComponentId(e.target.id); showGridLines(); } - + const currentWidget = boxList.find(({ id }) => id === e.target.id); let _gridWidth = useGridStore.getState().subContainerWidths[currentWidget.component?.parent] || gridWidth; if (currentWidget.component?.parent) { @@ -935,6 +961,9 @@ export default function Grid({ gridWidth, currentLayout }) { let left = Math.round(e.translate[0] / _gridWidth) * _gridWidth; let top = Math.round(e.translate[1] / GRID_HEIGHT) * GRID_HEIGHT; + const draggingWidgetWidth = getDraggingWidgetWidth(_dragParentId, e.target.clientWidth); + e.target.style.width = `${draggingWidgetWidth}px`; + // This logic is to handle the case when the dragged element is over a new canvas if (_dragParentId !== currentParentId) { left = e.translate[0]; @@ -987,6 +1016,7 @@ export default function Grid({ gridWidth, currentLayout }) { } else if (parentComponent?.component?.component === 'Modal') { // Never update parentId for Modal newParentId = parentComponent?.id; + e.target.style.width = `${e.target.clientWidth}px`; } if (newParentId !== prevDragParentId.current) { @@ -1007,12 +1037,7 @@ export default function Grid({ gridWidth, currentLayout }) { `translate: ${e.translate[0]} | Round: ${Math.round(e.translate[0] / gridWidth) * gridWidth} | ${gridWidth}` ); - // Postion ghost element exactly as same at dragged element - if (document.getElementById(`moveable-drag-ghost`)) { - document.getElementById(`moveable-drag-ghost`).style.transform = `translate(${left}px, ${top}px)`; - document.getElementById(`moveable-drag-ghost`).style.width = `${e.target.clientWidth}px`; - document.getElementById(`moveable-drag-ghost`).style.height = `${e.target.clientHeight}px`; - } + positionDragGhostWidget(e.target); }} onDragGroup={(ev) => { const { events } = ev; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js index f36f921be9..07a56a3c4c 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js @@ -2,6 +2,8 @@ import { useGridStore } from '@/_stores/gridStore'; import { isEmpty } from 'lodash'; import useStore from '@/AppBuilder/_stores/store'; import { getTabId, getSubContainerIdWithSlots } from '../appCanvasUtils'; +import { NO_OF_GRIDS } from '../appCanvasConstants'; + export function correctBounds(layout, bounds) { layout = scaleLayouts(layout); const collidesWith = []; @@ -517,3 +519,35 @@ export const computeScrollDelta = ({ source }) => { }; export const computeScrollDeltaOnDrag = computeScrollDelta; + +export const getDraggingWidgetWidth = (canvasParentId, widgetWidth) => { + const targetCanvasWidth = + document.getElementById(`canvas-${canvasParentId}`)?.offsetWidth || + document.getElementById('real-canvas')?.offsetWidth; + const gridUnitWidth = targetCanvasWidth / NO_OF_GRIDS; + const gridUnits = Math.round(widgetWidth / gridUnitWidth); + const draggingWidgetWidth = gridUnits * gridUnitWidth; + return draggingWidgetWidth; +}; + +export const positionDragGhostWidget = (draggedElement) => { + const ghostElement = document.getElementById('moveable-drag-ghost'); + + if (!ghostElement || !draggedElement) return; + + const mainCanvas = document.getElementById('real-canvas'); + if (!mainCanvas) return; + + const mainCanvasRect = mainCanvas.getBoundingClientRect(); + const draggedRect = draggedElement.getBoundingClientRect(); + + // Calculate position relative to main canvas + const relativeLeft = draggedRect.left - mainCanvasRect.left; + const relativeTop = draggedRect.top - mainCanvasRect.top; + + // Apply the position + ghostElement.style.left = `${relativeLeft}px`; + ghostElement.style.top = `${relativeTop}px`; + ghostElement.style.width = `${draggedRect.width}px`; + ghostElement.style.height = `${draggedRect.height}px`; +}; diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index df78a8afa4..11820c9ce8 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -108,7 +108,6 @@ const WidgetWrapper = memo( onOptionsChange={onOptionsChange} /> - ); diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index 5f362ba0b3..967d1ff719 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -59,6 +59,7 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre left = Math.max(0, NO_OF_GRIDS - width); width = Math.min(width, NO_OF_GRIDS); } + if (currentLayout === 'mobile') { componentData.definition.others.showOnDesktop.value = `{{false}}`; componentData.definition.others.showOnMobile.value = `{{true}}`; @@ -759,7 +760,6 @@ export const getSubContainerWidthAfterPadding = (canvasWidth, componentType, com if (isModalHeader) { const isModalHeaderCloseBtnEnabled = !useStore.getState().getResolvedComponent(componentId)?.properties ?.hideCloseButton; - console.log('isModalHeaderCloseBtnEnabled', isModalHeaderCloseBtnEnabled); padding = 2 * (MODAL_CANVAS_PADDING + (isModalHeaderCloseBtnEnabled ? 56 : 0)); } else { padding = 2 * MODAL_CANVAS_PADDING; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx index 9589ac145b..9fefedcdb5 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx @@ -35,25 +35,31 @@ const CustomDragLayer = ({ size }) => { currentOffset: monitor.getSourceClientOffset(), item: monitor.getItem(), })); - if (!currentOffset) return null; const canvasWidth = item?.canvasWidth; const canvasBounds = item?.canvasRef?.getBoundingClientRect(); const height = size.height; - const mainCanvasWidth = document.getElementById('real-canvas')?.offsetWidth || 0; + const appCanvasWidth = document.getElementById('real-canvas')?.offsetWidth || 0; + + // Calculate width based on the app canvas's grid + let width = (appCanvasWidth * size.width) / NO_OF_GRIDS; - let width = (mainCanvasWidth * size.width) / NO_OF_GRIDS; // Calculate position relative to the current canvas (parent or child) const left = currentOffset.x - (canvasBounds?.left || 0); const top = currentOffset.y - (canvasBounds?.top || 0); - // Adjust position and width if exceeding grid bounds - if (width >= canvasWidth) { + // Ensure width doesn't exceed the current container's width + if (width > canvasWidth) { width = canvasWidth; } + // Snap width to grid (round to nearest grid unit) + const gridUnitWidth = canvasWidth / NO_OF_GRIDS; + const gridUnits = Math.round(width / gridUnitWidth); + width = gridUnits * gridUnitWidth; + const [x, y] = snapToGrid(canvasWidth, left, top); return (
Date: Fri, 30 May 2025 12:59:28 +0530 Subject: [PATCH 02/33] Fix group selection in form --- frontend/src/AppBuilder/Widgets/Form/Form.jsx | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx index 02b2573484..4dd8cc500b 100644 --- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx +++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx @@ -75,13 +75,36 @@ export const Form = function Form(props) { const formContent = { overflow: 'hidden auto', display: 'flex', - height: '100%', + height: canHeight || '100%', paddingTop: `${CONTAINER_FORM_CANVAS_PADDING}px`, paddingBottom: showFooter ? '3px' : '7px', paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, }; + const headerMaxHeight = parseInt(height, 10) - parseInt(footerHeight, 10) - 100 - 10; + const footerMaxHeight = parseInt(height, 10) - parseInt(headerHeight, 10) - 100 - 10; + + const formFooter = { + flexShrink: 0, + paddingTop: '3px', + paddingBottom: '7px', + paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, + paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, + maxHeight: `${footerMaxHeight}px`, + backgroundColor: + ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor, + }; + const formHeader = { + flexShrink: 0, + paddingBottom: '3px', + paddingTop: '7px', + paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, + paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, + maxHeight: `${headerMaxHeight}px`, + backgroundColor: + ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor, + }; const parentRef = useRef(null); const childDataRef = useRef({}); @@ -293,28 +316,6 @@ export const Form = function Form(props) { const roundedHeight = Math.round(maxHeight / 10) * 10; setCanHeight(`${roundedHeight}px`); }, [computedFormBodyHeight, canvasHeight]); - const headerMaxHeight = parseInt(height, 10) - parseInt(footerHeight, 10) - 100 - 10; - const footerMaxHeight = parseInt(height, 10) - parseInt(headerHeight, 10) - 100 - 10; - const formFooter = { - flexShrink: 0, - paddingTop: '3px', - paddingBottom: '7px', - paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, - paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, - maxHeight: `${footerMaxHeight}px`, - backgroundColor: - ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor, - }; - const formHeader = { - flexShrink: 0, - paddingBottom: '3px', - paddingTop: '7px', - paddingLeft: `${CONTAINER_FORM_CANVAS_PADDING}px`, - paddingRight: `${CONTAINER_FORM_CANVAS_PADDING}px`, - maxHeight: `${headerMaxHeight}px`, - backgroundColor: - ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor, - }; return (
)} -
+
{isLoading ? (
) : ( -
+
{!advanced && ( -
+
Date: Fri, 30 May 2025 13:59:54 +0530 Subject: [PATCH 03/33] fix when dropping a component, shadow is coming on top of component manager. --- .../AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx index 9fefedcdb5..761a6a6ce3 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentsManagerTab/DragLayer.jsx @@ -66,11 +66,11 @@ const CustomDragLayer = ({ size }) => { style={{ position: 'fixed', pointerEvents: 'none', - zIndex: 1000, left: canvasBounds?.left || 0, top: canvasBounds?.top || 0, height: `${height}px`, width: `${width}px`, + zIndex: -1, }} >
Date: Fri, 30 May 2025 14:30:39 +0530 Subject: [PATCH 04/33] improve performace on useGroupedTargetsScrollHandler --- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 24 +-------- .../hooks/useGroupedTargetsScrollHandler.js | 51 +++++++++++++++++++ 2 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 frontend/src/AppBuilder/AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 6dae1ad972..505cb9e7ee 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -31,7 +31,7 @@ import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; import './Grid.css'; import { NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; -import { snapToGrid } from '../appCanvasUtils'; +import { useGroupedTargetsScrollHandler } from './hooks/useGroupedTargetsScrollHandler'; const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { @@ -570,27 +570,7 @@ export default function Grid({ gridWidth, currentLayout }) { } }, [draggingComponentId, resizingComponentId, isGroupDragging, selectedComponents]); - useEffect(() => { - const parentCanvasId = boxList.find((box) => box.id === groupedTargets?.[0]?.replace('.ele-', ''))?.parent; - const containerId = `canvas-${parentCanvasId}`; - const canvasContainer = document.getElementById(containerId); - - const handleScroll = () => { - if (groupedTargets.length > 1 && moveableRef.current) { - moveableRef.current.updateRect(); - } - }; - - if (canvasContainer) { - canvasContainer.addEventListener('scroll', handleScroll, { passive: true }); - } - - return () => { - if (canvasContainer) { - canvasContainer.removeEventListener('scroll', handleScroll); - } - }; - }, [groupedTargets.length, boxList]); + useGroupedTargetsScrollHandler(groupedTargets, boxList, moveableRef); if (mode !== 'edit') return null; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js new file mode 100644 index 0000000000..1924ba38ca --- /dev/null +++ b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js @@ -0,0 +1,51 @@ +import { useEffect, useMemo, useCallback, useRef } from 'react'; + +export const useGroupedTargetsScrollHandler = (groupedTargets, boxList, moveableRef) => { + const scrollRAF = useRef(null); // // Stores the requestAnimationFrame ID + + const parentCanvasId = useMemo(() => { + if (!groupedTargets?.[0] || groupedTargets.length === 0) return null; + + const targetId = groupedTargets[0].replace('.ele-', ''); + const targetBox = boxList.find((box) => box.id === targetId); + return targetBox?.parent || null; + }, [groupedTargets, boxList]); + + const containerId = useMemo(() => { + return parentCanvasId ? `canvas-${parentCanvasId}` : null; + }, [parentCanvasId]); + + const scrollHandler = useCallback(() => { + if (!scrollRAF.current) { + scrollRAF.current = requestAnimationFrame(() => { + if (groupedTargets.length > 1 && moveableRef.current) { + console.log('i arrive'); + moveableRef.current.updateRect(); + } + scrollRAF.current = null; + }); + } + }, [groupedTargets.length, moveableRef]); + + useEffect(() => { + // Early return if no container ID or not enough grouped targets + if (!containerId || groupedTargets.length <= 1) { + return; + } + + const canvasContainer = document.getElementById(containerId); + if (!canvasContainer) { + return; + } + console.log(canvasContainer, 'canvas'); + + canvasContainer.addEventListener('scroll', scrollHandler, { passive: true }); + + return () => { + canvasContainer.removeEventListener('scroll', scrollHandler); + if (scrollRAF.current) { + cancelAnimationFrame(scrollRAF.current); + } + }; + }, [containerId, groupedTargets.length, scrollHandler]); +}; From 3fe1119dcb8fbc9a6e151fd68f57cdf7fbda0a1d Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Fri, 30 May 2025 14:32:34 +0530 Subject: [PATCH 05/33] Fix --- .../AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js index 1924ba38ca..180bb8faa6 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useGroupedTargetsScrollHandler.js @@ -19,7 +19,6 @@ export const useGroupedTargetsScrollHandler = (groupedTargets, boxList, moveable if (!scrollRAF.current) { scrollRAF.current = requestAnimationFrame(() => { if (groupedTargets.length > 1 && moveableRef.current) { - console.log('i arrive'); moveableRef.current.updateRect(); } scrollRAF.current = null; @@ -37,7 +36,6 @@ export const useGroupedTargetsScrollHandler = (groupedTargets, boxList, moveable if (!canvasContainer) { return; } - console.log(canvasContainer, 'canvas'); canvasContainer.addEventListener('scroll', scrollHandler, { passive: true }); From 19c74c0a6823155db70415487f60a94d58c7a5ce Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Wed, 18 Jun 2025 21:40:41 +0530 Subject: [PATCH 06/33] initial --- .vscode/settings.json | 2 +- frontend/package-lock.json | 31 +++ .../src/AppBuilder/AppCanvas/Container.jsx | 49 ++++- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 38 +++- .../src/AppBuilder/_hooks/useGhostMoveable.js | 190 ++++++++++++++++++ frontend/src/_stores/gridStore.js | 14 ++ 6 files changed, 317 insertions(+), 7 deletions(-) create mode 100644 frontend/src/AppBuilder/_hooks/useGhostMoveable.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 57abcb74b4..d665a0406f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,6 @@ } ], "CodeGPT.apiKey": "CodeGPT Plus Beta", - "workbench.colorTheme": "Cursor Dark", + "workbench.colorTheme": "Default Dark Modern", "workbench.iconTheme": "vs-seti" } \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4f841128fc..00cc7b313b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -63,6 +63,7 @@ "dotenv": "^16.0.3", "draft-js": "^0.11.7", "draft-js-export-html": "^1.4.1", + "draft-js-import-html": "^1.4.1", "driver.js": "^0.9.8", "emoji-mart": "^5.5.2", "file-loader": "^6.2.0", @@ -30979,6 +30980,31 @@ "immutable": "3.x.x" } }, + "node_modules/draft-js-import-element": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/draft-js-import-element/-/draft-js-import-element-1.4.0.tgz", + "integrity": "sha512-WmYT5PrCm47lGL5FkH6sRO3TTAcn7qNHsD3igiPqLG/RXrqyKrqN4+wBgbcT2lhna/yfWTRtgzAbQsSJoS1Meg==", + "dependencies": { + "draft-js-utils": "^1.4.0", + "synthetic-dom": "^1.4.0" + }, + "peerDependencies": { + "draft-js": ">=0.10.0", + "immutable": "3.x.x" + } + }, + "node_modules/draft-js-import-html": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/draft-js-import-html/-/draft-js-import-html-1.4.1.tgz", + "integrity": "sha512-KOZmtgxZriCDgg5Smr3Y09TjubvXe7rHPy/2fuLSsL+aSzwUDwH/aHDA/k47U+WfpmL4qgyg4oZhqx9TYJV0tg==", + "dependencies": { + "draft-js-import-element": "^1.4.0" + }, + "peerDependencies": { + "draft-js": ">=0.10.0", + "immutable": "3.x.x" + } + }, "node_modules/draft-js-utils": { "version": "1.4.1", "license": "ISC", @@ -44891,6 +44917,11 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/synthetic-dom": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/synthetic-dom/-/synthetic-dom-1.4.0.tgz", + "integrity": "sha512-mHv51ZsmZ+ShT/4s5kg+MGUIhY7Ltq4v03xpN1c8T1Krb5pScsh/lzEjyhrVD0soVDbThbd2e+4dD9vnDG4rhg==" + }, "node_modules/tabbable": { "version": "6.2.0", "license": "MIT" diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index b90eb55380..d09d6afa86 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -4,7 +4,7 @@ import cx from 'classnames'; import WidgetWrapper from './WidgetWrapper'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useDrop } from 'react-dnd'; +import { useDrop, useDragLayer } from 'react-dnd'; import { addChildrenWidgetsToParent, addNewWidgetToTheEditor, @@ -29,6 +29,7 @@ import { ModuleContainerBlank } from '@/modules/Modules/components'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import useSortedComponents from '../_hooks/useSortedComponents'; import { noop } from 'lodash'; +import { useGhostMoveable } from '@/AppBuilder/_hooks/useGhostMoveable'; //TODO: Revisit the logic of height (dropRef) @@ -72,18 +73,62 @@ export const Container = React.memo( const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; + // Initialize ghost moveable hook (only for main canvas) + const { activateGhost, updateGhost, deactivateGhost } = useGhostMoveable(); + + // Monitor drag layer to update ghost position continuously + const { isDragging, currentOffset, dragItem } = useDragLayer((monitor) => ({ + isDragging: monitor.isDragging(), + currentOffset: monitor.getClientOffset(), + dragItem: monitor.getItem(), + })); + + // Update ghost position when dragging over main canvas + useEffect(() => { + if (id === 'canvas' && isDragging && currentOffset && dragItem && !dragItem.id && currentMode === 'edit') { + updateGhost(currentOffset, realCanvasRef); + } + }, [id, isDragging, currentOffset, dragItem, currentMode, updateGhost]); + + // Cleanup ghost when drag ends + useEffect(() => { + if (id === 'canvas' && !isDragging) { + deactivateGhost(); + } + }, [id, isDragging, deactivateGhost]); + const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; }, [index, componentType, currentMode]); const [{ isOverCurrent }, drop] = useDrop({ accept: 'box', - hover: (item) => { + hover: (item, monitor) => { item.canvasRef = realCanvasRef?.current; item.canvasId = id; item.canvasWidth = getContainerCanvasWidth(); + + // Only activate ghost for main canvas and when component is being dragged from sidebar + if (id === 'canvas' && !item.id && currentMode === 'edit') { + const canvasWidthValue = getContainerCanvasWidth(); + const currentGridWidth = canvasWidthValue / NO_OF_GRIDS; + + const componentSize = { + width: (item.component?.defaultSize?.width || 4) * currentGridWidth, + height: item.component?.defaultSize?.height || 40, + }; + + const clientOffset = monitor.getClientOffset(); + if (clientOffset) { + activateGhost(componentSize, clientOffset, realCanvasRef); + } + } }, drop: async ({ componentType, component }, monitor) => { + // Deactivate ghost when dropping + if (id === 'canvas') { + deactivateGhost(); + } setShowModuleBorder(false); // Hide the module border when dropping if (currentMode === 'view' || (appType === 'module' && componentType !== 'ModuleContainer')) return; const didDrop = monitor.didDrop(); diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index d92efe63fb..95dc7bc6c9 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -66,6 +66,7 @@ export default function Grid({ gridWidth, currentLayout }) { const resizingComponentId = useGridStore((state) => state.resizingComponentId, shallow); const [dragParentId, setDragParentId] = useState(null); const [elementGuidelines, setElementGuidelines] = useState([]); + const dynamicElementGuidelines = useGridStore((state) => state.dynamicElementGuidelines, shallow); const componentsSnappedTo = useRef(null); const prevDragParentId = useRef(null); const newDragParentId = useRef(null); @@ -73,6 +74,16 @@ export default function Grid({ gridWidth, currentLayout }) { const checkIfAnyWidgetVisibilityChanged = useStore((state) => state.checkIfAnyWidgetVisibilityChanged(), shallow); const getExposedValueOfComponent = useStore((state) => state.getExposedValueOfComponent, shallow); const setReorderContainerChildren = useStore((state) => state.setReorderContainerChildren, shallow); + const virtualTarget = useGridStore((state) => state.virtualTarget, shallow); + // Set moveable reference in grid store for access by other components + useEffect(() => { + if (moveableRef.current) { + useGridStore.getState().setMoveableRef(moveableRef.current); + } + return () => { + useGridStore.getState().setMoveableRef(null); + }; + }, [moveableRef.current]); useEffect(() => { const selectedSet = new Set(selectedComponents); @@ -105,9 +116,21 @@ export default function Grid({ gridWidth, currentLayout }) { return true; }) .map((box) => `.ele-${box.id}`); - setElementGuidelines(guidelines); - }, [boxList, dragParentId, draggingComponentId, resizingComponentId, selectedComponents, getResolvedValue]); + // Combine static guidelines with dynamic ones (for ghost elements) + const allGuidelines = [...guidelines, ...dynamicElementGuidelines]; + setElementGuidelines(allGuidelines); + }, [ + boxList, + dragParentId, + draggingComponentId, + resizingComponentId, + selectedComponents, + getResolvedValue, + dynamicElementGuidelines, + ]); + + // console.log('boxList', elementGuidelines, dynamicElementGuidelines); useEffect(() => { setBoxList( Object.keys(currentPageComponents) @@ -593,12 +616,18 @@ export default function Grid({ gridWidth, currentLayout }) { customMouseInteraction: groupedTargets.length < 2, multiComponentHandle: groupedTargets.length > 1, }} + resizable={{ + edge: ['e', 'w'], + renderDirections: ['w', 'e'], + }} flushSync={flushSync} - target={groupedTargets?.length > 1 ? groupedTargets : '.target'} + // target={groupedTargets?.length > 1 ? groupedTargets : '.target'} + target={virtualTarget ? virtualTarget : '.target'} origin={false} individualGroupable={groupedTargets.length <= 1} draggable={!shouldFreeze && mode !== 'view'} - resizable={!shouldFreeze ? RESIZABLE_CONFIG : false && mode !== 'view'} + // resizable={true} + // resizable={!shouldFreeze ? RESIZABLE_CONFIG : false && mode !== 'view'} keepRatio={false} individualGroupableProps={individualGroupableProps} onResize={(e) => { @@ -660,6 +689,7 @@ export default function Grid({ gridWidth, currentLayout }) { useGridStore.getState().resizingComponentId !== e.target.id && !e.target.classList.contains('delete-icon') ) { + console.log('resize start', e); // When clicked on widget boundary/resizer, select the component setSelectedComponents([e.target.id]); } diff --git a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js new file mode 100644 index 0000000000..0528e016e8 --- /dev/null +++ b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js @@ -0,0 +1,190 @@ +import { useRef, useCallback, useEffect } from 'react'; +import { useGridStore } from '@/_stores/gridStore'; +import { NO_OF_GRIDS, GRID_HEIGHT } from '@/AppBuilder/AppCanvas/appCanvasConstants'; + +export const useGhostMoveable = () => { + const ghostElementRef = useRef(null); + const isActiveRef = useRef(false); + const cleanupTimeoutRef = useRef(null); + + // Get access to grid store methods + const addToElementGuidelines = useGridStore((state) => state.addToElementGuidelines); + const removeFromElementGuidelines = useGridStore((state) => state.removeFromElementGuidelines); + const getMoveableRef = useGridStore((state) => state.moveableRef); + const setVirtualTarget = useGridStore((state) => state.actions.setVirtualTarget); + + const createGhostElement = useCallback( + (componentSize, canvasRef) => { + if (!canvasRef?.current || ghostElementRef.current) return; + + const ghost = document.createElement('div'); + ghost.id = 'moveable-ghost-element'; + ghost.className = 'moveable-ghost target'; + ghost.style.cssText = ` + position: absolute; + width: ${componentSize.width || 100}px; + height: ${componentSize.height || 40}px; + background: rgba(68, 170, 255, 0.1); + border: 1px dashed #4af; + opacity: 0.7; + pointer-events: none; + z-index: 9998; + box-sizing: border-box; + top: 0; + left: 0; + `; + + const container = document.getElementById('rm-container'); + + container.appendChild(ghost); + ghostElementRef.current = ghost; + + // Add ghost element to guidelines + if (addToElementGuidelines) { + addToElementGuidelines('#moveable-ghost-element'); + } + + return ghost; + }, + [addToElementGuidelines] + ); + + const updateGhostPosition = useCallback((x, y, canvasRef) => { + if (!ghostElementRef.current || !canvasRef?.current) return; + // debugger; + const canvasRect = canvasRef.current.getBoundingClientRect(); + const relativeX = x - canvasRect.left; + const relativeY = y - canvasRect.top; + + // Apply grid snapping similar to existing logic + const gridWidth = canvasRef.current.offsetWidth / NO_OF_GRIDS; + const snappedX = Math.round(relativeX / gridWidth) * gridWidth; + const snappedY = Math.round(relativeY / GRID_HEIGHT) * GRID_HEIGHT; + + ghostElementRef.current.style.transform = `translate(${snappedX}px, ${snappedY}px)`; + }, []); + + const activateGhost = useCallback( + (componentSize, mousePosition, canvasRef) => { + if (isActiveRef.current) return; + + isActiveRef.current = true; + + // Clear any pending cleanup + if (cleanupTimeoutRef.current) { + clearTimeout(cleanupTimeoutRef.current); + cleanupTimeoutRef.current = null; + } + + const ghost = createGhostElement(componentSize, canvasRef); + if (ghost && mousePosition) { + updateGhostPosition(mousePosition.x, mousePosition.y, canvasRef); + + // Trigger moveable drag on the ghost element to show guidelines + // setTimeout(() => { + const moveableInstance = getMoveableRef; + if (moveableInstance && ghost) { + try { + // Update moveable target to include the ghost + + // Create a proper mouse event for dragStart + const fakeEvent = new MouseEvent('mousedown', { + clientX: mousePosition.x, + clientY: mousePosition.y, + bubbles: true, + cancelable: true, + view: window, + button: 0, + buttons: 1, + }); + console.log('moveableInstance', moveableInstance); + moveableInstance.moveable.waitToChangeTarget().then(() => { + console.log('DRAGSTART'); + moveableInstance.moveable.dragStart(fakeEvent, ghost); + }); + setVirtualTarget(ghost); + moveableInstance.updateTarget(); + moveableInstance.updateRect(); + } catch (error) { + console.warn('Failed to trigger moveable dragStart:', error); + } + } + // }, 10); // Small delay to ensure DOM is updated + } + }, + [createGhostElement, updateGhostPosition, getMoveableRef] + ); + + const updateGhost = useCallback( + (mousePosition, canvasRef) => { + if (!isActiveRef.current || !mousePosition) return; + updateGhostPosition(mousePosition.x, mousePosition.y, canvasRef); + + // Trigger moveable drag event to update guidelines + const moveableInstance = getMoveableRef; + if (moveableInstance && ghostElementRef.current) { + try { + // Once dragStart is called, moveable automatically handles + // drag events, so we just need to update position + // The guidelines will update automatically + } catch (error) { + // Silently fail for mousemove events to avoid spam + } + } + }, + [updateGhostPosition, getMoveableRef] + ); + + const deactivateGhost = useCallback(() => { + if (!isActiveRef.current) return; + + isActiveRef.current = false; + + // End moveable drag if it's active + const moveableInstance = getMoveableRef; + if (moveableInstance && ghostElementRef.current) { + try { + // Stop any ongoing drag + // moveableInstance.stopDrag(); + setVirtualTarget(null); + } catch (error) { + console.warn('Failed to trigger moveable dragEnd:', error); + } + } + + // Remove from guidelines immediately + if (removeFromElementGuidelines) { + removeFromElementGuidelines('#moveable-ghost-element'); + } + + // Delay cleanup to avoid flickering + cleanupTimeoutRef.current = setTimeout(() => { + if (ghostElementRef.current) { + ghostElementRef.current.remove(); + ghostElementRef.current = null; + } + }, 100); + }, [removeFromElementGuidelines, getMoveableRef]); + + // Cleanup on unmount + useEffect(() => { + return () => { + if (cleanupTimeoutRef.current) { + clearTimeout(cleanupTimeoutRef.current); + } + if (ghostElementRef.current) { + ghostElementRef.current.remove(); + } + if (removeFromElementGuidelines) { + removeFromElementGuidelines('#moveable-ghost-element'); + } + }; + }, [removeFromElementGuidelines]); + + return { + activateGhost, + updateGhost, + deactivateGhost, + isActive: isActiveRef.current, + }; +}; diff --git a/frontend/src/_stores/gridStore.js b/frontend/src/_stores/gridStore.js index 213f07ac16..4df65d5258 100644 --- a/frontend/src/_stores/gridStore.js +++ b/frontend/src/_stores/gridStore.js @@ -12,6 +12,9 @@ const initialState = { idGroupDragged: false, openModalWidgetId: null, subContainerWidths: {}, + dynamicElementGuidelines: [], + moveableRef: null, + virtualTarget: null, }; export const useGridStore = create( @@ -26,7 +29,18 @@ export const useGridStore = create( setOpenModalWidgetId: (openModalWidgetId) => set({ openModalWidgetId }), setSubContainerWidths: (id, width) => set((state) => ({ subContainerWidths: { ...state.subContainerWidths, [id]: width } })), + setVirtualTarget: (target) => set({ virtualTarget: target }), }, + addToElementGuidelines: (selector) => + set((state) => ({ + dynamicElementGuidelines: [...state.dynamicElementGuidelines, selector], + })), + removeFromElementGuidelines: (selector) => + set((state) => ({ + dynamicElementGuidelines: state.dynamicElementGuidelines.filter((item) => item !== selector), + })), + clearDynamicElementGuidelines: () => set({ dynamicElementGuidelines: [] }), + setMoveableRef: (ref) => set({ moveableRef: ref }), }), { name: 'Grid Store' } ) From df9c81d48314a39958665bcb77863932b2150d12 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Wed, 25 Jun 2025 09:51:31 +0530 Subject: [PATCH 07/33] Fix guidelines --- frontend/package-lock.json | 9 +- frontend/package.json | 2 +- .../src/AppBuilder/AppCanvas/Container.jsx | 97 +++++---- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 162 +++++++++------ .../Grid/hooks/useElementGudelines.js | 75 +++++++ .../ComponentManagerTab/DragLayer.jsx | 16 +- .../src/AppBuilder/_hooks/useGhostMoveable.js | 190 ++++++------------ frontend/src/_stores/gridStore.js | 3 +- 8 files changed, 312 insertions(+), 242 deletions(-) create mode 100644 frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 00cc7b313b..27d012b4a1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -119,7 +119,7 @@ "react-loading-skeleton": "^3.1.1", "react-markdown": "^9.0.0", "react-mentions": "^4.4.7", - "react-moveable": "^0.54.1", + "react-moveable": "^0.56.0", "react-multi-select-component": "^4.3.4", "react-pdf": "^6.2.2", "react-phone-input-2": "^2.15.1", @@ -41915,8 +41915,9 @@ } }, "node_modules/react-moveable": { - "version": "0.54.2", - "license": "MIT", + "version": "0.56.0", + "resolved": "https://registry.npmjs.org/react-moveable/-/react-moveable-0.56.0.tgz", + "integrity": "sha512-FmJNmIOsOA36mdxbrc/huiE4wuXSRlmon/o+/OrfNhSiYYYL0AV5oObtPluEhb2Yr/7EfYWBHTxF5aWAvjg1SA==", "dependencies": { "@daybrush/utils": "^1.13.0", "@egjs/agent": "^2.2.1", @@ -41927,7 +41928,7 @@ "@scena/matrix": "^1.1.1", "css-to-mat": "^1.1.1", "framework-utils": "^1.1.0", - "gesto": "^1.19.0", + "gesto": "^1.19.3", "overlap-area": "^1.1.0", "react-css-styled": "^1.1.9", "react-selecto": "^1.25.0" diff --git a/frontend/package.json b/frontend/package.json index 22f34ae313..e96b8878ee 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -114,7 +114,7 @@ "react-loading-skeleton": "^3.1.1", "react-markdown": "^9.0.0", "react-mentions": "^4.4.7", - "react-moveable": "^0.54.1", + "react-moveable": "^0.56.0", "react-multi-select-component": "^4.3.4", "react-pdf": "^6.2.2", "react-phone-input-2": "^2.15.1", diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index d09d6afa86..be6aab2803 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -16,9 +16,6 @@ import { NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN, GRID_HEIGHT, - CONTAINER_FORM_CANVAS_PADDING, - SUBCONTAINER_CANVAS_BORDER_WIDTH, - BOX_PADDING, } from './appCanvasConstants'; import { useGridStore } from '@/_stores/gridStore'; import NoComponentCanvasContainer from './NoComponentCanvasContainer'; @@ -74,61 +71,78 @@ export const Container = React.memo( const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; // Initialize ghost moveable hook (only for main canvas) - const { activateGhost, updateGhost, deactivateGhost } = useGhostMoveable(); + const { activateGhost, deactivateGhost } = useGhostMoveable(id); // Monitor drag layer to update ghost position continuously - const { isDragging, currentOffset, dragItem } = useDragLayer((monitor) => ({ + const { isDragging } = useDragLayer((monitor) => ({ isDragging: monitor.isDragging(), - currentOffset: monitor.getClientOffset(), - dragItem: monitor.getItem(), })); - // Update ghost position when dragging over main canvas - useEffect(() => { - if (id === 'canvas' && isDragging && currentOffset && dragItem && !dragItem.id && currentMode === 'edit') { - updateGhost(currentOffset, realCanvasRef); - } - }, [id, isDragging, currentOffset, dragItem, currentMode, updateGhost]); - - // Cleanup ghost when drag ends - useEffect(() => { - if (id === 'canvas' && !isDragging) { - deactivateGhost(); - } - }, [id, isDragging, deactivateGhost]); + // // Cleanup ghost when drag ends + // useEffect(() => { + // if (!isDragging) { + // setTimeout(() => { + // deactivateGhost(); + // }, 1000); + // } + // }, [id, isDragging, deactivateGhost]); const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; }, [index, componentType, currentMode]); + const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); + const [{ isOverCurrent }, drop] = useDrop({ accept: 'box', - hover: (item, monitor) => { - item.canvasRef = realCanvasRef?.current; - item.canvasId = id; - item.canvasWidth = getContainerCanvasWidth(); + hover: (item, monitor) => { + // Use mouse position to determine the most specific container + const clientOffset = monitor.getClientOffset(); + + // If no client offset, the drag might be ending - clean up ghost + // if (!clientOffset) { + // deactivateGhost(); + // return; + // } + + const appCanvasWidth = realCanvasRef?.current?.offsetWidth || 0; - // Only activate ghost for main canvas and when component is being dragged from sidebar - if (id === 'canvas' && !item.id && currentMode === 'edit') { - const canvasWidthValue = getContainerCanvasWidth(); - const currentGridWidth = canvasWidthValue / NO_OF_GRIDS; - - const componentSize = { - width: (item.component?.defaultSize?.width || 4) * currentGridWidth, - height: item.component?.defaultSize?.height || 40, - }; - - const clientOffset = monitor.getClientOffset(); - if (clientOffset) { - activateGhost(componentSize, clientOffset, realCanvasRef); + if (clientOffset) { + const elementAtPoint = document.elementFromPoint(clientOffset.x, clientOffset.y); + const closestCanvas = elementAtPoint?.closest('.real-canvas'); + const canvasId = closestCanvas?.getAttribute('data-parentId') || + closestCanvas?.id?.replace('canvas-', '') || + (closestCanvas?.id === 'real-canvas' ? 'canvas' : null); + + // Only update if this container is the most specific one under the mouse + if (canvasId === id) { + setCurrentDragCanvasId(id); } } + // Calculate width based on the app canvas's grid + let width = (appCanvasWidth * item.component?.defaultSize?.width) / NO_OF_GRIDS; + const componentSize = { + width: width, + height: item.component?.defaultSize?.height + }; + + // const clientOffset = monitor.getClientOffset(); + if (clientOffset) { + activateGhost(componentSize, clientOffset, realCanvasRef); + } }, drop: async ({ componentType, component }, monitor) => { + console.log('drop'); + // Reset canvas ID when dropping + setCurrentDragCanvasId(null); + + // Ensure ghost is deactivated before processing drop + deactivateGhost(); + + // Add a small delay to allow moveable to properly clean up + // await new Promise(resolve => setTimeout(resolve, 10)); + // Deactivate ghost when dropping - if (id === 'canvas') { - deactivateGhost(); - } setShowModuleBorder(false); // Hide the module border when dropping if (currentMode === 'view' || (appType === 'module' && componentType !== 'ModuleContainer')) return; const didDrop = monitor.didDrop(); @@ -163,7 +177,6 @@ export const Container = React.memo( const childComponents = addChildrenWidgetsToParent(componentType, parentComponent?.id, currentLayout); const newComponents = [parentComponent, ...childComponents]; await addComponentToCurrentPage(newComponents); - // setSelectedComponents([parentComponent?.id]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); } else { const newComponent = addNewWidgetToTheEditor( @@ -175,7 +188,6 @@ export const Container = React.memo( moduleInfo ); await addComponentToCurrentPage([newComponent]); - // setSelectedComponents([newComponent?.id]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); } }, @@ -199,6 +211,7 @@ export const Container = React.memo( return realCanvasRef?.current?.offsetWidth; } const gridWidth = getContainerCanvasWidth() / NO_OF_GRIDS; + useEffect(() => { useGridStore.getState().actions.setSubContainerWidths(id, gridWidth); // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 3b0a4a7611..2e15c2926b 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -1,5 +1,5 @@ // import '@/Editor/wdyr'; -import React, { useEffect, useState, useRef, useCallback } from 'react'; +import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; // eslint-disable-next-line import/no-unresolved import Moveable from 'react-moveable'; import { shallow } from 'zustand/shallow'; @@ -10,10 +10,8 @@ import { useGridStore, useIsGroupHandleHoverd, useOpenModalWidgetId } from '@/_s import toast from 'react-hot-toast'; import { individualGroupableProps, - getMouseDistanceFromParentDiv, findChildrenAndGrandchildren, findHighestLevelofSelection, - getOffset, hasParentWithClass, getPositionForGroupDrag, adjustWidth, @@ -33,6 +31,8 @@ import './Grid.css'; import { useGroupedTargetsScrollHandler } from './hooks/useGroupedTargetsScrollHandler'; import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; +import { useElementGudelines } from './hooks/useElementGudelines'; + const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { edge: ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'], @@ -68,8 +68,16 @@ export default function Grid({ gridWidth, currentLayout }) { const draggingComponentId = useStore((state) => state.draggingComponentId, shallow); const resizingComponentId = useGridStore((state) => state.resizingComponentId, shallow); const [dragParentId, setDragParentId] = useState(null); - const [elementGuidelines, setElementGuidelines] = useState([]); - const dynamicElementGuidelines = useGridStore((state) => state.dynamicElementGuidelines, shallow); + const virtualTarget = useGridStore((state) => state.virtualTarget, shallow); + const { elementGuidelines } = useElementGudelines( + boxList, + selectedComponents, + draggingComponentId, + resizingComponentId, + dragParentId, + getResolvedValue, + virtualTarget + ); const componentsSnappedTo = useRef(null); const prevDragParentId = useRef(null); const newDragParentId = useRef(null); @@ -77,7 +85,20 @@ export default function Grid({ gridWidth, currentLayout }) { const checkIfAnyWidgetVisibilityChanged = useStore((state) => state.checkIfAnyWidgetVisibilityChanged(), shallow); const getExposedValueOfComponent = useStore((state) => state.getExposedValueOfComponent, shallow); const setReorderContainerChildren = useStore((state) => state.setReorderContainerChildren, shallow); - const virtualTarget = useGridStore((state) => state.virtualTarget, shallow); + const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); + const snapContainer = useMemo(() => { + if (currentDragCanvasId) { + return `#canvas-${currentDragCanvasId}`; + } + return '#real-canvas'; + }, [currentDragCanvasId]); + const moveableTarget = useMemo(() => { + if (virtualTarget) { + return '#moveable-ghost-element'; + } + return groupedTargets?.length > 1 ? groupedTargets : '.target'; + }, [virtualTarget, groupedTargets]); + // Set moveable reference in grid store for access by other components useEffect(() => { if (moveableRef.current) { @@ -86,54 +107,59 @@ export default function Grid({ gridWidth, currentLayout }) { return () => { useGridStore.getState().setMoveableRef(null); }; - }, [moveableRef.current]); + }, []); - useEffect(() => { - const selectedSet = new Set(selectedComponents); - const draggingOrResizingId = draggingComponentId || resizingComponentId; - const isGrouped = findHighestLevelofSelection().length > 1; - const firstSelectedParent = - selectedComponents.length > 0 ? boxList.find((b) => b.id === selectedComponents[0])?.parent : null; - const selectedParent = dragParentId || firstSelectedParent; + // useEffect(() => { + // const selectedSet = new Set(selectedComponents); + // const draggingOrResizingId = draggingComponentId || resizingComponentId; + // const isGrouped = findHighestLevelofSelection().length > 1; + // const firstSelectedParent = + // selectedComponents.length > 0 ? boxList.find((b) => b.id === selectedComponents[0])?.parent : null; + // const selectedParent = dragParentId || firstSelectedParent; - const guidelines = boxList - .filter((box) => { - const isVisible = - getResolvedValue(box?.component?.definition?.properties?.visibility?.value) || - getResolvedValue(box?.component?.definition?.styles?.visibility?.value); + // const guidelines = boxList + // .filter((box) => { + // const isVisible = + // getResolvedValue(box?.component?.definition?.properties?.visibility?.value) || + // getResolvedValue(box?.component?.definition?.styles?.visibility?.value); - // Early return for non-visible elements - if (!isVisible) return false; + // // Early return for non-visible elements + // if (!isVisible) return false; - if (isGrouped) { - // If component is selected, don't show its guidelines - if (selectedSet.has(box.id)) return false; - return selectedParent ? box.parent === selectedParent : !box.parent; - } + // // if (virtualTarget) { + // // return true; + // // } - if (draggingOrResizingId) { - if (box.id === draggingOrResizingId) return false; - return dragParentId ? box.parent === dragParentId : !box.parent; - } + // if (isGrouped) { + // // If component is selected, don't show its guidelines + // if (selectedSet.has(box.id)) return false; + // return selectedParent ? box.parent === selectedParent : !box.parent; + // } - return true; - }) - .map((box) => `.ele-${box.id}`); + // if (draggingOrResizingId) { + // if (box.id === draggingOrResizingId) return false; + // return dragParentId ? box.parent === dragParentId : !box.parent; + // } - // Combine static guidelines with dynamic ones (for ghost elements) - const allGuidelines = [...guidelines, ...dynamicElementGuidelines]; - setElementGuidelines(allGuidelines); - }, [ - boxList, - dragParentId, - draggingComponentId, - resizingComponentId, - selectedComponents, - getResolvedValue, - dynamicElementGuidelines, - ]); + // // if (virtualTarget) { + // // return true; + // // } + + // return true; + // }) + // .map((box) => `.ele-${box.id}`); + + // // Combine static guidelines with dynamic ones (for ghost elements) + // setElementGuidelines(guidelines); + // }, [ + // boxList, + // dragParentId, + // draggingComponentId, + // resizingComponentId, + // selectedComponents, + // getResolvedValue, + // ]); - // console.log('boxList', elementGuidelines, dynamicElementGuidelines); useEffect(() => { setBoxList( Object.keys(currentPageComponents) @@ -608,9 +634,7 @@ export default function Grid({ gridWidth, currentLayout }) { }, [draggingComponentId, resizingComponentId, isGroupDragging, selectedComponents]); useGroupedTargetsScrollHandler(groupedTargets, boxList, moveableRef); - if (mode !== 'edit') return null; - return ( <> 1, }} - resizable={{ - edge: ['e', 'w'], - renderDirections: ['w', 'e'], - }} flushSync={flushSync} - // target={groupedTargets?.length > 1 ? groupedTargets : '.target'} - target={virtualTarget ? virtualTarget : '.target'} + target={moveableTarget} origin={false} - individualGroupable={groupedTargets.length <= 1} + individualGroupable={virtualTarget ? false : groupedTargets.length <= 1} draggable={!shouldFreeze && mode !== 'view'} - // resizable={true} - // resizable={!shouldFreeze ? RESIZABLE_CONFIG : false && mode !== 'view'} + resizable={!shouldFreeze ? RESIZABLE_CONFIG : false && mode !== 'view'} keepRatio={false} individualGroupableProps={individualGroupableProps} onResize={(e) => { @@ -695,7 +713,6 @@ export default function Grid({ gridWidth, currentLayout }) { useGridStore.getState().resizingComponentId !== e.target.id && !e.target.classList.contains('delete-icon') ) { - console.log('resize start', e); // When clicked on widget boundary/resizer, select the component setSelectedComponents([e.target.id]); } @@ -859,6 +876,9 @@ export default function Grid({ gridWidth, currentLayout }) { }} checkInput onDragStart={(e) => { + if (e.target.id === 'moveable-ghost-element') { + return true; + } // This is to prevent parent component from being dragged and the stop the propagation of the event if (getHoveredComponentForGrid() !== e.target.id) { return false; @@ -909,7 +929,13 @@ export default function Grid({ gridWidth, currentLayout }) { handleActivateNonDraggingComponents(); }} onDragEnd={(e) => { + console.log('e.lastEvent', moveableRef); handleDeactivateTargets(); + if (e.target.id === 'moveable-ghost-element') { + console.log('e.target', false); + // e.target.remove(); + return; + } try { if (isDraggingRef.current) { useStore.getState().setDraggingComponentId(null); @@ -922,7 +948,6 @@ export default function Grid({ gridWidth, currentLayout }) { setDragParentId(null); if (!e.lastEvent) return; - // Build the drag context from the event const dragContext = dragContextBuilder({ event: e, widgets: boxList, isModuleEditor }); const { target, source, dragged } = dragContext; @@ -969,6 +994,21 @@ export default function Grid({ gridWidth, currentLayout }) { toggleCanvasUpdater(); }} onDrag={(e) => { + if (e.target.id === 'moveable-ghost-element') { + // showGridLines(); + // + const _gridWidth = useGridStore.getState().subContainerWidths[currentDragCanvasId] || gridWidth; + let left = e.translate[0]; + let top = e.translate[1]; + // console.log('e.translate', e.translate); + if (currentDragCanvasId === 'canvas') { + left = Math.round(e.translate[0] / _gridWidth) * _gridWidth; + top = Math.round(e.translate[1] / GRID_HEIGHT) * GRID_HEIGHT; + } + e.target.style.transform = `translate(${left}px, ${top}px)`; + console.log('e.target', false); + return false; + } // Since onDrag is called multiple times when dragging, hence we are using isDraggingRef to prevent setting state again and again if (!isDraggingRef.current) { useStore.getState().setDraggingComponentId(e.target.id); @@ -1157,8 +1197,10 @@ export default function Grid({ gridWidth, currentLayout }) { component.element.classList.add('active-target'); } }} - snapGridAll={true} + // snapGridAll={true} scrollable={true} + snapContainer={snapContainer} + snapGridWidth={100} /> ); diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js new file mode 100644 index 0000000000..6b6ea394da --- /dev/null +++ b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js @@ -0,0 +1,75 @@ +import { useEffect, useState } from 'react'; +import { findHighestLevelofSelection } from '../gridUtils'; +import { useGridStore } from '@/_stores/gridStore'; + +export const useElementGudelines = ( + boxList, + selectedComponents, + draggingComponentId, + resizingComponentId, + dragParentId, + getResolvedValue, + virtualTarget +) => { + const [elementGuidelines, setElementGuidelines] = useState([]); + + // Get current drag canvas ID from store instead of drag layer + const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId); + + useEffect(() => { + const selectedSet = new Set(selectedComponents); + const draggingOrResizingId = draggingComponentId || resizingComponentId; + const isGrouped = findHighestLevelofSelection().length > 1; + const firstSelectedParent = + selectedComponents.length > 0 ? boxList.find((b) => b.id === selectedComponents[0])?.parent : null; + const selectedParent = dragParentId || firstSelectedParent; + + const guidelines = boxList + .filter((box) => { + const isVisible = + getResolvedValue(box?.component?.definition?.properties?.visibility?.value) || + getResolvedValue(box?.component?.definition?.styles?.visibility?.value); + + // Early return for non-visible elements + if (!isVisible) return false; + + // This block is for first time drop using react-dnd + if (virtualTarget && currentDragCanvasId !== null) { + // For main canvas (id = 'canvas'), show components with no parent or parent = 'canvas' + if (currentDragCanvasId === 'canvas') { + if (box.parent && box.parent !== 'canvas') return false; + } else { + // For sub-containers, only show components whose parent matches the canvasId + if (box.parent !== currentDragCanvasId) return false; + } + } + + if (isGrouped) { + // If component is selected, don't show its guidelines + if (selectedSet.has(box.id)) return false; + return selectedParent ? box.parent === selectedParent : !box.parent; + } + + if (draggingOrResizingId) { + if (box.id === draggingOrResizingId) return false; + return dragParentId ? box.parent === dragParentId : !box.parent; + } + + return true; + }) + .map((box) => `.ele-${box.id}`); + + setElementGuidelines(guidelines); + }, [ + boxList, + dragParentId, + draggingComponentId, + resizingComponentId, + selectedComponents, + getResolvedValue, + currentDragCanvasId, + virtualTarget, + ]); + + return { elementGuidelines }; +}; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index 4fd20fd8bd..d0d6249c98 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import { WidgetBox } from '../WidgetBox'; import { ModuleWidgetBox } from '@/modules/Modules/components'; import { useDrag, useDragLayer } from 'react-dnd'; @@ -9,6 +9,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { noop } from 'lodash'; +import { useGridStore } from '@/_stores/gridStore'; export const DragLayer = ({ index, component, isModuleTab = false }) => { const { isModuleEditor } = useModuleContext(); @@ -21,7 +22,9 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { }), [component.component] ); - + const getMoveableRef = useGridStore((state) => state.moveableRef); + const setVirtualTarget = useGridStore((state) => state.actions.setVirtualTarget); + const newDiv = useRef(null); useEffect(() => { preview(getEmptyImage(), { captureDraggingState: true }); }, []); @@ -38,12 +41,15 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { // ? component.module_container.layouts[currentLayout] // : component.defaultSize || { width: 30, height: 40 }; - const size = component.defaultSize || { width: 30, height: 40 }; return ( <> - {isDragging && } -
+ {/* {isDragging && } */} +
{isModuleTab ? : }
diff --git a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js index 0528e016e8..eee05f6157 100644 --- a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js +++ b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js @@ -1,31 +1,26 @@ -import { useRef, useCallback, useEffect } from 'react'; +import { useRef } from 'react'; import { useGridStore } from '@/_stores/gridStore'; import { NO_OF_GRIDS, GRID_HEIGHT } from '@/AppBuilder/AppCanvas/appCanvasConstants'; +import { snapToGrid } from '@/AppBuilder/AppCanvas/appCanvasUtils'; -export const useGhostMoveable = () => { +export const useGhostMoveable = (canvasId) => { const ghostElementRef = useRef(null); const isActiveRef = useRef(false); - const cleanupTimeoutRef = useRef(null); - // Get access to grid store methods - const addToElementGuidelines = useGridStore((state) => state.addToElementGuidelines); - const removeFromElementGuidelines = useGridStore((state) => state.removeFromElementGuidelines); const getMoveableRef = useGridStore((state) => state.moveableRef); const setVirtualTarget = useGridStore((state) => state.actions.setVirtualTarget); - const createGhostElement = useCallback( - (componentSize, canvasRef) => { - if (!canvasRef?.current || ghostElementRef.current) return; + const createGhostElement = (componentSize) => { + if (ghostElementRef.current) return; - const ghost = document.createElement('div'); - ghost.id = 'moveable-ghost-element'; - ghost.className = 'moveable-ghost target'; - ghost.style.cssText = ` + const ghost = document.createElement('div'); + ghost.id = 'moveable-ghost-element'; + ghost.className = 'moveable-ghost target'; + ghost.style.cssText = ` position: absolute; width: ${componentSize.width || 100}px; height: ${componentSize.height || 40}px; - background: rgba(68, 170, 255, 0.1); - border: 1px dashed #4af; + background: #D9E2FC; opacity: 0.7; pointer-events: none; z-index: 9998; @@ -34,157 +29,94 @@ export const useGhostMoveable = () => { left: 0; `; - const container = document.getElementById('rm-container'); + // const container = document.querySelectorAll(`[component-id="${canvasId}"]`)[0]; + const container = document.getElementById('real-canvas'); + container.appendChild(ghost); + ghostElementRef.current = ghost; - container.appendChild(ghost); - ghostElementRef.current = ghost; + return ghost; + }; - // Add ghost element to guidelines - if (addToElementGuidelines) { - addToElementGuidelines('#moveable-ghost-element'); - } + const updateGhostPosition = (mousePosition, canvasRef) => { + if (!ghostElementRef.current || !canvasRef?.current || !mousePosition) return; - return ghost; - }, - [addToElementGuidelines] - ); - - const updateGhostPosition = useCallback((x, y, canvasRef) => { - if (!ghostElementRef.current || !canvasRef?.current) return; - // debugger; const canvasRect = canvasRef.current.getBoundingClientRect(); - const relativeX = x - canvasRect.left; - const relativeY = y - canvasRect.top; + const relativeX = mousePosition.x - canvasRect.left; + const relativeY = mousePosition.y - canvasRect.top; - // Apply grid snapping similar to existing logic + // Apply grid snapping const gridWidth = canvasRef.current.offsetWidth / NO_OF_GRIDS; const snappedX = Math.round(relativeX / gridWidth) * gridWidth; const snappedY = Math.round(relativeY / GRID_HEIGHT) * GRID_HEIGHT; - + console.log(snappedX, snappedY); ghostElementRef.current.style.transform = `translate(${snappedX}px, ${snappedY}px)`; - }, []); + }; - const activateGhost = useCallback( - (componentSize, mousePosition, canvasRef) => { - if (isActiveRef.current) return; + const activateGhost = (componentSize, mousePosition, canvasRef) => { + if (isActiveRef.current) return; - isActiveRef.current = true; + isActiveRef.current = true; - // Clear any pending cleanup - if (cleanupTimeoutRef.current) { - clearTimeout(cleanupTimeoutRef.current); - cleanupTimeoutRef.current = null; - } + const ghost = createGhostElement(componentSize, canvasRef); + if (ghost && mousePosition) { + updateGhostPosition(mousePosition, canvasRef); - const ghost = createGhostElement(componentSize, canvasRef); - if (ghost && mousePosition) { - updateGhostPosition(mousePosition.x, mousePosition.y, canvasRef); - - // Trigger moveable drag on the ghost element to show guidelines - // setTimeout(() => { - const moveableInstance = getMoveableRef; - if (moveableInstance && ghost) { - try { - // Update moveable target to include the ghost - - // Create a proper mouse event for dragStart - const fakeEvent = new MouseEvent('mousedown', { - clientX: mousePosition.x, - clientY: mousePosition.y, - bubbles: true, - cancelable: true, - view: window, - button: 0, - buttons: 1, - }); - console.log('moveableInstance', moveableInstance); - moveableInstance.moveable.waitToChangeTarget().then(() => { - console.log('DRAGSTART'); - moveableInstance.moveable.dragStart(fakeEvent, ghost); - }); - setVirtualTarget(ghost); - moveableInstance.updateTarget(); - moveableInstance.updateRect(); - } catch (error) { - console.warn('Failed to trigger moveable dragStart:', error); - } - } - // }, 10); // Small delay to ensure DOM is updated - } - }, - [createGhostElement, updateGhostPosition, getMoveableRef] - ); - - const updateGhost = useCallback( - (mousePosition, canvasRef) => { - if (!isActiveRef.current || !mousePosition) return; - updateGhostPosition(mousePosition.x, mousePosition.y, canvasRef); - - // Trigger moveable drag event to update guidelines + // Trigger moveable drag on the ghost element to show guidelines const moveableInstance = getMoveableRef; - if (moveableInstance && ghostElementRef.current) { + if (moveableInstance && ghost) { try { - // Once dragStart is called, moveable automatically handles - // drag events, so we just need to update position - // The guidelines will update automatically + const fakeEvent = new MouseEvent('mousedown', { + clientX: mousePosition.x, + clientY: mousePosition.y, + bubbles: true, + cancelable: true, + view: window, + button: 0, + buttons: 1, + }); + moveableInstance.waitToChangeTarget().then((e) => { + moveableInstance.dragStart(fakeEvent, ghost); + }); + setVirtualTarget(ghost); } catch (error) { - // Silently fail for mousemove events to avoid spam + console.warn('Failed to trigger moveable dragStart:', error); } } - }, - [updateGhostPosition, getMoveableRef] - ); + } + }; - const deactivateGhost = useCallback(() => { + const deactivateGhost = () => { if (!isActiveRef.current) return; isActiveRef.current = false; - // End moveable drag if it's active const moveableInstance = getMoveableRef; if (moveableInstance && ghostElementRef.current) { try { - // Stop any ongoing drag - // moveableInstance.stopDrag(); setVirtualTarget(null); + ghostElementRef.current.remove(); + ghostElementRef.current = null; + // End any active drag operation first + // moveableInstance.dragEnd(); + + } catch (error) { console.warn('Failed to trigger moveable dragEnd:', error); } } + }; - // Remove from guidelines immediately - if (removeFromElementGuidelines) { - removeFromElementGuidelines('#moveable-ghost-element'); + // New function to update ghost position during continuous hover + const updateGhostOnHover = (mousePosition, canvasRef) => { + if (isActiveRef.current) { + updateGhostPosition(mousePosition, canvasRef); } - - // Delay cleanup to avoid flickering - cleanupTimeoutRef.current = setTimeout(() => { - if (ghostElementRef.current) { - ghostElementRef.current.remove(); - ghostElementRef.current = null; - } - }, 100); - }, [removeFromElementGuidelines, getMoveableRef]); - - // Cleanup on unmount - useEffect(() => { - return () => { - if (cleanupTimeoutRef.current) { - clearTimeout(cleanupTimeoutRef.current); - } - if (ghostElementRef.current) { - ghostElementRef.current.remove(); - } - if (removeFromElementGuidelines) { - removeFromElementGuidelines('#moveable-ghost-element'); - } - }; - }, [removeFromElementGuidelines]); + }; return { activateGhost, - updateGhost, deactivateGhost, + updateGhostOnHover, // New function for continuous updates isActive: isActiveRef.current, }; }; diff --git a/frontend/src/_stores/gridStore.js b/frontend/src/_stores/gridStore.js index 4df65d5258..d43799ff4a 100644 --- a/frontend/src/_stores/gridStore.js +++ b/frontend/src/_stores/gridStore.js @@ -12,9 +12,9 @@ const initialState = { idGroupDragged: false, openModalWidgetId: null, subContainerWidths: {}, - dynamicElementGuidelines: [], moveableRef: null, virtualTarget: null, + currentDragCanvasId: null, }; export const useGridStore = create( @@ -30,6 +30,7 @@ export const useGridStore = create( setSubContainerWidths: (id, width) => set((state) => ({ subContainerWidths: { ...state.subContainerWidths, [id]: width } })), setVirtualTarget: (target) => set({ virtualTarget: target }), + setCurrentDragCanvasId: (canvasId) => set({ currentDragCanvasId: canvasId }), }, addToElementGuidelines: (selector) => set((state) => ({ From 9a0430a94a301701aa38ccd2182d819adc2931b6 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Wed, 25 Jun 2025 22:44:04 +0530 Subject: [PATCH 08/33] fix --- .vscode/settings.json | 7 +- .../src/AppBuilder/AppCanvas/Container.jsx | 118 +++++------------- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 85 +++---------- .../AppBuilder/AppCanvas/appCanvasUtils.js | 2 +- .../AppCanvas/useCanvasDropHandler.js | 94 ++++++++++++++ .../ComponentManagerTab/DragLayer.jsx | 83 ++++-------- .../src/AppBuilder/_hooks/useGhostMoveable.js | 7 +- .../AppBuilder/_stores/slices/gridSlice.js | 4 + server/package-lock.json | 2 +- 9 files changed, 176 insertions(+), 226 deletions(-) create mode 100644 frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js diff --git a/.vscode/settings.json b/.vscode/settings.json index e13d54d778..50d6d9e897 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,8 +8,11 @@ "typescript", "typescriptreact" ], - "eslint.format.enable": false, - "editor.formatOnSave": false, + "eslint.format.enable": true, + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, "json.schemas": [ { "fileMatch": [ diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index be6aab2803..4bdafa8a15 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -4,19 +4,14 @@ import cx from 'classnames'; import WidgetWrapper from './WidgetWrapper'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useDrop, useDragLayer } from 'react-dnd'; +import { useDrop, useDragLayer, useDragDropManager } from 'react-dnd'; import { addChildrenWidgetsToParent, addNewWidgetToTheEditor, computeViewerBackgroundColor, getSubContainerWidthAfterPadding, } from './appCanvasUtils'; -import { - CANVAS_WIDTHS, - NO_OF_GRIDS, - WIDGETS_WITH_DEFAULT_CHILDREN, - GRID_HEIGHT, -} from './appCanvasConstants'; +import { CANVAS_WIDTHS, NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN, GRID_HEIGHT } from './appCanvasConstants'; import { useGridStore } from '@/_stores/gridStore'; import NoComponentCanvasContainer from './NoComponentCanvasContainer'; import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; @@ -27,6 +22,7 @@ import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import useSortedComponents from '../_hooks/useSortedComponents'; import { noop } from 'lodash'; import { useGhostMoveable } from '@/AppBuilder/_hooks/useGhostMoveable'; +import { useCanvasDropHandler } from './useCanvasDropHandler'; //TODO: Revisit the logic of height (dropRef) @@ -70,7 +66,7 @@ export const Container = React.memo( const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; - // Initialize ghost moveable hook (only for main canvas) + // Initialize ghost moveable hook const { activateGhost, deactivateGhost } = useGhostMoveable(id); // Monitor drag layer to update ghost position continuously @@ -78,14 +74,12 @@ export const Container = React.memo( isDragging: monitor.isDragging(), })); - // // Cleanup ghost when drag ends - // useEffect(() => { - // if (!isDragging) { - // setTimeout(() => { - // deactivateGhost(); - // }, 1000); - // } - // }, [id, isDragging, deactivateGhost]); + // // // Cleanup ghost when drag ends + useEffect(() => { + if (!isDragging) { + deactivateGhost(); + } + }, [id, isDragging, deactivateGhost]); const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; @@ -93,29 +87,29 @@ export const Container = React.memo( const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); + // Get the drop handler from the new hook + const handleDrop = useCanvasDropHandler({ + appType, + }); + const [{ isOverCurrent }, drop] = useDrop({ accept: 'box', - hover: (item, monitor) => { - // Use mouse position to determine the most specific container + hover: (item, monitor) => { const clientOffset = monitor.getClientOffset(); - - // If no client offset, the drag might be ending - clean up ghost - // if (!clientOffset) { - // deactivateGhost(); - // return; - // } - + const appCanvasWidth = realCanvasRef?.current?.offsetWidth || 0; if (clientOffset) { const elementAtPoint = document.elementFromPoint(clientOffset.x, clientOffset.y); const closestCanvas = elementAtPoint?.closest('.real-canvas'); - const canvasId = closestCanvas?.getAttribute('data-parentId') || - closestCanvas?.id?.replace('canvas-', '') || - (closestCanvas?.id === 'real-canvas' ? 'canvas' : null); - + const canvasId = + closestCanvas?.getAttribute('data-parentId') || + closestCanvas?.id?.replace('canvas-', '') || + (closestCanvas?.id === 'real-canvas' ? 'canvas' : null); + // Only update if this container is the most specific one under the mouse if (canvasId === id) { + // console.log('Container hover', canvasId, id); setCurrentDragCanvasId(id); } } @@ -123,73 +117,17 @@ export const Container = React.memo( let width = (appCanvasWidth * item.component?.defaultSize?.width) / NO_OF_GRIDS; const componentSize = { width: width, - height: item.component?.defaultSize?.height + height: item.component?.defaultSize?.height, }; // const clientOffset = monitor.getClientOffset(); - if (clientOffset) { + if (clientOffset && id === 'canvas') { activateGhost(componentSize, clientOffset, realCanvasRef); } }, - drop: async ({ componentType, component }, monitor) => { - console.log('drop'); - // Reset canvas ID when dropping - setCurrentDragCanvasId(null); - - // Ensure ghost is deactivated before processing drop - deactivateGhost(); - - // Add a small delay to allow moveable to properly clean up - // await new Promise(resolve => setTimeout(resolve, 10)); - - // Deactivate ghost when dropping - setShowModuleBorder(false); // Hide the module border when dropping - if (currentMode === 'view' || (appType === 'module' && componentType !== 'ModuleContainer')) return; - const didDrop = monitor.didDrop(); - if (didDrop) return; - if (componentType === 'PDF' && !isPDFSupported()) { - toast.error( - 'PDF is not supported in this version of browser. We recommend upgrading to the latest version for full support.' - ); - return; - } - - // IMPORTANT: This logic needs to be changed when we implement the module versioning - const moduleInfo = component?.moduleId - ? { - moduleId: component.moduleId, - versionId: component.versionId, - environmentId: component.environmentId, - moduleName: component.displayName, - moduleContainer: component.moduleContainer, - } - : undefined; - - if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentType)) { - const parentComponent = addNewWidgetToTheEditor( - componentType, - monitor, - currentLayout, - realCanvasRef, - id, - moduleInfo - ); - const childComponents = addChildrenWidgetsToParent(componentType, parentComponent?.id, currentLayout); - const newComponents = [parentComponent, ...childComponents]; - await addComponentToCurrentPage(newComponents); - setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); - } else { - const newComponent = addNewWidgetToTheEditor( - componentType, - monitor, - currentLayout, - realCanvasRef, - id, - moduleInfo - ); - await addComponentToCurrentPage([newComponent]); - setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); - } + drop: (item, monitor) => { + console.log('Container drop', item, monitor.getClientOffset()); + handleDrop(item, monitor, id); }, collect: (monitor) => ({ isOverCurrent: monitor.isOver({ shallow: true }), diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 2e15c2926b..297be699a0 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -86,12 +86,17 @@ export default function Grid({ gridWidth, currentLayout }) { const getExposedValueOfComponent = useStore((state) => state.getExposedValueOfComponent, shallow); const setReorderContainerChildren = useStore((state) => state.setReorderContainerChildren, shallow); const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); + const snapContainer = useMemo(() => { if (currentDragCanvasId) { return `#canvas-${currentDragCanvasId}`; } + if (dragParentId) { + return `#canvas-${dragParentId}`; + } return '#real-canvas'; - }, [currentDragCanvasId]); + }, [currentDragCanvasId, dragParentId]); + const moveableTarget = useMemo(() => { if (virtualTarget) { return '#moveable-ghost-element'; @@ -109,57 +114,6 @@ export default function Grid({ gridWidth, currentLayout }) { }; }, []); - // useEffect(() => { - // const selectedSet = new Set(selectedComponents); - // const draggingOrResizingId = draggingComponentId || resizingComponentId; - // const isGrouped = findHighestLevelofSelection().length > 1; - // const firstSelectedParent = - // selectedComponents.length > 0 ? boxList.find((b) => b.id === selectedComponents[0])?.parent : null; - // const selectedParent = dragParentId || firstSelectedParent; - - // const guidelines = boxList - // .filter((box) => { - // const isVisible = - // getResolvedValue(box?.component?.definition?.properties?.visibility?.value) || - // getResolvedValue(box?.component?.definition?.styles?.visibility?.value); - - // // Early return for non-visible elements - // if (!isVisible) return false; - - // // if (virtualTarget) { - // // return true; - // // } - - // if (isGrouped) { - // // If component is selected, don't show its guidelines - // if (selectedSet.has(box.id)) return false; - // return selectedParent ? box.parent === selectedParent : !box.parent; - // } - - // if (draggingOrResizingId) { - // if (box.id === draggingOrResizingId) return false; - // return dragParentId ? box.parent === dragParentId : !box.parent; - // } - - // // if (virtualTarget) { - // // return true; - // // } - - // return true; - // }) - // .map((box) => `.ele-${box.id}`); - - // // Combine static guidelines with dynamic ones (for ghost elements) - // setElementGuidelines(guidelines); - // }, [ - // boxList, - // dragParentId, - // draggingComponentId, - // resizingComponentId, - // selectedComponents, - // getResolvedValue, - // ]); - useEffect(() => { setBoxList( Object.keys(currentPageComponents) @@ -626,12 +580,12 @@ export default function Grid({ gridWidth, currentLayout }) { (component) => !selectedComponents.includes(component.getAttribute('widgetid')) ); const draggingOrResizing = draggingComponentId || resizingComponentId; - if (!draggingOrResizing && components.length > 0) { + if (!draggingOrResizing && components.length > 0 && !virtualTarget) { for (const component of components) { component?.classList?.remove('active-target'); } } - }, [draggingComponentId, resizingComponentId, isGroupDragging, selectedComponents]); + }, [draggingComponentId, resizingComponentId, isGroupDragging, selectedComponents, virtualTarget]); useGroupedTargetsScrollHandler(groupedTargets, boxList, moveableRef); if (mode !== 'edit') return null; @@ -995,18 +949,17 @@ export default function Grid({ gridWidth, currentLayout }) { }} onDrag={(e) => { if (e.target.id === 'moveable-ghost-element') { - // showGridLines(); - // + showGridLines(); const _gridWidth = useGridStore.getState().subContainerWidths[currentDragCanvasId] || gridWidth; let left = e.translate[0]; let top = e.translate[1]; // console.log('e.translate', e.translate); - if (currentDragCanvasId === 'canvas') { - left = Math.round(e.translate[0] / _gridWidth) * _gridWidth; - top = Math.round(e.translate[1] / GRID_HEIGHT) * GRID_HEIGHT; - } + // if (currentDragCanvasId === 'canvas') { + // console.log('e.translate', e.translate, _gridWidth); + left = Math.round(e.translate[0] / _gridWidth) * _gridWidth; + top = Math.round(e.translate[1] / GRID_HEIGHT) * GRID_HEIGHT; + console.log('e.translate', e.translate, left, top); e.target.style.transform = `translate(${left}px, ${top}px)`; - console.log('e.target', false); return false; } // Since onDrag is called multiple times when dragging, hence we are using isDraggingRef to prevent setting state again and again @@ -1029,10 +982,10 @@ export default function Grid({ gridWidth, currentLayout }) { e.target.style.width = `${draggingWidgetWidth}px`; // This logic is to handle the case when the dragged element is over a new canvas - if (_dragParentId !== currentParentId) { - left = e.translate[0]; - top = e.translate[1]; - } + // if (_dragParentId !== currentParentId) { + // left = e.translate[0]; + // top = e.translate[1]; + // } // Special case for Modal const oldParentId = boxList.find((b) => b.id === e.target.id)?.parent; @@ -1200,7 +1153,7 @@ export default function Grid({ gridWidth, currentLayout }) { // snapGridAll={true} scrollable={true} snapContainer={snapContainer} - snapGridWidth={100} + // snapGridWidth={100} /> ); diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index 6222cc596d..fd58bf2959 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -34,7 +34,7 @@ export const addNewWidgetToTheEditor = ( parentId, moduleInfo = undefined ) => { - const canvasBoundingRect = realCanvasRef?.current?.getBoundingClientRect(); + const canvasBoundingRect = realCanvasRef?.current?.getBoundingClientRect() || realCanvasRef?.getBoundingClientRect(); const componentMeta = componentTypes.find((component) => component.component === componentType); const componentName = computeComponentName(componentType, useStore.getState().getCurrentPageComponents()); diff --git a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js new file mode 100644 index 0000000000..557ec8a697 --- /dev/null +++ b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js @@ -0,0 +1,94 @@ +import useStore from '@/AppBuilder/_stores/store'; +import { useGridStore } from '@/_stores/gridStore'; +import { shallow } from 'zustand/shallow'; +import { noop } from 'lodash'; +import { addChildrenWidgetsToParent, addNewWidgetToTheEditor } from '../AppCanvas/appCanvasUtils'; +import { WIDGETS_WITH_DEFAULT_CHILDREN } from '../AppCanvas/appCanvasConstants'; +import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; +import { isPDFSupported } from '@/_helpers/appUtils'; +import toast from 'react-hot-toast'; +import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; +import { useGhostMoveable } from '../_hooks/useGhostMoveable'; + +export const useCanvasDropHandler = ({ appType }) => { + const { moduleId } = useModuleContext(); + + const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow); + const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow); + const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; + const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); + const currentLayout = useStore((state) => state.currentLayout, shallow); + const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); + const { deactivateGhost } = useGhostMoveable(); + const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); + + // console.log('currentDragCanvasId', currentDragCanvasId); + + const handleDrop = ({ componentType: draggedComponentType, component }, monitor, canvasId) => { + const realCanvasRef = + document.getElementById(`canvas-${currentDragCanvasId}`) || document.getElementById(`real-canvas`); + // Reset canvas ID when dropping + setCurrentDragCanvasId(null); + + // Ensure ghost is deactivated before processing drop + deactivateGhost(); + + // Deactivate ghost when dropping + setShowModuleBorder(false); // Hide the module border when dropping + + if (currentMode === 'view' || (appType === 'module' && draggedComponentType !== 'ModuleContainer')) { + return; + } + + // const didDrop = monitor.didDrop(); + // if (didDrop) { + // return; + // } + + if (draggedComponentType === 'PDF' && !isPDFSupported()) { + toast.error( + 'PDF is not supported in this version of browser. We recommend upgrading to the latest version for full support.' + ); + return; + } + + // IMPORTANT: This logic needs to be changed when we implement the module versioning + const moduleInfo = component?.moduleId + ? { + moduleId: component.moduleId, + versionId: component.versionId, + environmentId: component.environmentId, + moduleName: component.displayName, + moduleContainer: component.moduleContainer, + } + : undefined; + + if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(draggedComponentType)) { + const parentComponent = addNewWidgetToTheEditor( + draggedComponentType, + monitor, + currentLayout, + realCanvasRef, + currentDragCanvasId, + moduleInfo + ); + const childComponents = addChildrenWidgetsToParent(draggedComponentType, parentComponent?.id, currentLayout); + const newComponents = [parentComponent, ...childComponents]; + addComponentToCurrentPage(newComponents); + setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); + } else { + const newComponent = addNewWidgetToTheEditor( + draggedComponentType, + monitor, + currentLayout, + realCanvasRef, + currentDragCanvasId, + moduleInfo + ); + addComponentToCurrentPage([newComponent]); + setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); + } + }; + + return handleDrop; +}; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index d0d6249c98..fb925d5cee 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -10,21 +10,36 @@ import { shallow } from 'zustand/shallow'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { noop } from 'lodash'; import { useGridStore } from '@/_stores/gridStore'; +import { useCanvasDropHandler } from '@/AppBuilder/AppCanvas/useCanvasDropHandler'; export const DragLayer = ({ index, component, isModuleTab = false }) => { const { isModuleEditor } = useModuleContext(); const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; + const handleDrop = useCanvasDropHandler({ appType: isModuleTab ? 'module' : 'app' }) || noop; + const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); + const [{ isDragging }, drag, preview] = useDrag( () => ({ type: 'box', item: { componentType: component.component, component }, collect: (monitor) => ({ isDragging: monitor.isDragging() }), + end: (item, monitor) => { + const clientOffset = monitor.getClientOffset(); + console.log('end', item, monitor.getDropResult(), monitor.getClientOffset()); + console.log('currentDragCanvasId', currentDragCanvasId); + if (clientOffset) { + // const canvas = document.getElementById(`canvas-${currentDragCanvasId}`); + const realCanvas = document.getElementById(`real-canvas`); + handleDrop(item, monitor, realCanvas, currentDragCanvasId); + } + // if (didDrop) { + // handleDrop(item, monitor); + // } + }, }), [component.component] ); - const getMoveableRef = useGridStore((state) => state.moveableRef); - const setVirtualTarget = useGridStore((state) => state.actions.setVirtualTarget); - const newDiv = useRef(null); + useEffect(() => { preview(getEmptyImage(), { captureDraggingState: true }); }, []); @@ -41,7 +56,6 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { // ? component.module_container.layouts[currentLayout] // : component.defaultSize || { width: 30, height: 40 }; - return ( <> {/* {isDragging && } */} @@ -49,66 +63,13 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { ref={drag} className="draggable-box" style={{ height: '100%', width: isModuleTab && '100%' }} + // onDragEnd={(e) => { + // const realCanvas = document.getElementById(`real-canvas`); + // handleDrop(e, realCanvas, currentDragCanvasId); + // }} > {isModuleTab ? : }
); }; - -const CustomDragLayer = ({ size }) => { - const { currentOffset, item } = useDragLayer((monitor) => ({ - currentOffset: monitor.getSourceClientOffset(), - item: monitor.getItem(), - })); - if (!currentOffset) return null; - - const canvasWidth = item?.canvasWidth; - const canvasBounds = item?.canvasRef?.getBoundingClientRect(); - const height = size.height; - - const appCanvasWidth = document.getElementById('real-canvas')?.offsetWidth || 0; - - // Calculate width based on the app canvas's grid - let width = (appCanvasWidth * size.width) / NO_OF_GRIDS; - - // Calculate position relative to the current canvas (parent or child) - const left = currentOffset.x - (canvasBounds?.left || 0); - const top = currentOffset.y - (canvasBounds?.top || 0); - - // Ensure width doesn't exceed the current container's width - if (width > canvasWidth) { - width = canvasWidth; - } - - // Snap width to grid (round to nearest grid unit) - const gridUnitWidth = canvasWidth / NO_OF_GRIDS; - const gridUnits = Math.round(width / gridUnitWidth); - width = gridUnits * gridUnitWidth; - - const [x, y] = snapToGrid(canvasWidth, left, top); - return ( -
-
-
- ); -}; diff --git a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js index eee05f6157..cea7a461cb 100644 --- a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js +++ b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js @@ -3,7 +3,7 @@ import { useGridStore } from '@/_stores/gridStore'; import { NO_OF_GRIDS, GRID_HEIGHT } from '@/AppBuilder/AppCanvas/appCanvasConstants'; import { snapToGrid } from '@/AppBuilder/AppCanvas/appCanvasUtils'; -export const useGhostMoveable = (canvasId) => { +export const useGhostMoveable = () => { const ghostElementRef = useRef(null); const isActiveRef = useRef(false); @@ -29,7 +29,6 @@ export const useGhostMoveable = (canvasId) => { left: 0; `; - // const container = document.querySelectorAll(`[component-id="${canvasId}"]`)[0]; const container = document.getElementById('real-canvas'); container.appendChild(ghost); ghostElementRef.current = ghost; @@ -49,7 +48,7 @@ export const useGhostMoveable = (canvasId) => { const snappedX = Math.round(relativeX / gridWidth) * gridWidth; const snappedY = Math.round(relativeY / GRID_HEIGHT) * GRID_HEIGHT; console.log(snappedX, snappedY); - ghostElementRef.current.style.transform = `translate(${snappedX}px, ${snappedY}px)`; + ghostElementRef.current.style.transform = `translate(${relativeX}px, ${relativeY}px)`; }; const activateGhost = (componentSize, mousePosition, canvasRef) => { @@ -98,8 +97,6 @@ export const useGhostMoveable = (canvasId) => { ghostElementRef.current = null; // End any active drag operation first // moveableInstance.dragEnd(); - - } catch (error) { console.warn('Failed to trigger moveable dragEnd:', error); } diff --git a/frontend/src/AppBuilder/_stores/slices/gridSlice.js b/frontend/src/AppBuilder/_stores/slices/gridSlice.js index 8b2f61bd9a..d168bd57da 100644 --- a/frontend/src/AppBuilder/_stores/slices/gridSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/gridSlice.js @@ -12,6 +12,7 @@ const initialState = { containerId: null, triggerUpdate: 0, }, + shouldPreventDrop: false, }; export const createGridSlice = (set, get) => ({ @@ -99,4 +100,7 @@ export const createGridSlice = (set, get) => ({ reorderContainerChildren: { containerId, triggerUpdate: state.reorderContainerChildren.triggerUpdate + 1 }, })); }, + setShouldPreventDrop: (shouldPreventDrop) => { + set(() => ({ shouldPreventDrop })); + }, }); diff --git a/server/package-lock.json b/server/package-lock.json index fa1e6d68ba..3cef36d54f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -20645,4 +20645,4 @@ } } } -} \ No newline at end of file +} From 268d4a1aef1cf6bb2711b5eaf5ad0beb80b39d31 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 26 Jun 2025 15:34:45 +0530 Subject: [PATCH 09/33] Fixes --- .../src/AppBuilder/AppCanvas/Container.jsx | 3 +-- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 27 +++++++++---------- .../AppBuilder/AppCanvas/appCanvasUtils.js | 22 ++++++++------- .../AppCanvas/useCanvasDropHandler.js | 20 +++++++------- .../ComponentManagerTab/DragLayer.jsx | 21 +++------------ .../src/AppBuilder/_hooks/useGhostMoveable.js | 18 +++---------- frontend/src/_stores/gridStore.js | 6 ++++- 7 files changed, 46 insertions(+), 71 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 4bdafa8a15..c9fe887257 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -105,7 +105,7 @@ export const Container = React.memo( const canvasId = closestCanvas?.getAttribute('data-parentId') || closestCanvas?.id?.replace('canvas-', '') || - (closestCanvas?.id === 'real-canvas' ? 'canvas' : null); + (closestCanvas?.id === 'real-canvas' ? 'real-canvas' : null); // Only update if this container is the most specific one under the mouse if (canvasId === id) { @@ -126,7 +126,6 @@ export const Container = React.memo( } }, drop: (item, monitor) => { - console.log('Container drop', item, monitor.getClientOffset()); handleDrop(item, monitor, id); }, collect: (monitor) => ({ diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 297be699a0..b1e297c40e 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -86,6 +86,7 @@ export default function Grid({ gridWidth, currentLayout }) { const getExposedValueOfComponent = useStore((state) => state.getExposedValueOfComponent, shallow); const setReorderContainerChildren = useStore((state) => state.setReorderContainerChildren, shallow); const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); + const groupedTargets = [...findHighestLevelofSelection().map((component) => '.ele-' + component.id)]; const snapContainer = useMemo(() => { if (currentDragCanvasId) { @@ -97,12 +98,12 @@ export default function Grid({ gridWidth, currentLayout }) { return '#real-canvas'; }, [currentDragCanvasId, dragParentId]); - const moveableTarget = useMemo(() => { + const getMoveableTarget = () => { if (virtualTarget) { return '#moveable-ghost-element'; } return groupedTargets?.length > 1 ? groupedTargets : '.target'; - }, [virtualTarget, groupedTargets]); + }; // Set moveable reference in grid store for access by other components useEffect(() => { @@ -326,8 +327,6 @@ export default function Grid({ gridWidth, currentLayout }) { }); }, [selectedComponents]); - const groupedTargets = [...findHighestLevelofSelection().map((component) => '.ele-' + component.id)]; - useEffect(() => { reloadGrid(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -601,7 +600,7 @@ export default function Grid({ gridWidth, currentLayout }) { multiComponentHandle: groupedTargets.length > 1, }} flushSync={flushSync} - target={moveableTarget} + target={getMoveableTarget()} origin={false} individualGroupable={virtualTarget ? false : groupedTargets.length <= 1} draggable={!shouldFreeze && mode !== 'view'} @@ -883,11 +882,8 @@ export default function Grid({ gridWidth, currentLayout }) { handleActivateNonDraggingComponents(); }} onDragEnd={(e) => { - console.log('e.lastEvent', moveableRef); handleDeactivateTargets(); if (e.target.id === 'moveable-ghost-element') { - console.log('e.target', false); - // e.target.remove(); return; } try { @@ -953,12 +949,13 @@ export default function Grid({ gridWidth, currentLayout }) { const _gridWidth = useGridStore.getState().subContainerWidths[currentDragCanvasId] || gridWidth; let left = e.translate[0]; let top = e.translate[1]; - // console.log('e.translate', e.translate); - // if (currentDragCanvasId === 'canvas') { - // console.log('e.translate', e.translate, _gridWidth); - left = Math.round(e.translate[0] / _gridWidth) * _gridWidth; - top = Math.round(e.translate[1] / GRID_HEIGHT) * GRID_HEIGHT; - console.log('e.translate', e.translate, left, top); + + if (currentDragCanvasId === 'canvas') { + left = Math.round(e.translate[0] / _gridWidth) * _gridWidth; + top = Math.round(e.translate[1] / GRID_HEIGHT) * GRID_HEIGHT; + } + + useGridStore.getState().actions.setGhostDragPosition({ left, top, e }); e.target.style.transform = `translate(${left}px, ${top}px)`; return false; } @@ -1152,7 +1149,7 @@ export default function Grid({ gridWidth, currentLayout }) { }} // snapGridAll={true} scrollable={true} - snapContainer={snapContainer} + // snapContainer={snapContainer} // snapGridWidth={100} /> diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index fd58bf2959..228af4225e 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -5,7 +5,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { toast } from 'react-hot-toast'; import _, { debounce } from 'lodash'; import { useGridStore } from '@/_stores/gridStore'; -import { findHighestLevelofSelection } from './Grid/gridUtils'; +import { findHighestLevelofSelection, getMouseDistanceFromParentDiv } from './Grid/gridUtils'; import { CANVAS_WIDTHS, NO_OF_GRIDS, @@ -34,26 +34,28 @@ export const addNewWidgetToTheEditor = ( parentId, moduleInfo = undefined ) => { - const canvasBoundingRect = realCanvasRef?.current?.getBoundingClientRect() || realCanvasRef?.getBoundingClientRect(); + const canvasBoundingRect = realCanvasRef?.getBoundingClientRect(); const componentMeta = componentTypes.find((component) => component.component === componentType); const componentName = computeComponentName(componentType, useStore.getState().getCurrentPageComponents()); - + const parentCanvasType = realCanvasRef?.getAttribute('component-type'); const componentData = deepClone(componentMeta); const defaultWidth = componentData.defaultSize.width; const defaultHeight = componentData.defaultSize.height; - const offsetFromTopOfWindow = canvasBoundingRect?.top; - const offsetFromLeftOfWindow = canvasBoundingRect?.left; - const currentOffset = eventMonitorObject?.getSourceClientOffset(); + const { e } = useGridStore.getState().getGhostDragPosition(); const subContainerWidth = canvasBoundingRect?.width; - let left = Math.round(currentOffset?.x - offsetFromLeftOfWindow); - let top = Math.round(currentOffset?.y - offsetFromTopOfWindow); - - [left, top] = snapToGrid(subContainerWidth, left, top); + const { left: left3, top: top3 } = getMouseDistanceFromParentDiv( + e, + parentId === 'canvas' ? 'real-canvas' : parentId, + parentCanvasType + ); + // [left, top] = snapToGrid(subContainerWidth, left, top); + let [left, top] = snapToGrid(subContainerWidth, left3, top3); const gridWidth = subContainerWidth / NO_OF_GRIDS; left = Math.round(left / gridWidth); + // Adjust widget width based on the dropping canvas width const mainCanvasWidth = useGridStore.getState().subContainerWidths['canvas']; let width = Math.round((defaultWidth * mainCanvasWidth) / gridWidth); diff --git a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js index 557ec8a697..b17bfc3880 100644 --- a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js +++ b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js @@ -9,6 +9,7 @@ import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { useGhostMoveable } from '../_hooks/useGhostMoveable'; +import { handleDeactivateTargets, hideGridLines } from '../AppCanvas/Grid/gridUtils'; export const useCanvasDropHandler = ({ appType }) => { const { moduleId } = useModuleContext(); @@ -20,20 +21,17 @@ export const useCanvasDropHandler = ({ appType }) => { const currentLayout = useStore((state) => state.currentLayout, shallow); const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); const { deactivateGhost } = useGhostMoveable(); - const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); - - // console.log('currentDragCanvasId', currentDragCanvasId); - const handleDrop = ({ componentType: draggedComponentType, component }, monitor, canvasId) => { const realCanvasRef = - document.getElementById(`canvas-${currentDragCanvasId}`) || document.getElementById(`real-canvas`); - // Reset canvas ID when dropping - setCurrentDragCanvasId(null); + !canvasId || canvasId === 'canvas' + ? document.getElementById(`real-canvas`) + : document.getElementById(`canvas-${canvasId}`); // Ensure ghost is deactivated before processing drop deactivateGhost(); + handleDeactivateTargets(); + hideGridLines(); - // Deactivate ghost when dropping setShowModuleBorder(false); // Hide the module border when dropping if (currentMode === 'view' || (appType === 'module' && draggedComponentType !== 'ModuleContainer')) { @@ -69,7 +67,7 @@ export const useCanvasDropHandler = ({ appType }) => { monitor, currentLayout, realCanvasRef, - currentDragCanvasId, + canvasId, moduleInfo ); const childComponents = addChildrenWidgetsToParent(draggedComponentType, parentComponent?.id, currentLayout); @@ -82,12 +80,14 @@ export const useCanvasDropHandler = ({ appType }) => { monitor, currentLayout, realCanvasRef, - currentDragCanvasId, + canvasId, moduleInfo ); addComponentToCurrentPage([newComponent]); setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); } + // Reset canvas ID when dropping + setCurrentDragCanvasId(null); }; return handleDrop; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index fb925d5cee..3f554a413f 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -16,7 +16,6 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { const { isModuleEditor } = useModuleContext(); const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; const handleDrop = useCanvasDropHandler({ appType: isModuleTab ? 'module' : 'app' }) || noop; - const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); const [{ isDragging }, drag, preview] = useDrag( () => ({ @@ -25,16 +24,10 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { collect: (monitor) => ({ isDragging: monitor.isDragging() }), end: (item, monitor) => { const clientOffset = monitor.getClientOffset(); - console.log('end', item, monitor.getDropResult(), monitor.getClientOffset()); - console.log('currentDragCanvasId', currentDragCanvasId); + const currentDragCanvasId = useGridStore.getState().currentDragCanvasId; if (clientOffset) { - // const canvas = document.getElementById(`canvas-${currentDragCanvasId}`); - const realCanvas = document.getElementById(`real-canvas`); - handleDrop(item, monitor, realCanvas, currentDragCanvasId); + handleDrop(item, monitor, currentDragCanvasId); } - // if (didDrop) { - // handleDrop(item, monitor); - // } }, }), [component.component] @@ -59,15 +52,7 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { return ( <> {/* {isDragging && } */} -
{ - // const realCanvas = document.getElementById(`real-canvas`); - // handleDrop(e, realCanvas, currentDragCanvasId); - // }} - > +
{isModuleTab ? : }
diff --git a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js index cea7a461cb..0e5adaa9c1 100644 --- a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js +++ b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js @@ -44,10 +44,9 @@ export const useGhostMoveable = () => { const relativeY = mousePosition.y - canvasRect.top; // Apply grid snapping - const gridWidth = canvasRef.current.offsetWidth / NO_OF_GRIDS; - const snappedX = Math.round(relativeX / gridWidth) * gridWidth; - const snappedY = Math.round(relativeY / GRID_HEIGHT) * GRID_HEIGHT; - console.log(snappedX, snappedY); + // const gridWidth = canvasRef.current.offsetWidth / NO_OF_GRIDS; + // const snappedX = Math.round(relativeX / gridWidth) * gridWidth; + // const snappedY = Math.round(relativeY / GRID_HEIGHT) * GRID_HEIGHT; ghostElementRef.current.style.transform = `translate(${relativeX}px, ${relativeY}px)`; }; @@ -95,25 +94,14 @@ export const useGhostMoveable = () => { setVirtualTarget(null); ghostElementRef.current.remove(); ghostElementRef.current = null; - // End any active drag operation first - // moveableInstance.dragEnd(); } catch (error) { console.warn('Failed to trigger moveable dragEnd:', error); } } }; - // New function to update ghost position during continuous hover - const updateGhostOnHover = (mousePosition, canvasRef) => { - if (isActiveRef.current) { - updateGhostPosition(mousePosition, canvasRef); - } - }; - return { activateGhost, deactivateGhost, - updateGhostOnHover, // New function for continuous updates - isActive: isActiveRef.current, }; }; diff --git a/frontend/src/_stores/gridStore.js b/frontend/src/_stores/gridStore.js index d43799ff4a..6b9822f6dd 100644 --- a/frontend/src/_stores/gridStore.js +++ b/frontend/src/_stores/gridStore.js @@ -15,11 +15,12 @@ const initialState = { moveableRef: null, virtualTarget: null, currentDragCanvasId: null, + ghostDragPosition: null, }; export const useGridStore = create( zustandDevTools( - (set) => ({ + (set, get) => ({ ...initialState, actions: { setResizingComponentId: (id) => set({ resizingComponentId: id }), @@ -31,6 +32,7 @@ export const useGridStore = create( set((state) => ({ subContainerWidths: { ...state.subContainerWidths, [id]: width } })), setVirtualTarget: (target) => set({ virtualTarget: target }), setCurrentDragCanvasId: (canvasId) => set({ currentDragCanvasId: canvasId }), + setGhostDragPosition: (position) => set({ ghostDragPosition: position }), }, addToElementGuidelines: (selector) => set((state) => ({ @@ -42,6 +44,8 @@ export const useGridStore = create( })), clearDynamicElementGuidelines: () => set({ dynamicElementGuidelines: [] }), setMoveableRef: (ref) => set({ moveableRef: ref }), + + getGhostDragPosition: () => get().ghostDragPosition, }), { name: 'Grid Store' } ) From 5fcfb40dca44e19cfe298c60622b2b42d4ed6b55 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 1 Jul 2025 19:02:09 +0530 Subject: [PATCH 10/33] remove console --- frontend/src/AppBuilder/AppCanvas/Container.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index ac22aff055..49462b382e 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -109,7 +109,6 @@ export const Container = React.memo( // Only update if this container is the most specific one under the mouse if (canvasId === id) { - // console.log('Container hover', canvasId, id); setCurrentDragCanvasId(id); } } From b88d76a4bb6bf2736f1752f446bde10f15d01387 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 1 Jul 2025 19:13:26 +0530 Subject: [PATCH 11/33] merge conflic fix --- .../AppCanvas/useCanvasDropHandler.js | 45 +++++++++++++++---- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js index b17bfc3880..8dcb595f86 100644 --- a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js +++ b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js @@ -2,7 +2,11 @@ import useStore from '@/AppBuilder/_stores/store'; import { useGridStore } from '@/_stores/gridStore'; import { shallow } from 'zustand/shallow'; import { noop } from 'lodash'; -import { addChildrenWidgetsToParent, addNewWidgetToTheEditor } from '../AppCanvas/appCanvasUtils'; +import { + addChildrenWidgetsToParent, + addNewWidgetToTheEditor, + addDefaultButtonIdToForm, +} from '../AppCanvas/appCanvasUtils'; import { WIDGETS_WITH_DEFAULT_CHILDREN } from '../AppCanvas/appCanvasConstants'; import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; @@ -21,7 +25,7 @@ export const useCanvasDropHandler = ({ appType }) => { const currentLayout = useStore((state) => state.currentLayout, shallow); const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); const { deactivateGhost } = useGhostMoveable(); - const handleDrop = ({ componentType: draggedComponentType, component }, monitor, canvasId) => { + const handleDrop = async ({ componentType: draggedComponentType, component }, monitor, canvasId) => { const realCanvasRef = !canvasId || canvasId === 'canvas' ? document.getElementById(`real-canvas`) @@ -61,8 +65,10 @@ export const useCanvasDropHandler = ({ appType }) => { } : undefined; + let addedComponent; + if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(draggedComponentType)) { - const parentComponent = addNewWidgetToTheEditor( + let parentComponent = addNewWidgetToTheEditor( draggedComponentType, monitor, currentLayout, @@ -71,9 +77,11 @@ export const useCanvasDropHandler = ({ appType }) => { moduleInfo ); const childComponents = addChildrenWidgetsToParent(draggedComponentType, parentComponent?.id, currentLayout); - const newComponents = [parentComponent, ...childComponents]; - addComponentToCurrentPage(newComponents); - setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); + if (draggedComponentType === 'Form') { + parentComponent = addDefaultButtonIdToForm(parentComponent, childComponents); + } + addedComponent = [parentComponent, ...childComponents]; + await addComponentToCurrentPage(addedComponent); } else { const newComponent = addNewWidgetToTheEditor( draggedComponentType, @@ -83,8 +91,29 @@ export const useCanvasDropHandler = ({ appType }) => { canvasId, moduleInfo ); - addComponentToCurrentPage([newComponent]); - setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); + addedComponent = [newComponent]; + await addComponentToCurrentPage(addedComponent); + } + + setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); + + const canvas = document.querySelector('.canvas-container'); + const sidebar = document.querySelector('.editor-sidebar'); + const droppedElem = document.getElementById(addedComponent?.[0]?.id); + + if (!canvas || !sidebar || !droppedElem) return; + + const droppedRect = droppedElem.getBoundingClientRect(); + const sidebarRect = sidebar.getBoundingClientRect(); + + const isOverlapping = droppedRect.right > sidebarRect.left && droppedRect.left < sidebarRect.right; + + if (isOverlapping) { + const overlap = droppedRect.right - sidebarRect.left; + canvas.scrollTo({ + left: canvas.scrollLeft + overlap, + behavior: 'smooth', + }); } // Reset canvas ID when dropping setCurrentDragCanvasId(null); From d315ce48e8b24d95386c2c595d8f83222e1cd5ed Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 1 Jul 2025 19:14:17 +0530 Subject: [PATCH 12/33] fix --- .../common/components/BaseColorSwatches/BaseColorSwatches.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx b/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx index 160799dc9d..0da388a0eb 100644 --- a/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx +++ b/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx @@ -116,7 +116,6 @@ const BaseColorSwatches = ({ ); }; const ColorPickerInputBox = () => { - console.log('onReset', onReset); return (
Date: Wed, 2 Jul 2025 10:48:16 +0530 Subject: [PATCH 13/33] fix for modal and resolve merge conflicts --- .../src/AppBuilder/AppCanvas/Container.jsx | 101 ++---------------- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 35 +++--- .../AppBuilder/AppCanvas/Grid/gridUtils.js | 26 +++++ .../Grid/hooks/useElementGudelines.js | 23 ++-- .../AppBuilder/_stores/slices/gridSlice.js | 2 + 5 files changed, 57 insertions(+), 130 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index b1c2893ca5..65fec613d1 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -4,26 +4,18 @@ import cx from 'classnames'; import WidgetWrapper from './WidgetWrapper'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useDrop, useDragLayer, useDragDropManager } from 'react-dnd'; -import { - addChildrenWidgetsToParent, - addNewWidgetToTheEditor, - computeViewerBackgroundColor, - getSubContainerWidthAfterPadding, - addDefaultButtonIdToForm, -} from './appCanvasUtils'; -import { CANVAS_WIDTHS, NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN, GRID_HEIGHT } from './appCanvasConstants'; +import { useDrop, useDragLayer } from 'react-dnd'; +import { computeViewerBackgroundColor, getSubContainerWidthAfterPadding } from './appCanvasUtils'; +import { CANVAS_WIDTHS, NO_OF_GRIDS, GRID_HEIGHT } from './appCanvasConstants'; import { useGridStore } from '@/_stores/gridStore'; import NoComponentCanvasContainer from './NoComponentCanvasContainer'; -import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; -import { isPDFSupported } from '@/_helpers/appUtils'; -import toast from 'react-hot-toast'; import { ModuleContainerBlank } from '@/modules/Modules/components'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import useSortedComponents from '../_hooks/useSortedComponents'; import { noop } from 'lodash'; import { useGhostMoveable } from '@/AppBuilder/_hooks/useGhostMoveable'; import { useCanvasDropHandler } from './useCanvasDropHandler'; +import { findNewParentIdFromMousePosition } from './Grid/gridUtils'; //TODO: Revisit the logic of height (dropRef) @@ -102,14 +94,7 @@ export const Container = React.memo( const appCanvasWidth = realCanvasRef?.current?.offsetWidth || 0; if (clientOffset) { - const elementAtPoint = document.elementFromPoint(clientOffset.x, clientOffset.y); - const closestCanvas = elementAtPoint?.closest('.real-canvas'); - const canvasId = - closestCanvas?.getAttribute('data-parentId') || - closestCanvas?.id?.replace('canvas-', '') || - (closestCanvas?.id === 'real-canvas' ? 'real-canvas' : null); - - // Only update if this container is the most specific one under the mouse + const canvasId = findNewParentIdFromMousePosition(clientOffset.x, clientOffset.y, id); if (canvasId === id) { setCurrentDragCanvasId(id); } @@ -117,11 +102,9 @@ export const Container = React.memo( // Calculate width based on the app canvas's grid let width = (appCanvasWidth * item.component?.defaultSize?.width) / NO_OF_GRIDS; const componentSize = { - width: width, + width, height: item.component?.defaultSize?.height, }; - - // const clientOffset = monitor.getClientOffset(); if (clientOffset && id === 'canvas') { activateGhost(componentSize, clientOffset, realCanvasRef); } @@ -129,78 +112,6 @@ export const Container = React.memo( drop: (item, monitor) => { handleDrop(item, monitor, id); }, - // drop: async ({ componentType, component }, monitor) => { - // setShowModuleBorder(false); - // if (currentMode === 'view' || (appType === 'module' && componentType !== 'ModuleContainer')) return; - - // const didDrop = monitor.didDrop(); - // if (didDrop) return; - - // const moduleInfo = component?.moduleId - // ? { - // moduleId: component.moduleId, - // versionId: component.versionId, - // environmentId: component.environmentId, - // moduleName: component.displayName, - // moduleContainer: component.moduleContainer, - // } - // : undefined; - - // let addedComponent; - - // if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentType)) { - // let parentComponent = addNewWidgetToTheEditor( - // componentType, - // monitor, - // currentLayout, - // realCanvasRef, - // id, - // moduleInfo - // ); - // const childComponents = addChildrenWidgetsToParent(componentType, parentComponent?.id, currentLayout); - // if (componentType === 'Form') { - // parentComponent = addDefaultButtonIdToForm(parentComponent, childComponents); - // } - // addedComponent = [parentComponent, ...childComponents]; - // await addComponentToCurrentPage(addedComponent); - // } else { - // const newComponent = addNewWidgetToTheEditor( - // componentType, - // monitor, - // currentLayout, - // realCanvasRef, - // id, - // moduleInfo - // ); - // addedComponent = [newComponent]; - // await addComponentToCurrentPage(addedComponent); - // } - - // setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); - - // const canvas = document.querySelector('.canvas-container'); - // const sidebar = document.querySelector('.editor-sidebar'); - // const droppedElem = document.getElementById(addedComponent?.[0]?.id); - - // if (!canvas || !sidebar || !droppedElem) return; - - // const droppedRect = droppedElem.getBoundingClientRect(); - // const sidebarRect = sidebar.getBoundingClientRect(); - - // const isOverlapping = droppedRect.right > sidebarRect.left && droppedRect.left < sidebarRect.right; - - // if (isOverlapping) { - // const overlap = droppedRect.right - sidebarRect.left; - // canvas.scrollTo({ - // left: canvas.scrollLeft + overlap, - // behavior: 'smooth', - // }); - // } - // }, - - // collect: (monitor) => ({ - // isOverCurrent: monitor.isOver({ shallow: true }), - // }), }); const showEmptyContainer = diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index e94bb0419f..d58cd1ed39 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -24,6 +24,7 @@ import { computeScrollDeltaOnDrag, getDraggingWidgetWidth, positionDragGhostWidget, + findNewParentIdFromMousePosition, } from './gridUtils'; import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; @@ -73,15 +74,11 @@ export default function Grid({ gridWidth, currentLayout }) { const getTemporaryLayouts = useStore((state) => state.getTemporaryLayouts, shallow); const updateContainerAutoHeight = useStore((state) => state.updateContainerAutoHeight, shallow); const [canvasBounds, setCanvasBounds] = useState(CANVAS_BOUNDS); - const draggingComponentId = useGridStore((state) => state.draggingComponentId, shallow); - const resizingComponentId = useGridStore((state) => state.resizingComponentId, shallow); const [dragParentId, setDragParentId] = useState(null); const virtualTarget = useGridStore((state) => state.virtualTarget, shallow); const { elementGuidelines } = useElementGudelines( boxList, selectedComponents, - draggingComponentId, - resizingComponentId, dragParentId, getResolvedValue, virtualTarget @@ -97,6 +94,8 @@ export default function Grid({ gridWidth, currentLayout }) { const groupedTargets = [...findHighestLevelofSelection().map((component) => '.ele-' + component.id)]; const [isVerticalExpansionRestricted, setIsVerticalExpansionRestricted] = useState(false); const toggleRightSidebar = useStore((state) => state.toggleRightSidebar, shallow); + const draggingComponentId = useStore((state) => state.draggingComponentId, shallow); + const resizingComponentId = useStore((state) => state.resizingComponentId, shallow); const snapContainer = useMemo(() => { if (currentDragCanvasId) { @@ -350,6 +349,7 @@ export default function Grid({ gridWidth, currentLayout }) { moveableRef.current.updateTarget(); } }, [temporaryHeight]); + useEffect(() => { reloadGrid(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -601,8 +601,8 @@ export default function Grid({ gridWidth, currentLayout }) { const components = Array.from(document.querySelectorAll('.active-target')).filter( (component) => !selectedComponents.includes(component.getAttribute('widgetid')) ); - const draggingOrResizing = draggingComponentId || resizingComponentId; - if (!draggingOrResizing && components.length > 0 && !virtualTarget) { + const draggingOrResizingComponentId = draggingComponentId || resizingComponentId; + if (!draggingOrResizingComponentId && components.length > 0 && !virtualTarget) { for (const component of components) { component?.classList?.remove('active-target'); } @@ -639,7 +639,7 @@ export default function Grid({ gridWidth, currentLayout }) { onResize={(e) => { const temporaryLayouts = getTemporaryLayouts(); if (resizingComponentId !== e.target.id) { - useGridStore.getState().actions.setResizingComponentId(e.target.id); + useStore.getState().setResizingComponentId(e.target.id); showGridLines(); } @@ -715,7 +715,7 @@ export default function Grid({ gridWidth, currentLayout }) { }} onResizeEnd={(e) => { try { - useGridStore.getState().actions.setResizingComponentId(null); + useStore.getState().setResizingComponentId(null); const currentWidget = boxList.find(({ id }) => { return id === e.target.id; }); @@ -1018,10 +1018,10 @@ export default function Grid({ gridWidth, currentLayout }) { e.target.style.width = `${draggingWidgetWidth}px`; // This logic is to handle the case when the dragged element is over a new canvas - // if (_dragParentId !== currentParentId) { - // left = e.translate[0]; - // top = e.translate[1]; - // } + if (_dragParentId !== currentParentId) { + left = e.translate[0]; + top = e.translate[1]; + } // Special case for Modal const oldParentId = boxList.find((b) => b.id === e.target.id)?.parent; @@ -1066,16 +1066,7 @@ export default function Grid({ gridWidth, currentLayout }) { // This block is to show grid lines on the canvas when the dragged element is over a new canvas if (document.elementFromPoint(e.clientX, e.clientY)) { - const targetElems = document.elementsFromPoint(e.clientX, e.clientY); - const draggedOverElements = targetElems.filter( - (ele) => - (ele.id !== e.target.id && ele.classList.contains('target')) || ele.classList.contains('real-canvas') - ); - const draggedOverElem = draggedOverElements.find((ele) => ele.classList.contains('target')); - const draggedOverContainer = draggedOverElements.find((ele) => ele.classList.contains('real-canvas')); - - // Determine potential new parent - let newParentId = draggedOverContainer?.getAttribute('data-parentId') || draggedOverElem?.id; + let newParentId = findNewParentIdFromMousePosition(e.clientX, e.clientY, e.target.id); if (newParentId === e.target.id) { newParentId = boxList.find((box) => box.id === e.target.id)?.component?.parent; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js index 07a56a3c4c..ba42d4152f 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js @@ -551,3 +551,29 @@ export const positionDragGhostWidget = (draggedElement) => { ghostElement.style.width = `${draggedRect.width}px`; ghostElement.style.height = `${draggedRect.height}px`; }; + +/** + * Finds the new parent ID based on the current mouse position during drag operations + * @param {number} clientX - The X coordinate of the mouse position + * @param {number} clientY - The Y coordinate of the mouse position + * @param {string} currentTargetId - The ID of the currently dragged element to exclude from search + * @returns {string|null} - The new parent ID or null if no valid parent is found + */ +export const findNewParentIdFromMousePosition = (clientX, clientY, currentTargetId) => { + if (!document.elementFromPoint(clientX, clientY)) { + return null; + } + + const targetElems = document.elementsFromPoint(clientX, clientY); + const draggedOverElements = targetElems.filter( + (ele) => (ele.id !== currentTargetId && ele.classList.contains('target')) || ele.classList.contains('real-canvas') + ); + + const draggedOverElem = draggedOverElements.find((ele) => ele.classList.contains('target')); + const draggedOverContainer = draggedOverElements.find((ele) => ele.classList.contains('real-canvas')); + + // Determine potential new parent + const newParentId = draggedOverContainer?.getAttribute('data-parentId') || draggedOverElem?.id; + + return newParentId || null; +}; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js index 6b6ea394da..55587b33fd 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js @@ -1,19 +1,12 @@ import { useEffect, useState } from 'react'; import { findHighestLevelofSelection } from '../gridUtils'; import { useGridStore } from '@/_stores/gridStore'; +import useStore from '@/AppBuilder/_stores/store'; -export const useElementGudelines = ( - boxList, - selectedComponents, - draggingComponentId, - resizingComponentId, - dragParentId, - getResolvedValue, - virtualTarget -) => { +export const useElementGudelines = (boxList, selectedComponents, dragParentId, getResolvedValue, virtualTarget) => { const [elementGuidelines, setElementGuidelines] = useState([]); - - // Get current drag canvas ID from store instead of drag layer + const draggingComponentId = useStore((state) => state.draggingComponentId); + const resizingComponentId = useStore((state) => state.resizingComponentId); const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId); useEffect(() => { @@ -23,6 +16,7 @@ export const useElementGudelines = ( const firstSelectedParent = selectedComponents.length > 0 ? boxList.find((b) => b.id === selectedComponents[0])?.parent : null; const selectedParent = dragParentId || firstSelectedParent; + const isAnyModalOpen = document.querySelector('#modal-container') ? true : false; const guidelines = boxList .filter((box) => { @@ -33,9 +27,13 @@ export const useElementGudelines = ( // Early return for non-visible elements if (!isVisible) return false; + // Don't show guidelines for components which are outside the modal specially on main canvas + if (virtualTarget && isAnyModalOpen) { + if (box.parent === 'canvas' || !box.parent) return false; + } + // This block is for first time drop using react-dnd if (virtualTarget && currentDragCanvasId !== null) { - // For main canvas (id = 'canvas'), show components with no parent or parent = 'canvas' if (currentDragCanvasId === 'canvas') { if (box.parent && box.parent !== 'canvas') return false; } else { @@ -58,7 +56,6 @@ export const useElementGudelines = ( return true; }) .map((box) => `.ele-${box.id}`); - setElementGuidelines(guidelines); }, [ boxList, diff --git a/frontend/src/AppBuilder/_stores/slices/gridSlice.js b/frontend/src/AppBuilder/_stores/slices/gridSlice.js index 97e04ae4bf..6a74c12049 100644 --- a/frontend/src/AppBuilder/_stores/slices/gridSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/gridSlice.js @@ -10,6 +10,7 @@ const initialState = { lastCanvasClickPosition: null, temporaryLayouts: {}, draggingComponentId: null, + resizingComponentId: null, reorderContainerChildren: { containerId: null, triggerUpdate: 0, @@ -36,6 +37,7 @@ export const createGridSlice = (set, get) => ({ get().toggleCanvasUpdater(); }, 200), setDraggingComponentId: (id) => set(() => ({ draggingComponentId: id })), + setResizingComponentId: (id) => set(() => ({ resizingComponentId: id })), moveComponentPosition: (direction) => { const { setComponentLayout, currentLayout, getSelectedComponentsDefinition, debouncedToggleCanvasUpdater } = get(); let layouts = {}; From 7198427ff958e230e7ba920086012a266a82d589 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Wed, 2 Jul 2025 14:44:21 +0530 Subject: [PATCH 14/33] Refactor --- .../src/AppBuilder/AppCanvas/Container.jsx | 47 ++------ .../AppBuilder/AppCanvas/appCanvasUtils.js | 6 +- .../AppCanvas/useCanvasDropHandler.js | 14 +-- .../ComponentManagerTab/DragLayer.jsx | 3 +- .../_hooks/useDropVirtualMoveableGhost.js | 101 ++++++++++++++++++ 5 files changed, 113 insertions(+), 58 deletions(-) create mode 100644 frontend/src/AppBuilder/_hooks/useDropVirtualMoveableGhost.js diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 65fec613d1..31bac368a7 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -12,8 +12,7 @@ import NoComponentCanvasContainer from './NoComponentCanvasContainer'; import { ModuleContainerBlank } from '@/modules/Modules/components'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import useSortedComponents from '../_hooks/useSortedComponents'; -import { noop } from 'lodash'; -import { useGhostMoveable } from '@/AppBuilder/_hooks/useGhostMoveable'; +import { useDropVirtualMoveableGhost } from '@/AppBuilder/_hooks/useDropVirtualMoveableGhost'; import { useCanvasDropHandler } from './useCanvasDropHandler'; import { findNewParentIdFromMousePosition } from './Grid/gridUtils'; @@ -37,33 +36,25 @@ export const Container = React.memo( columns, darkMode, canvasMaxWidth, - isViewerSidebarPinned, - pageSidebarStyle, - pagePositionType, componentType, appType, }) => { const { moduleId } = useModuleContext(); const realCanvasRef = useRef(null); const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow); - - const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow); - const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow); const setLastCanvasClickPosition = useStore((state) => state.setLastCanvasClickPosition, shallow); const canvasBgColor = useStore( (state) => (id === 'canvas' ? state.getCanvasBackgroundColor('canvas', darkMode) : ''), shallow ); - const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); - const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; // Initialize ghost moveable hook - const { activateGhost, deactivateGhost } = useGhostMoveable(id); + const { activateMoveableGhost, deactivateMoveableGhost } = useDropVirtualMoveableGhost(); - // Monitor drag layer to update ghost position continuously + // // Monitor drag layer to update ghost position continuously const { isDragging } = useDragLayer((monitor) => ({ isDragging: monitor.isDragging(), })); @@ -71,9 +62,9 @@ export const Container = React.memo( // // // Cleanup ghost when drag ends useEffect(() => { if (!isDragging) { - deactivateGhost(); + deactivateMoveableGhost(); } - }, [id, isDragging, deactivateGhost]); + }, [id, isDragging, deactivateMoveableGhost]); const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; @@ -81,8 +72,7 @@ export const Container = React.memo( const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); - // Get the drop handler from the new hook - const handleDrop = useCanvasDropHandler({ + const { handleDrop } = useCanvasDropHandler({ appType, }); @@ -106,7 +96,7 @@ export const Container = React.memo( height: item.component?.defaultSize?.height, }; if (clientOffset && id === 'canvas') { - activateGhost(componentSize, clientOffset, realCanvasRef); + activateMoveableGhost(componentSize, clientOffset, realCanvasRef); } }, drop: (item, monitor) => { @@ -136,29 +126,6 @@ export const Container = React.memo( // eslint-disable-next-line react-hooks/exhaustive-deps }, [canvasWidth, listViewMode, columns]); - const getCanvasWidth = useCallback(() => { - // if ( - // id === 'canvas' && - // !isPagesSidebarHidden && - // isViewerSidebarPinned && - // currentLayout !== 'mobile' && - // pagePositionType == 'side' && - // appType !== 'module' - // ) { - // return `calc(100% - ${pageSidebarStyle === 'icon' ? '85px' : '226px'})`; - // } - // if ( - // id === 'canvas' && - // !isPagesSidebarHidden && - // !isViewerSidebarPinned && - // currentLayout !== 'mobile' && - // pagePositionType == 'side' - // ) { - // return `calc(100% - ${'44px'})`; - // } - return '100%'; - }, [id, isPagesSidebarHidden, isViewerSidebarPinned, currentLayout, pagePositionType, pageSidebarStyle]); - const handleCanvasClick = useCallback( (e) => { const realCanvas = e.target.closest('.real-canvas'); diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index 45b1e63584..88548bd884 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -29,7 +29,6 @@ export function snapToGrid(canvasWidth, x, y) { //TODO: componentTypes should be a key value pair and get the definition directly by passing the componentType export const addNewWidgetToTheEditor = ( componentType, - eventMonitorObject, currentLayout, realCanvasRef, parentId, @@ -46,13 +45,12 @@ export const addNewWidgetToTheEditor = ( const { e } = useGridStore.getState().getGhostDragPosition(); const subContainerWidth = canvasBoundingRect?.width; - const { left: left3, top: top3 } = getMouseDistanceFromParentDiv( + const { left: _left, top: _top } = getMouseDistanceFromParentDiv( e, parentId === 'canvas' ? 'real-canvas' : parentId, parentCanvasType ); - // [left, top] = snapToGrid(subContainerWidth, left, top); - let [left, top] = snapToGrid(subContainerWidth, left3, top3); + let [left, top] = snapToGrid(subContainerWidth, _left, _top); const gridWidth = subContainerWidth / NO_OF_GRIDS; left = Math.round(left / gridWidth); diff --git a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js index 8dcb595f86..a13571b933 100644 --- a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js +++ b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js @@ -12,7 +12,6 @@ import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; import { isPDFSupported } from '@/_helpers/appUtils'; import toast from 'react-hot-toast'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -import { useGhostMoveable } from '../_hooks/useGhostMoveable'; import { handleDeactivateTargets, hideGridLines } from '../AppCanvas/Grid/gridUtils'; export const useCanvasDropHandler = ({ appType }) => { @@ -24,15 +23,13 @@ export const useCanvasDropHandler = ({ appType }) => { const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); - const { deactivateGhost } = useGhostMoveable(); + const handleDrop = async ({ componentType: draggedComponentType, component }, monitor, canvasId) => { const realCanvasRef = !canvasId || canvasId === 'canvas' ? document.getElementById(`real-canvas`) : document.getElementById(`canvas-${canvasId}`); - // Ensure ghost is deactivated before processing drop - deactivateGhost(); handleDeactivateTargets(); hideGridLines(); @@ -42,11 +39,6 @@ export const useCanvasDropHandler = ({ appType }) => { return; } - // const didDrop = monitor.didDrop(); - // if (didDrop) { - // return; - // } - if (draggedComponentType === 'PDF' && !isPDFSupported()) { toast.error( 'PDF is not supported in this version of browser. We recommend upgrading to the latest version for full support.' @@ -70,7 +62,6 @@ export const useCanvasDropHandler = ({ appType }) => { if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(draggedComponentType)) { let parentComponent = addNewWidgetToTheEditor( draggedComponentType, - monitor, currentLayout, realCanvasRef, canvasId, @@ -85,7 +76,6 @@ export const useCanvasDropHandler = ({ appType }) => { } else { const newComponent = addNewWidgetToTheEditor( draggedComponentType, - monitor, currentLayout, realCanvasRef, canvasId, @@ -119,5 +109,5 @@ export const useCanvasDropHandler = ({ appType }) => { setCurrentDragCanvasId(null); }; - return handleDrop; + return { handleDrop }; }; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index 32c7975e29..a3175ce64e 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -20,7 +20,7 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { const isRightSidebarPinned = useStore((state) => state.isRightSidebarPinned); const { isModuleEditor } = useModuleContext(); const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; - const handleDrop = useCanvasDropHandler({ appType: isModuleTab ? 'module' : 'app' }) || noop; + const { handleDrop } = useCanvasDropHandler({ appType: isModuleTab ? 'module' : 'app' }) || noop; const [{ isDragging }, drag, preview] = useDrag( () => ({ @@ -59,7 +59,6 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { return ( <> - {/* {isDragging && } */}
{isModuleTab ? : }
diff --git a/frontend/src/AppBuilder/_hooks/useDropVirtualMoveableGhost.js b/frontend/src/AppBuilder/_hooks/useDropVirtualMoveableGhost.js new file mode 100644 index 0000000000..e2795235eb --- /dev/null +++ b/frontend/src/AppBuilder/_hooks/useDropVirtualMoveableGhost.js @@ -0,0 +1,101 @@ +import { useRef } from 'react'; +import { useGridStore } from '@/_stores/gridStore'; + +export const useDropVirtualMoveableGhost = () => { + const ghostElementRef = useRef(null); + const isActiveRef = useRef(false); + + const getMoveableRef = useGridStore((state) => state.moveableRef); + const setVirtualTarget = useGridStore((state) => state.actions.setVirtualTarget); + + const createGhostMoveElement = (componentSize) => { + if (ghostElementRef.current) return; + + const ghost = document.createElement('div'); + ghost.id = 'moveable-ghost-element'; + ghost.className = 'moveable-ghost target'; + ghost.style.cssText = ` + position: absolute; + width: ${componentSize.width || 100}px; + height: ${componentSize.height || 40}px; + background: #D9E2FC; + opacity: 0.7; + pointer-events: none; + z-index: 9998; + box-sizing: border-box; + top: 0; + left: 0; + `; + + const container = document.getElementById('real-canvas'); + container.appendChild(ghost); + ghostElementRef.current = ghost; + + return ghost; + }; + + const updateMoveableGhostPosition = (mousePosition, canvasRef) => { + if (!ghostElementRef.current || !canvasRef?.current || !mousePosition) return; + + const canvasRect = canvasRef.current.getBoundingClientRect(); + const relativeX = mousePosition.x - canvasRect.left; + const relativeY = mousePosition.y - canvasRect.top; + + ghostElementRef.current.style.transform = `translate(${relativeX}px, ${relativeY}px)`; + }; + + const activateMoveableGhost = (componentSize, mousePosition, canvasRef) => { + if (isActiveRef.current) return; + + isActiveRef.current = true; + + const ghost = createGhostMoveElement(componentSize, canvasRef); + if (ghost && mousePosition) { + updateMoveableGhostPosition(mousePosition, canvasRef); + + // Trigger moveable drag on the ghost element to show guidelines + const moveableInstance = getMoveableRef; + if (moveableInstance && ghost) { + try { + const fakeEvent = new MouseEvent('mousedown', { + clientX: mousePosition.x, + clientY: mousePosition.y, + bubbles: true, + cancelable: true, + view: window, + button: 0, + buttons: 1, + }); + moveableInstance.waitToChangeTarget().then((e) => { + moveableInstance.dragStart(fakeEvent, ghost); + }); + setVirtualTarget(ghost); + } catch (error) { + console.warn('Failed to trigger moveable dragStart:', error); + } + } + } + }; + + const deactivateMoveableGhost = () => { + if (!isActiveRef.current) return; + + isActiveRef.current = false; + + const moveableInstance = getMoveableRef; + if (moveableInstance && ghostElementRef.current) { + try { + setVirtualTarget(null); + ghostElementRef.current.remove(); + ghostElementRef.current = null; + } catch (error) { + console.warn('Failed to trigger moveable dragEnd:', error); + } + } + }; + + return { + activateMoveableGhost, + deactivateMoveableGhost, + }; +}; From 823c669878c0ff0f9580aba0a174df3076b5c0d7 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Mon, 7 Jul 2025 00:00:19 +0530 Subject: [PATCH 15/33] merge branch 'main' into fix/appbuilder-02 --- frontend/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index 82d28db0cd..1a14db61e1 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 82d28db0cd954aa017d97938f368586b0650ef09 +Subproject commit 1a14db61e117145def63bf66e9225e049a2f5818 From 3be1b963f8c989559455831300d2e75428ef8924 Mon Sep 17 00:00:00 2001 From: NishidhJain11 Date: Mon, 7 Jul 2025 14:13:06 +0530 Subject: [PATCH 16/33] Enable HMR & optimize build & re-build times in development mode by updating devtool source-map value (#13047) Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com> --- frontend/package-lock.json | 114 ++++++++++++++++++++++++++++++++----- frontend/package.json | 4 +- frontend/webpack.config.js | 11 +++- 3 files changed, 113 insertions(+), 16 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8d15bf0995..1ebb29cca8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -161,6 +161,7 @@ "@babel/plugin-transform-runtime": "^7.19.6", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", + "@pmmmwh/react-refresh-webpack-plugin": "^0.6.0", "@storybook/addon-essentials": "^7.2.1", "@storybook/addon-interactions": "^7.2.1", "@storybook/addon-links": "^7.2.1", @@ -197,6 +198,7 @@ "postcss": "^8.4.35", "postcss-loader": "^8.1.0", "prettier": "^2.8.4", + "react-refresh": "^0.17.0", "sass": "^1.78.0", "sass-loader": "^13.2.0", "storybook": "^7.2.1", @@ -4903,32 +4905,31 @@ } }, "node_modules/@pmmmwh/react-refresh-webpack-plugin": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.16.tgz", - "integrity": "sha512-kLQc9xz6QIqd2oIYyXRUiAp79kGpFBm3fEM9ahfG1HI0WI5gdZ2OVHWdmZYnwODt7ISck+QuQ6sBPrtvUBML7Q==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.6.0.tgz", + "integrity": "sha512-AAc+QWfZ1KQ/e1C6OHWVlxU+ks6zFGOA44IJUlvju7RlDS8nsX6poPFOIlsg/rTofO9vKov12+WCjMhKkRKD5g==", "dev": true, "license": "MIT", "dependencies": { - "ansi-html": "^0.0.9", + "anser": "^2.1.1", "core-js-pure": "^3.23.3", "error-stack-parser": "^2.0.6", "html-entities": "^2.1.0", - "loader-utils": "^2.0.4", "schema-utils": "^4.2.0", "source-map": "^0.7.3" }, "engines": { - "node": ">= 10.13" + "node": ">=18.12" }, "peerDependencies": { - "@types/webpack": "4.x || 5.x", + "@types/webpack": "5.x", "react-refresh": ">=0.10.0 <1.0.0", "sockjs-client": "^1.4.0", "type-fest": ">=0.17.0 <5.0.0", - "webpack": ">=4.43.0 <6.0.0", - "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack": "^5.0.0", + "webpack-dev-server": "^4.8.0 || 5.x", "webpack-hot-middleware": "2.x", - "webpack-plugin-serve": "0.x || 1.x" + "webpack-plugin-serve": "1.x" }, "peerDependenciesMeta": { "@types/webpack": { @@ -4951,6 +4952,13 @@ } } }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/anser": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.2.tgz", + "integrity": "sha512-PMqBCBvrOVDRqLGooQb+z+t1Q0PiPyurUQeZRR5uHBOVZcW8B04KMmnT12USnhpNX2wCPagWzLVppQMUG3u0Dw==", + "dev": true, + "license": "MIT" + }, "node_modules/@pmmmwh/react-refresh-webpack-plugin/node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -10103,6 +10111,55 @@ } } }, + "node_modules/@storybook/preset-react-webpack/node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.16.tgz", + "integrity": "sha512-kLQc9xz6QIqd2oIYyXRUiAp79kGpFBm3fEM9ahfG1HI0WI5gdZ2OVHWdmZYnwODt7ISck+QuQ6sBPrtvUBML7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, "node_modules/@storybook/preset-react-webpack/node_modules/@types/node": { "version": "18.19.100", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.100.tgz", @@ -10113,6 +10170,26 @@ "undici-types": "~5.26.4" } }, + "node_modules/@storybook/preset-react-webpack/node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@storybook/preset-react-webpack/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/@storybook/preset-react-webpack/node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -29240,9 +29317,10 @@ "license": "MIT" }, "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -29698,6 +29776,16 @@ } } }, + "node_modules/react-spring/node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-spring/node_modules/scheduler": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 57785151d3..83239053e8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -156,6 +156,7 @@ "@babel/plugin-transform-runtime": "^7.19.6", "@babel/preset-env": "^7.20.2", "@babel/preset-react": "^7.18.6", + "@pmmmwh/react-refresh-webpack-plugin": "^0.6.0", "@storybook/addon-essentials": "^7.2.1", "@storybook/addon-interactions": "^7.2.1", "@storybook/addon-links": "^7.2.1", @@ -192,6 +193,7 @@ "postcss": "^8.4.35", "postcss-loader": "^8.1.0", "prettier": "^2.8.4", + "react-refresh": "^0.17.0", "sass": "^1.78.0", "sass-loader": "^13.2.0", "storybook": "^7.2.1", @@ -233,7 +235,7 @@ } }, "scripts": { - "start": "webpack serve --port 8082 --host 0.0.0.0", + "start": "webpack serve --hot --port 8082 --host 0.0.0.0", "build": "webpack --mode=production && cp -a ./assets/. ./build/assets/", "lint": "eslint . '**/*.{js,jsx}'", "format": "eslint . --fix '**/*.{js,jsx}'", diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 986c7011b1..6fedc3b217 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -9,9 +9,11 @@ const { sentryWebpackPlugin } = require('@sentry/webpack-plugin'); const fs = require('fs'); const versionPath = path.resolve(__dirname, '.version'); const version = fs.readFileSync(versionPath, 'utf-8').trim(); +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); const environment = process.env.NODE_ENV === 'production' ? 'production' : 'development'; const edition = process.env.TOOLJET_EDITION; +const isDevEnv = process.env.NODE_ENV === 'development'; // Create path to empty module const emptyModulePath = path.resolve(__dirname, 'src/modules/emptyModule'); @@ -76,6 +78,10 @@ if (process.env.APM_VENDOR === 'sentry') { ); } +if (isDevEnv) { + plugins.push(new ReactRefreshWebpackPlugin()); +} + module.exports = { mode: environment, optimization: { @@ -122,7 +128,7 @@ module.exports = { '@cloud/modules': emptyModulePath, }, }, - devtool: environment === 'development' ? 'source-map' : 'hidden-source-map', + devtool: environment === 'development' ? 'eval-source-map' : 'hidden-source-map', module: { rules: [ { @@ -197,8 +203,9 @@ module.exports = { loader: 'babel-loader', options: { plugins: [ + isDevEnv && require.resolve('react-refresh/babel'), ['import', { libraryName: 'lodash', libraryDirectory: '', camel2DashComponentName: false }, 'lodash'], - ], + ].filter(Boolean), }, }, }, From fb7c751a34a6955f7206d28a8d6549e8d4ac01d7 Mon Sep 17 00:00:00 2001 From: NishidhJain11 Date: Mon, 7 Jul 2025 15:11:58 +0530 Subject: [PATCH 17/33] Feat/AI modularisation (#13142) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix deps * Modularisation changes for Build with AI feature * New app loading UI for Build with AI feature & added animation for chat messages * Fix Error using AI feature * add missing services and logic * fix app gen * update submodules * EE frontend submodule updated * update submodules * EE frontend submodule updated post sync * Added Artifact Preview component to ee moddules list * Updated ai slice code * app gen changes * Resolved fix with AI bugs * Resolved AI Copilot bugs * app gen changes and query fixes * fix query generation bugs * update copilot * Resolved ChatMode dropdown & popover bug fix * Resolved PR suggestions & PreviewBox component in CE edition * Synced frontend/ee with main * Synced server/ee with main branch * Enhance submodule checkout process to handle branch existence and fallback to main (#13218) * Enhance submodule checkout process to handle branch existence and fallback to main * Improve submodule checkout process to handle branch validation and fallback to main * chore: Comment out Node.js setup, dependency installation, and build steps in cloud frontend workflow * refactor: Enhance submodule checkout process to include submodule name in logs * Update submodule checkout process to use the correct submodule name extraction method * fix: Update submodule checkout script to use correct submodule path variable * Improve submodule checkout process to correctly handle branch names and fallback to main * chore: Uncomment Node.js setup, dependency installation, and build steps in cloud frontend workflow * fix: Update branch checkout logic to use correct syntax and improve fallback handling * fix: Update git checkout command to use -B flag for branch creation * fix: Improve submodule checkout process to explicitly fetch branch ref before checkout * fix: Enhance submodule checkout process with improved branch validation and error handling * fix: Improve branch checkout logic by enhancing fetch command and validating branch existence * fix: Enhance manual Git checkout process with improved fetch and error handling * fix: Restore Node.js setup, dependency installation, and Netlify deployment steps in workflow * 🔄 chore: update submodules to latest main after auto-merge * Took sync of fix/appbuilder-02 in frontend/ee --------- Co-authored-by: Kartik Gupta Co-authored-by: Adish M <44204658+adishM98@users.noreply.github.com> Co-authored-by: adishM98 Bot --- .github/workflows/cloud-frontend.yml | 47 +- frontend/ee | 2 +- frontend/package-lock.json | 2344 ++++++++++++++--- frontend/package.json | 1 + frontend/src/AppBuilder/AppBuilder.jsx | 37 +- .../src/AppBuilder/ArtifactPreview/index.jsx | 9 + .../src/AppBuilder/CodeEditor/FixWithAi.jsx | 9 + .../CodeEditor/MultiLineCodeEditor.jsx | 22 +- .../src/AppBuilder/CodeEditor/PreviewBox.jsx | 138 +- .../CodeEditor/SingleLineCodeEditor.jsx | 16 +- .../src/AppBuilder/Header/EditorHeader.jsx | 91 +- frontend/src/AppBuilder/Header/Steps.jsx | 64 + .../AppBuilder/LeftSidebar/LeftSidebar.jsx | 41 +- .../Components/DataSourceSelect.jsx | 1 - frontend/src/AppBuilder/_hooks/useAppData.js | 20 +- .../src/AppBuilder/_stores/slices/appSlice.js | 36 +- .../_stores/slices/componentsSlice.js | 1 + .../_stores/slices/dataQuerySlice.js | 14 +- .../AppBuilder/_stores/slices/fixWithAi.js | 5 + frontend/src/AppBuilder/_stores/store.js | 2 + frontend/src/AppBuilder/_stores/utils.js | 10 + .../_utils/component-properties-validation.js | 2 +- frontend/src/HomePage/HomePage.jsx | 12 +- .../CopyToClipboard/CopyToClipboard.jsx | 16 +- frontend/src/_services/ai.service.js | 14 +- frontend/src/_styles/componentdesign.scss | 2 +- frontend/src/_styles/popover.scss | 1 + frontend/src/_styles/theme.scss | 293 +-- frontend/src/_ui/Icon/solidIcons/AddApp.jsx | 12 + frontend/src/_ui/Icon/solidIcons/Bug.jsx | 21 +- frontend/src/_ui/Icon/solidIcons/Bulb.jsx | 16 + .../src/_ui/Icon/solidIcons/CheckCircle.jsx | 14 + .../src/_ui/Icon/solidIcons/DottedArrow.jsx | 12 + .../src/_ui/Icon/solidIcons/LoadingCircle.jsx | 24 + frontend/src/_ui/Icon/solidIcons/PageIcon.jsx | 18 + frontend/src/_ui/Icon/solidIcons/Retry.jsx | 4 +- .../src/_ui/Icon/solidIcons/SolidArrow.jsx | 12 + frontend/src/_ui/Icon/solidIcons/index.js | 15 + frontend/src/lib/utils.js | 6 +- frontend/tailwind.config.js | 10 +- ...1750927057649-AddAiGenerationFlagsInApp.ts | 62 + .../1750927083207-AddArtifactsTable.ts | 81 + server/ee | 2 +- server/package-lock.json | 72 + server/package.json | 2 + .../ai_conversation_message.entity.ts | 5 + server/src/entities/app.entity.ts | 30 + server/src/entities/artifact.entity.ts | 51 + .../src/modules/ai/interfaces/IUtilService.ts | 1 + server/src/modules/ai/module.ts | 39 +- .../ai-conversation-message.repository.ts | 2 +- .../ai/repositories/artifact.repository.ts | 11 + .../src/modules/ai/services/graph.service.ts | 27 + server/src/modules/ai/util.service.ts | 1 + server/src/modules/apps/dto/index.ts | 13 + .../modules/apps/interfaces/IUtilService.ts | 2 +- server/src/modules/apps/service.ts | 4 +- .../apps/services/component.service.ts | 17 + .../modules/apps/services/event.service.ts | 9 + server/src/modules/apps/util.service.ts | 33 +- server/src/modules/data-queries/module.ts | 2 +- server/src/modules/data-queries/service.ts | 7 + 62 files changed, 3141 insertions(+), 746 deletions(-) create mode 100644 frontend/src/AppBuilder/ArtifactPreview/index.jsx create mode 100644 frontend/src/AppBuilder/CodeEditor/FixWithAi.jsx create mode 100644 frontend/src/AppBuilder/Header/Steps.jsx create mode 100644 frontend/src/AppBuilder/_stores/slices/fixWithAi.js create mode 100644 frontend/src/_ui/Icon/solidIcons/AddApp.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/Bulb.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/CheckCircle.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/DottedArrow.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/LoadingCircle.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/PageIcon.jsx create mode 100644 frontend/src/_ui/Icon/solidIcons/SolidArrow.jsx create mode 100644 server/data-migrations/1750927057649-AddAiGenerationFlagsInApp.ts create mode 100644 server/data-migrations/1750927083207-AddArtifactsTable.ts create mode 100644 server/src/entities/artifact.entity.ts create mode 100644 server/src/modules/ai/repositories/artifact.repository.ts create mode 100644 server/src/modules/ai/services/graph.service.ts diff --git a/.github/workflows/cloud-frontend.yml b/.github/workflows/cloud-frontend.yml index 5ae1feea92..9d0fda1e30 100644 --- a/.github/workflows/cloud-frontend.yml +++ b/.github/workflows/cloud-frontend.yml @@ -47,19 +47,44 @@ jobs: echo "🔀 Attempting to checkout '$BRANCH' in each submodule and validating" - git submodule foreach --recursive bash -c " - echo '↪ '\$name': trying to checkout $BRANCH' - git fetch --all || true - git fetch origin $BRANCH:$BRANCH || echo '⚠️ '\$name': could not fetch $BRANCH' - git checkout $BRANCH || echo '⚠️ '\$name': checkout failed, staying on current branch' - CURRENT=\$(git rev-parse --abbrev-ref HEAD) - echo '✅ '\$name': now on branch '\$CURRENT - if [ \"\$CURRENT\" != \"$BRANCH\" ]; then - echo '❌ '\$name': expected $BRANCH but got '\$CURRENT + BRANCH="$BRANCH" git submodule foreach --recursive bash -c ' + name="$sm_path" + echo "" + echo "Entering '\''$name'\''" + echo "↪ $name: trying to checkout branch '\''$BRANCH'\''" + + if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null; then + git fetch origin "$BRANCH:$BRANCH" || { + echo "❌ $name: fetch failed for $BRANCH" + exit 1 + } + + PREV=$(git rev-parse --short HEAD || echo "unknown") + git checkout "$BRANCH" || { + echo "❌ $name: checkout failed for $BRANCH" + exit 1 + } + + echo "Previous HEAD position was $PREV: $(git log -1 --pretty=%s || echo 'unknown')" + echo "✅ $name: checked out branch $BRANCH" + else + echo "⚠️ $name: branch '$BRANCH' not found on origin. Falling back to 'main'" + PREV=$(git rev-parse --short HEAD || echo "unknown") + git checkout main && git pull origin main || { + echo "❌ $name: fallback to main failed" + exit 1 + } + echo "Previous HEAD position was $PREV: $(git log -1 --pretty=%s || echo 'unknown')" + echo "✅ $name: now on branch main" + fi + + CURRENT=$(git rev-parse --abbrev-ref HEAD) + echo "🔎 $name: current branch = $CURRENT" + if [ "$CURRENT" != "$BRANCH" ] && [ "$CURRENT" != "main" ]; then + echo "❌ $name: unexpected branch state — wanted '$BRANCH' or fallback 'main', got '$CURRENT'" exit 1 fi - " - + ' - name: 🧰 Setup Node.js uses: actions/setup-node@v2 diff --git a/frontend/ee b/frontend/ee index 1a14db61e1..6708f78aa5 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 1a14db61e117145def63bf66e9225e049a2f5818 +Subproject commit 6708f78aa593343813563ff39ee784e26cd97f1f diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1ebb29cca8..b165f8fb62 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,6 +22,7 @@ "@dnd-kit/utilities": "^3.2.1", "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", + "@mdxeditor/editor": "^3.38.0", "@microsoft/fetch-event-source": "^2.0.1", "@radix-ui/colors": "^0.1.8", "@radix-ui/react-avatar": "^1.0.4", @@ -2592,6 +2593,30 @@ "@lezer/common": "^1.1.0" } }, + "node_modules/@codemirror/lang-angular": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz", + "integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.3" + } + }, + "node_modules/@codemirror/lang-cpp": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/cpp": "^1.0.0" + } + }, "node_modules/@codemirror/lang-css": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", @@ -2605,6 +2630,46 @@ "@lezer/css": "^1.1.7" } }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/go": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", + "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.0" + } + }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, "node_modules/@codemirror/lang-javascript": { "version": "6.2.3", "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.3.tgz", @@ -2620,6 +2685,73 @@ "@lezer/javascript": "^1.0.0" } }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-less": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz", + "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-liquid": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.2.3.tgz", + "integrity": "sha512-yeN+nMSrf/lNii3FJxVVEGQwFG0/2eDyH6gNOj+TGCa0hlNO4bhQnoO5ISnd7JOG+7zTEcI/GOoyraisFVY7jQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.3.tgz", + "integrity": "sha512-1fn1hQAPWlSSMCvnF810AkhWpNLkJpl66CRfIy3vVl20Sl4NwChkorCHqpMtNbXr1EuMJsrDnhEpjZxKZ2UX3A==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, "node_modules/@codemirror/lang-python": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.0.tgz", @@ -2633,6 +2765,16 @@ "@lezer/python": "^1.1.4" } }, + "node_modules/@codemirror/lang-rust": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/rust": "^1.0.0" + } + }, "node_modules/@codemirror/lang-sass": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz", @@ -2660,6 +2802,61 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@codemirror/lang-vue": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz", + "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-wast": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz", + "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", + "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz", @@ -2674,6 +2871,45 @@ "style-mod": "^4.0.0" } }, + "node_modules/@codemirror/language-data": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.1.tgz", + "integrity": "sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-angular": "^0.1.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-go": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-less": "^6.0.0", + "@codemirror/lang-liquid": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sass": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-vue": "^0.1.1", + "@codemirror/lang-wast": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/legacy-modes": "^6.4.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.1.tgz", + "integrity": "sha512-DJYQQ00N1/KdESpZV7jg9hafof/iBNp9h7TYo1SLMk86TWl9uDsVdho2dzd81K+v4retmK6mdC7WpuOQDytQqw==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, "node_modules/@codemirror/lint": { "version": "6.8.5", "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", @@ -2685,6 +2921,19 @@ "crelt": "^1.0.5" } }, + "node_modules/@codemirror/merge": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/@codemirror/merge/-/merge-6.10.2.tgz", + "integrity": "sha512-rmHzVkt5FnCtsi0IgvDIDjh/J4LmbfOboB7FMvVl21IHO0p1QM6jSwjkBjBD3D+c+T79OabEqoduCqvJCBV8Yg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/highlight": "^1.0.0", + "style-mod": "^4.1.0" + } + }, "node_modules/@codemirror/search": { "version": "6.5.10", "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz", @@ -2728,6 +2977,73 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@codesandbox/nodebox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz", + "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==", + "license": "SEE LICENSE IN ./LICENSE", + "dependencies": { + "outvariant": "^1.4.0", + "strict-event-emitter": "^0.4.3" + } + }, + "node_modules/@codesandbox/sandpack-client": { + "version": "2.19.8", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", + "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==", + "license": "Apache-2.0", + "dependencies": { + "@codesandbox/nodebox": "0.1.8", + "buffer": "^6.0.3", + "dequal": "^2.0.2", + "mime-db": "^1.52.0", + "outvariant": "1.4.0", + "static-browser-server": "1.0.3" + } + }, + "node_modules/@codesandbox/sandpack-react": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-react/-/sandpack-react-2.20.0.tgz", + "integrity": "sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==", + "license": "Apache-2.0", + "dependencies": { + "@codemirror/autocomplete": "^6.4.0", + "@codemirror/commands": "^6.1.3", + "@codemirror/lang-css": "^6.0.1", + "@codemirror/lang-html": "^6.4.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.3.2", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.1", + "@codesandbox/sandpack-client": "^2.19.8", + "@lezer/highlight": "^1.1.3", + "@react-hook/intersection-observer": "^3.1.1", + "@stitches/core": "^1.2.6", + "anser": "^2.1.1", + "clean-set": "^1.1.2", + "dequal": "^2.0.2", + "escape-carriage": "^1.3.1", + "lz-string": "^1.4.4", + "react-devtools-inline": "4.4.0", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/@codesandbox/sandpack-react/node_modules/anser": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.2.tgz", + "integrity": "sha512-PMqBCBvrOVDRqLGooQb+z+t1Q0PiPyurUQeZRR5uHBOVZcW8B04KMmnT12USnhpNX2wCPagWzLVppQMUG3u0Dw==", + "license": "MIT" + }, + "node_modules/@codesandbox/sandpack-react/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -4316,12 +4632,278 @@ "dev": true, "license": "MIT" }, + "node_modules/@lexical/clipboard": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.32.1.tgz", + "integrity": "sha512-oO7CuMVh3EFEqtE6+7Ccf7jMD5RNUmSdTnFm/X4kYNGqs9lgGt8j5PgSk7oP9OuAjxKNdBTbltSlh54CX3AUIg==", + "license": "MIT", + "dependencies": { + "@lexical/html": "0.32.1", + "@lexical/list": "0.32.1", + "@lexical/selection": "0.32.1", + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/code": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/code/-/code-0.32.1.tgz", + "integrity": "sha512-2rXj8s/CG32XKQ2EpORpACfpzyAxB+/SrQW2cjwczarLs5Fxnx6u6HwahZnxaF0z5UHIPUy90qDiOiRExc74Yg==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.32.1", + "lexical": "0.32.1", + "prismjs": "^1.30.0" + } + }, + "node_modules/@lexical/devtools-core": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.32.1.tgz", + "integrity": "sha512-3WnZQo6Qig7ccjDu2b8s1Kb5CCXowxnK0i8CCRG9mHAw7i6XpZUYAbk4rmcK/qbhLHrc7LwUrAMFzGtfLEH3XA==", + "license": "MIT", + "dependencies": { + "@lexical/html": "0.32.1", + "@lexical/link": "0.32.1", + "@lexical/mark": "0.32.1", + "@lexical/table": "0.32.1", + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/dragon": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.32.1.tgz", + "integrity": "sha512-Dlx8P2b/O7gZLmXnoanmDkFL5RgA8Vvix4ZuSvT0apblqySzgi8l3NHHwwqXy1g2nfSupvpr7Dsf10Lu3l0Hlw==", + "license": "MIT", + "dependencies": { + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/hashtag": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.32.1.tgz", + "integrity": "sha512-S63bb7uIB4hO2V0UmzUiKlwAGegQlyFKqrOw9NJwOb8O96gHRxr27FUsEb8ToWLM8TSm2aw1WsZXs7CJQqGtCg==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/history": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.32.1.tgz", + "integrity": "sha512-IRsKllumYEWxmzR2evN30MFY+JBM723lSyzm2PAQcgHCeBxi8t0Vc3EdyJRay+YdN65JgrohQi1WbktbK923uQ==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/html": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.32.1.tgz", + "integrity": "sha512-uctCdC9gVzx/Sw9CimT4C2IDfSbfEGYunyIrJBpsfcdqp0rroGNizjIoZNBH3xcgkk9UDboSADo+wimbzEoy8A==", + "license": "MIT", + "dependencies": { + "@lexical/selection": "0.32.1", + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/link": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.32.1.tgz", + "integrity": "sha512-atdwNpWjZ0U2/kgS0ATTkZ8lJLHiv3TsJgqJL33BuV9Gn7advJokd4faM79Y8XxkhiPi1lVTBSHgI8V4hs+c+Q==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/list": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.32.1.tgz", + "integrity": "sha512-3zShCfEdAvodR6mQ5CNN1gcEwfV341LXJzWCIkZzG1cPwaiBHUlT7TynQtKTPn1sATCEMmxoDG0/T+itsRNZgA==", + "license": "MIT", + "dependencies": { + "@lexical/selection": "0.32.1", + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/mark": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.32.1.tgz", + "integrity": "sha512-AXF2wmUvvSI45y+sgZKnU0pnUdttd9v75DDQgdplqtCkyDqHVGxVCNCrLE+PJtzIrwJxtA2UyC8yFZMBM92HpA==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/markdown": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.32.1.tgz", + "integrity": "sha512-AmUTRRx6Je0AOiQqp48Xn92/71AzhFgi4nO1EtPW5eae1CihrtiEh5UQr48mV6EyjvH9D3OlOLU8XrzS+J9a+w==", + "license": "MIT", + "dependencies": { + "@lexical/code": "0.32.1", + "@lexical/link": "0.32.1", + "@lexical/list": "0.32.1", + "@lexical/rich-text": "0.32.1", + "@lexical/text": "0.32.1", + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/offset": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/offset/-/offset-0.32.1.tgz", + "integrity": "sha512-zfHqoLlQ0lq1akFHy81xnDaRRE5KkqFa7OovOxKPBpALQCiJIAb2ykqj/Woc2oUeYaEcnkaFU9+kEWMK9yY0fQ==", + "license": "MIT", + "dependencies": { + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/overflow": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.32.1.tgz", + "integrity": "sha512-wjcFGjzkbugds2Q5Wag59WrcxJwMUACEXms1FtFdu1/YcBPqNAKJSyfo8Z/5LGfstQEb2nPtSuEQZUb7v+XYyA==", + "license": "MIT", + "dependencies": { + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/plain-text": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.32.1.tgz", + "integrity": "sha512-uFS3xoETB3phnYHZXfMKvl8gh6YRW7rpokuJmQVMHNNBklORmMpN00rRQ/zsc/jt/nPzaPpE5cLwSHXeJdqJUg==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.32.1", + "@lexical/selection": "0.32.1", + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/react": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.32.1.tgz", + "integrity": "sha512-PCiAiwGIGfkYb2o9Kx+gGGqXwxqb7/W4cGSnw1nzmNtCerJ3S64WZs87Lgcow0RlDSwqzpH534+eCyIddueSqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.8", + "@lexical/devtools-core": "0.32.1", + "@lexical/dragon": "0.32.1", + "@lexical/hashtag": "0.32.1", + "@lexical/history": "0.32.1", + "@lexical/link": "0.32.1", + "@lexical/list": "0.32.1", + "@lexical/mark": "0.32.1", + "@lexical/markdown": "0.32.1", + "@lexical/overflow": "0.32.1", + "@lexical/plain-text": "0.32.1", + "@lexical/rich-text": "0.32.1", + "@lexical/table": "0.32.1", + "@lexical/text": "0.32.1", + "@lexical/utils": "0.32.1", + "@lexical/yjs": "0.32.1", + "lexical": "0.32.1", + "react-error-boundary": "^3.1.4" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/rich-text": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.32.1.tgz", + "integrity": "sha512-SnmpZ7boTLxeYfNezNLvchDiJOAALA2nD0Uq/SpkIOJ6R01R7m1aPdLv55LGKoBT9UxCRdo0HWXytwiVZI+ehQ==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.32.1", + "@lexical/selection": "0.32.1", + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/selection": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.32.1.tgz", + "integrity": "sha512-X1aXJdq/5EOuSuMOqK3t+rEVmpqLf+vc2Kl5YuP8+gGWUbXuxR6iryrQuy1mAViZpF/5qw4HO/Sb+9JjubaZEg==", + "license": "MIT", + "dependencies": { + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/table": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.32.1.tgz", + "integrity": "sha512-sGk2jUbQHj5hatpxRyl6IE2oWsjRnYhmaP94THzn95/uK69o8eSizcnd148WzYsX8Zz+L9PTLS1xjvCbfLTP+A==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.32.1", + "@lexical/utils": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/text": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.32.1.tgz", + "integrity": "sha512-0Ek8F3KC4d16b2YaTHdyYFqDSBZ5KRtGrqU3GBog+VOGxucGaEbXEK1/ypX5CTe/wwkQDrH0FKWPQbd3l5t5YQ==", + "license": "MIT", + "dependencies": { + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/utils": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.32.1.tgz", + "integrity": "sha512-ZaqZZksNIHJd+g8GXc11D1ESi8JzsdLVQZ+9odXVaNxtwDIaGIqMSccFyuZ9VSoJDde4JXZkgp/0PShbAZDkyw==", + "license": "MIT", + "dependencies": { + "@lexical/list": "0.32.1", + "@lexical/selection": "0.32.1", + "@lexical/table": "0.32.1", + "lexical": "0.32.1" + } + }, + "node_modules/@lexical/yjs": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.32.1.tgz", + "integrity": "sha512-VHGTg5z4wcDkPe8NnhzA5+CoOKJ5tVmTmMvoiZ91rtNUFQPxWRky88Gjt1e3yXldYp4pImNEgtAjlWqvaJBYGA==", + "license": "MIT", + "dependencies": { + "@lexical/offset": "0.32.1", + "@lexical/selection": "0.32.1", + "lexical": "0.32.1" + }, + "peerDependencies": { + "yjs": ">=13.5.22" + } + }, "node_modules/@lezer/common": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", "license": "MIT" }, + "node_modules/@lezer/cpp": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.3.tgz", + "integrity": "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lezer/css": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.1.11.tgz", @@ -4333,6 +4915,17 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, "node_modules/@lezer/highlight": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", @@ -4342,6 +4935,28 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/html": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", + "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/java": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz", + "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lezer/javascript": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz", @@ -4353,6 +4968,17 @@ "@lezer/lr": "^1.3.0" } }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lezer/lr": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", @@ -4362,6 +4988,27 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/markdown": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.4.3.tgz", + "integrity": "sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@lezer/php": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.2.tgz", + "integrity": "sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, "node_modules/@lezer/python": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", @@ -4373,6 +5020,17 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@lezer/rust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz", + "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@lezer/sass": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.0.7.tgz", @@ -4384,6 +5042,28 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz", + "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/@mapbox/geojson-rewind/-/geojson-rewind-0.5.2.tgz", @@ -4524,6 +5204,190 @@ "react": ">=16" } }, + "node_modules/@mdxeditor/editor": { + "version": "3.38.0", + "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-3.38.0.tgz", + "integrity": "sha512-m7vK7TQ4P5TUWvRr1yHgzoJm2kupmR2PWKGPRzl3+pPL10bwnVtGhTL2l3PrLMlnRhNqpaBK23XmqdLSq4tvzg==", + "license": "MIT", + "dependencies": { + "@codemirror/commands": "^6.2.4", + "@codemirror/lang-markdown": "^6.2.3", + "@codemirror/language-data": "^6.5.1", + "@codemirror/merge": "^6.4.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.23.0", + "@codesandbox/sandpack-react": "^2.20.0", + "@lexical/clipboard": "^0.32.1", + "@lexical/link": "^0.32.1", + "@lexical/list": "^0.32.1", + "@lexical/markdown": "^0.32.1", + "@lexical/plain-text": "^0.32.1", + "@lexical/react": "^0.32.1", + "@lexical/rich-text": "^0.32.1", + "@lexical/selection": "^0.32.1", + "@lexical/utils": "^0.32.1", + "@mdxeditor/gurx": "^1.1.4", + "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-dialog": "^1.1.11", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-popover": "^1.1.11", + "@radix-ui/react-popper": "^1.2.4", + "@radix-ui/react-select": "^2.2.2", + "@radix-ui/react-toggle-group": "^1.1.7", + "@radix-ui/react-toolbar": "^1.1.7", + "@radix-ui/react-tooltip": "^1.2.4", + "classnames": "^2.3.2", + "cm6-theme-basic-light": "^0.2.0", + "codemirror": "^6.0.1", + "downshift": "^7.6.0", + "js-yaml": "4.1.0", + "lexical": "^0.32.1", + "mdast-util-directive": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-frontmatter": "^2.0.1", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-mdx": "^3.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "micromark-extension-directive": "^3.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.1", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.1", + "micromark-util-symbol": "^2.0.0", + "react-hook-form": "^7.56.1", + "unidiff": "^1.0.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">= 18 || >= 19", + "react-dom": ">= 18 || >= 19" + } + }, + "node_modules/@mdxeditor/editor/node_modules/@radix-ui/colors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", + "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==", + "license": "MIT" + }, + "node_modules/@mdxeditor/editor/node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@mdxeditor/editor/node_modules/@radix-ui/react-popper": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@mdxeditor/editor/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@mdxeditor/editor/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mdxeditor/gurx": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@mdxeditor/gurx/-/gurx-1.2.3.tgz", + "integrity": "sha512-5DQOlEx46oN9spggrC8husAGAhVoEFBGIYKN48es08XhRUbSU6l5bcIQYwRrQaY8clU1tExIcXzw8/fNnoxjpg==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">= 18 || >= 19", + "react-dom": ">= 18 || >= 19" + } + }, "node_modules/@microsoft/fetch-event-source": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz", @@ -4792,6 +5656,12 @@ "node": ">= 8" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -5077,30 +5947,6 @@ } } }, - "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", - "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-collection": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.6.tgz", @@ -5157,6 +6003,159 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", + "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -5439,6 +6438,15 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -5625,30 +6633,6 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", - "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-popper": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.2.tgz", @@ -5968,6 +6952,30 @@ } } }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", + "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-primitive": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.2.tgz", @@ -5991,6 +6999,104 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.4.tgz", @@ -6165,6 +7271,70 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", + "integrity": "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slider": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.4.tgz", @@ -6245,18 +7415,43 @@ } } }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.9.tgz", + "integrity": "sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-toggle-group": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.9.tgz", - "integrity": "sha512-HJ6gXdYVN38q/5KDdCcd+JTuXUyFZBMJbwXaU/82/Gi+V2ps6KpiZ2sQecAeZCV80POGRfkUBdUIj6hIdF6/MQ==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.10.tgz", + "integrity": "sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.2", - "@radix-ui/react-roving-focus": "1.1.9", - "@radix-ui/react-toggle": "1.1.8", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-toggle": "1.1.9", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { @@ -6274,21 +7469,13 @@ } } }, - "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.9.tgz", - "integrity": "sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ==", + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-collection": "1.1.6", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.2", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -6305,15 +7492,31 @@ } } }, - "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-toggle": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.8.tgz", - "integrity": "sha512-hrpa59m3zDnsa35LrTOH5s/a3iGv/VD+KKQjjiCTo/W4r0XwPpiWQvAv6Xl1nupSoaZeNNxW6sJH9ZydsjKdYQ==", + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-primitive": "2.1.2", - "@radix-ui/react-use-controllable-state": "1.2.2" + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -6330,6 +7533,94 @@ } } }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.10.tgz", + "integrity": "sha512-jiwQsduEL++M4YBIurjSa+voD86OIytCod0/dbIxFZDLD8NfO1//keXYMfsW8BPcfqwoNjt+y06XcJqAb4KR7A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.10" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.6.tgz", @@ -6447,30 +7738,6 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-presence": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz", - "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-visually-hidden": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.2.tgz", @@ -6806,6 +8073,28 @@ "integrity": "sha512-tieX9Va5w1yP88vMgfH1pHTacDQ9TgDTjox3tLlisKDXRQWdjw+QeVVghhf5XqqIxXHgPdcGwBvKY6UP+SIvLw==", "license": "MIT" }, + "node_modules/@react-hook/intersection-observer": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-hook/intersection-observer/-/intersection-observer-3.1.2.tgz", + "integrity": "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==", + "license": "MIT", + "dependencies": { + "@react-hook/passive-layout-effect": "^1.2.0", + "intersection-observer": "^0.10.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/@react-hook/passive-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", + "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/@react-native/assets-registry": { "version": "0.79.2", "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.2.tgz", @@ -7672,6 +8961,12 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@stitches/core": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", + "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==", + "license": "MIT" + }, "node_modules/@storybook/addon-actions": { "version": "7.6.20", "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-7.6.20.tgz", @@ -8902,241 +10197,6 @@ } } }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.9.tgz", - "integrity": "sha512-qqGkE9h018CSbpO4ag4rR6ZuOc/A9wM3dUv2jHrkfwUqspuvZmPegBPElVimH0FPWrYn4Alt4QTOptRjbwJnKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-primitive": "2.1.2", - "@radix-ui/react-roving-focus": "1.1.9", - "@radix-ui/react-separator": "1.1.6", - "@radix-ui/react-toggle-group": "1.1.9" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.9.tgz", - "integrity": "sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-collection": "1.1.6", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.2", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-collection": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.6.tgz", - "integrity": "sha512-PbhRFK4lIEw9ADonj48tiYWzkllz81TM7KVYyyMMw2cwHO7D5h4XKEblL8NlaRisTK3QTe6tBEhDccFUryxHBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.2", - "@radix-ui/react-slot": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz", - "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-id/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-controllable-state/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@storybook/components/node_modules/@radix-ui/react-toolbar/node_modules/@radix-ui/react-separator": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.6.tgz", - "integrity": "sha512-Izof3lPpbCfTM7WDta+LRkz31jem890VjEvpVRoWQNKpDUMMVffuyq854XPGP1KYGWWmjmYvHvPFeocWhFCy1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, "node_modules/@storybook/components/node_modules/@radix-ui/react-use-callback-ref": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", @@ -13030,7 +14090,6 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -15017,6 +16076,12 @@ "node": ">=0.10.0" } }, + "node_modules/clean-set": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz", + "integrity": "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==", + "license": "MIT" + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -15130,6 +16195,18 @@ "node": ">=6" } }, + "node_modules/cm6-theme-basic-light": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz", + "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -15449,6 +16526,12 @@ "node": ">= 0.6" } }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -16074,7 +17157,6 @@ "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "license": "ISC", - "peer": true, "dependencies": { "es5-ext": "^0.10.64", "type": "^2.7.2" @@ -16763,6 +17845,15 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -16999,6 +18090,28 @@ "node": ">=12" } }, + "node_modules/downshift": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.2.tgz", + "integrity": "sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/downshift/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, "node_modules/draft-js": { "version": "0.11.7", "resolved": "https://registry.npmjs.org/draft-js/-/draft-js-0.11.7.tgz", @@ -17593,7 +18706,6 @@ "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "hasInstallScript": true, "license": "ISC", - "peer": true, "dependencies": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -17609,7 +18721,6 @@ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "license": "MIT", - "peer": true, "dependencies": { "d": "1", "es5-ext": "^0.10.35", @@ -17621,7 +18732,6 @@ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", "license": "ISC", - "peer": true, "dependencies": { "d": "^1.0.2", "ext": "^1.7.0" @@ -17782,6 +18892,12 @@ "node": ">=6" } }, + "node_modules/escape-carriage": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz", + "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==", + "license": "MIT" + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -18342,7 +19458,6 @@ "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", "license": "ISC", - "peer": true, "dependencies": { "d": "^1.0.1", "es5-ext": "^0.10.62", @@ -18441,6 +19556,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -18470,7 +19599,6 @@ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", "license": "MIT", - "peer": true, "dependencies": { "d": "1", "es5-ext": "~0.10.14" @@ -18644,7 +19772,6 @@ "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", "license": "ISC", - "peer": true, "dependencies": { "type": "^2.7.2" } @@ -18821,6 +19948,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", @@ -19536,6 +20676,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -21382,6 +22530,12 @@ "node": ">= 0.10" } }, + "node_modules/intersection-observer": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.10.0.tgz", + "integrity": "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==", + "license": "W3C-20150513" + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -23847,6 +25001,12 @@ "node": ">= 0.8.0" } }, + "node_modules/lexical": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.32.1.tgz", + "integrity": "sha512-Rvr9p00zUwzjXIqElIjMDyl/24QHw68yaqmXUWIT3lSdSAr8OpjSJK3iWBLZwVZwwpVhwShZRckomc+3vSb/zw==", + "license": "MIT" + }, "node_modules/lib0": { "version": "0.2.107", "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.107.tgz", @@ -24121,7 +25281,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, "license": "MIT", "bin": { "lz-string": "bin/bin.js" @@ -24373,6 +25532,16 @@ "license": "ISC", "peer": true }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/markdown-to-jsx": { "version": "7.7.6", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.6.tgz", @@ -24481,6 +25650,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-from-markdown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", @@ -24505,6 +25695,101 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", @@ -25281,6 +26566,213 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, "node_modules/micromark-factory-destination": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", @@ -25324,6 +26816,39 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, "node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", @@ -25525,6 +27050,37 @@ ], "license": "MIT" }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-events-to-acorn/node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", @@ -26005,8 +27561,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/no-case": { "version": "3.0.4", @@ -26544,6 +28099,12 @@ "node": ">= 6" } }, + "node_modules/outvariant": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", + "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", + "license": "MIT" + }, "node_modules/overlap-area": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/overlap-area/-/overlap-area-1.1.0.tgz", @@ -27664,6 +29225,15 @@ "node": ">= 0.8" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/probe-image-size": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/probe-image-size/-/probe-image-size-7.2.3.tgz", @@ -28639,6 +30209,15 @@ } } }, + "node_modules/react-devtools-inline": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz", + "integrity": "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==", + "license": "MIT", + "dependencies": { + "es6-symbol": "^3" + } + }, "node_modules/react-dnd": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", @@ -28793,6 +30372,22 @@ "dev": true, "license": "MIT" }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-fit": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/react-fit/-/react-fit-1.7.1.tgz", @@ -28838,6 +30433,22 @@ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-4.0.3.tgz", "integrity": "sha512-QmpUu4KqDmX0plH4u+tf0riMc1KHE1+lw95cMrLlXQAFOx/xnBtwhZ52XJxd9X2O6kwKBqX32kmhbhlobD0cuw==" }, + "node_modules/react-hook-form": { + "version": "7.59.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.59.0.tgz", + "integrity": "sha512-kmkek2/8grqarTJExFNjy+RXDIP8yM+QTl3QL6m6Q8b2bih4ltmiXxH7T9n+yXNK477xPh5yZT/6vD8sYGzJTA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-hot-toast": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", @@ -32015,6 +33626,18 @@ "node": ">=8" } }, + "node_modules/static-browser-server": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", + "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==", + "license": "Apache-2.0", + "dependencies": { + "@open-draft/deferred-promise": "^2.1.0", + "dotenv": "^16.0.3", + "mime-db": "^1.52.0", + "outvariant": "^1.3.0" + } + }, "node_modules/static-eval": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.1.1.tgz", @@ -32106,6 +33729,12 @@ "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", "license": "MIT" }, + "node_modules/strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -33577,8 +35206,7 @@ "version": "2.7.3", "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/type-check": { "version": "0.4.0", @@ -33865,6 +35493,15 @@ "node": ">=4" } }, + "node_modules/unidiff": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unidiff/-/unidiff-1.0.4.tgz", + "integrity": "sha512-ynU0vsAXw0ir8roa+xPCUHmnJ5goc5BTM2Kuc3IJd8UwgaeRs7VSD5+eeaQL+xp1JtB92hu/Zy/Lgy7RZcr1pQ==", + "license": "MIT", + "dependencies": { + "diff": "^5.1.0" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -33935,6 +35572,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -35329,4 +36979,4 @@ } } } -} \ No newline at end of file +} diff --git a/frontend/package.json b/frontend/package.json index 83239053e8..6a65305bb8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "@dnd-kit/utilities": "^3.2.1", "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", + "@mdxeditor/editor": "^3.38.0", "@microsoft/fetch-event-source": "^2.0.1", "@radix-ui/colors": "^0.1.8", "@radix-ui/react-avatar": "^1.0.4", diff --git a/frontend/src/AppBuilder/AppBuilder.jsx b/frontend/src/AppBuilder/AppBuilder.jsx index fa12e0d1d9..b300329eef 100644 --- a/frontend/src/AppBuilder/AppBuilder.jsx +++ b/frontend/src/AppBuilder/AppBuilder.jsx @@ -17,6 +17,8 @@ import { ModuleProvider } from '@/AppBuilder/_contexts/ModuleContext'; import RightSidebarToggle from '@/AppBuilder/RightSideBar/RightSidebarToggle'; import { shallow } from 'zustand/shallow'; +import ArtifactPreview from './ArtifactPreview'; + // const EditorHeader = lazy(() => import('@/AppBuilder/Header')); // const LeftSidebar = lazy(() => import('@/AppBuilder/LeftSidebar')); // const AppCanvas = lazy(() => import('@/AppBuilder/AppCanvas')); @@ -32,6 +34,9 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod const isModuleEditor = appType === 'module'; const updateIsTJDarkMode = useStore((state) => state.updateIsTJDarkMode, shallow); + const appBuilderMode = useStore((state) => state.appStore.modules[moduleId]?.app?.appBuilderMode ?? 'visual'); + + const isUserInZeroToOneFlow = appBuilderMode === 'ai'; const changeToDarkMode = (newMode) => { updateIsTJDarkMode(newMode); @@ -51,17 +56,29 @@ export const Editor = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod Loading...
}> - - + + + - {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } - - - - - {isRightSidebarOpen && }{' '} - - + + {isUserInZeroToOneFlow ? ( + + ) : ( + <> + {window?.public_config?.ENABLE_MULTIPLAYER_EDITING === 'true' && } + + + + + {isRightSidebarOpen && }{' '} + + + + )}
diff --git a/frontend/src/AppBuilder/ArtifactPreview/index.jsx b/frontend/src/AppBuilder/ArtifactPreview/index.jsx new file mode 100644 index 0000000000..103119d9bc --- /dev/null +++ b/frontend/src/AppBuilder/ArtifactPreview/index.jsx @@ -0,0 +1,9 @@ +import React from 'react'; + +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +function ArtifactPreview() { + return <>; +} + +export default withEditionSpecificComponent(ArtifactPreview, 'AiBuilder'); diff --git a/frontend/src/AppBuilder/CodeEditor/FixWithAi.jsx b/frontend/src/AppBuilder/CodeEditor/FixWithAi.jsx new file mode 100644 index 0000000000..c56c2368e0 --- /dev/null +++ b/frontend/src/AppBuilder/CodeEditor/FixWithAi.jsx @@ -0,0 +1,9 @@ +import React from 'react'; + +import { withEditionSpecificComponent } from '@/modules/common/helpers/withEditionSpecificComponent'; + +function FixWithAi() { + return <>; +} + +export default withEditionSpecificComponent(FixWithAi, 'AiBuilder'); diff --git a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx index ce3c310f24..869ab065f2 100644 --- a/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/MultiLineCodeEditor.jsx @@ -56,6 +56,8 @@ const MultiLineCodeEditor = (props) => { renderCopilot, setCodeEditorView, } = props; + const editorRef = useRef(null); + const replaceIdsWithName = useStore((state) => state.replaceIdsWithName, shallow); const wrapperRef = useRef(null); const getSuggestions = useStore((state) => state.getSuggestions, shallow); @@ -330,6 +332,11 @@ const MultiLineCodeEditor = (props) => { } } + const onAiSuggestionAccept = (newValue) => { + currentValueRef.current = newValue; + onChange(newValue); + }; + return (
{ ref={wrapperRef} >
- + + renderCopilot({ + darkMode, + language: lang, + editorRef, + onAiSuggestionAccept, + }) + } + /> + {
{ const SIZE_LIMIT_KB = 5 * 1024; // 5 KB in bytes @@ -143,7 +147,7 @@ export const PreviewBox = ({ useEffect(() => { if (error) { setErrorStateActive(true); - setErrorMessage(error.message); + setErrorMessage(error); } else { setErrorStateActive(false); setErrorMessage(null); @@ -159,6 +163,8 @@ export const PreviewBox = ({ validationFn ); + const completeErrMessage = Array.isArray(_error) ? _error.join('.') : _error; + const resolvedValue = typeof rawResolvedValue === 'function' ? undefined : rawResolvedValue; const newValue = typeof rawNewValue === 'function' ? undefined : rawNewValue; @@ -185,7 +191,7 @@ export const PreviewBox = ({ setError(null); } else if (!valid && !newValue && !resolvedValue && !isSecretError) { const err = !error ? `Invalid value for ${validationSchema?.schema?.type}` : `${_error}`; - setError({ message: err, value: resolvedValue, type: 'Invalid' }); + setError({ message: err, value: resolvedValue, type: 'Invalid', completeErrorMessage: completeErrMessage }); } else { const jsErrorType = isSecretError ? 'Error' @@ -211,6 +217,7 @@ export const PreviewBox = ({ ? JSON.stringify(errValue, reservedKeywordReplacer) : resolvedValue, type: isSecretError ? 'Error' : jsErrorType, + completeErrorMessage: completeErrMessage, }); setCoersionData(null); } @@ -309,13 +316,118 @@ const PreviewContainer = ({ isPortalOpen, previewRef, showPreview, + onAiSuggestionAccept, ...restProps }) => { - const { validationSchema, isWorkspaceVariable, errorStateActive, previewPlacement, validationFn } = restProps; - const [errorMessage, setErrorMessage] = useState(''); - const typeofError = getCurrentNodeType(errorMessage); - const errorMsg = typeofError === 'Array' ? errorMessage[0] : errorMessage; + const { + validationSchema, + isWorkspaceVariable, + errorStateActive, + previewPlacement, + validationFn, + componentId, + paramName, + fieldMeta, + setIsFocused, + currentValue, + } = restProps; + + const aiFeaturesEnabled = useStore((state) => state.ai?.aiFeaturesEnabled ?? false); + const fetchErrorFixUsingAi = useStore((state) => state.fetchErrorFixUsingAi); + const clearChatHistory = useStore((state) => state.clearChatHistory); + const componentDefinition = useStore((state) => state.getComponentDefinition(componentId), shallow); // TODO: check if moduleId needs to be passed here + + const componentName = componentDefinition?.component?.name; + const componentKey = `${componentName} - ${fieldMeta?.displayName}`; + + const chatList = useStore((state) => state.fixWithAiSlice?.[componentId]?.[componentKey]?.chatHistory ?? []); + + const [errorMessage, setErrorMessage] = useState(null); + + const [popoverToShow, setPopoverToShow] = useState('preview'); // preview | fixWithAI + + const errMsg = errorMessage?.message ?? null; + + const typeofError = getCurrentNodeType(errMsg); + + const errorMsg = typeofError === 'Array' ? errMsg[0] : errMsg; + const darkMode = localStorage.getItem('darkMode') === 'true'; + + useEffect(() => { + !showPreview && setPopoverToShow('preview'); + }, [showPreview]); + + useEffect(() => { + setPopoverToShow('preview'); + + if (chatList?.length) { + clearChatHistory(componentId, componentKey); + } + }, [currentValue]); + + const fetchFixUsingAi = () => { + const defaultValue = validationSchema?.defaultValue + ? validationSchema?.defaultValue + : validationSchema + ? findDefault(validationSchema?.schema ?? {}, errorMessage?.value) + : undefined; + + const errorData = { + key: componentKey, + componentId: componentId, + message: errorMessage?.completeErrorMessage, + error: { + resolvedProperty: { [paramName]: errorMessage?.value }, + effectiveProperty: { [paramName]: defaultValue }, + componentId, + }, + }; + + fetchErrorFixUsingAi(errorData, { + componentDisplayName: + componentDefinition?.component?.displayName ?? componentDefinition?.component?.component ?? componentName, + errorPropertyDisplayName: fieldMeta?.displayName, + customErrMessage: errorMessage?.message, + }); + }; + + const handleFixErrorWithAI = () => { + setPopoverToShow('fixWithAI'); + + if (!componentId || chatList?.length) { + return; + } + + fetchFixUsingAi(); + }; + + const fixWithAIPopover = ( + setCursorInsidePreview(true)} + onMouseLeave={() => setCursorInsidePreview(false)} + > + + setIsFocused(false)} + /> + + + ); + const popover = (
{errorMsg !== 'null' ? errorMsg : 'Invalid'}
+ + {aiFeaturesEnabled && ( + + )}
)} @@ -479,7 +603,7 @@ const PreviewContainer = ({ }, }} > - {(props) => React.cloneElement(popover, props)} + {(props) => React.cloneElement(popoverToShow === 'fixWithAI' ? fixWithAIPopover : popover, props)} )} diff --git a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx index c3d0bb798c..ed07b6da70 100644 --- a/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx +++ b/frontend/src/AppBuilder/CodeEditor/SingleLineCodeEditor.jsx @@ -37,7 +37,7 @@ import Icon from '@/_ui/Icon/solidIcons/index'; const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { const { moduleId } = useModuleContext(); - const { initialValue, onChange, enablePreview = true, portalProps } = restProps; + const { initialValue, onChange, enablePreview = true, portalProps, paramName } = restProps; const { validation = {} } = fieldMeta; const [showPreview, setShowPreview] = useState(false); const [isFocused, setIsFocused] = useState(false); @@ -146,17 +146,24 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r enablePreview={enablePreview} currentValue={currentValue} isFocused={isFocused} + setIsFocused={setIsFocused} setCursorInsidePreview={setCursorInsidePreview} componentName={componentName} validationSchema={validation} setErrorStateActive={setErrorStateActive} ignoreValidation={restProps?.ignoreValidation || isEmpty(validation)} - componentId={restProps?.componentId ?? null} + componentId={componentId ?? null} + fieldMeta={fieldMeta} + paramName={paramName} isWorkspaceVariable={isWorkspaceVariable} errorStateActive={errorStateActive} previewPlacement={restProps?.cyLabel === 'canvas-bg-colour' ? 'top' : 'left-start'} isPortalOpen={restProps?.portalProps?.isOpen} validationFn={validationFn} + onAiSuggestionAccept={(newValue) => { + setCurrentValue(newValue); + onChange(newValue); + }} >
@@ -176,6 +183,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r showPreview={showPreview} wrapperRef={wrapperRef} showSuggestions={showSuggestions} + cursorInsidePreview={cursorInsidePreview} {...restProps} />
@@ -211,6 +219,7 @@ const EditorInput = ({ wrapperRef, showSuggestions, setCodeEditorView = null, // Function to set the CodeMirror view + cursorInsidePreview = false, }) => { const codeHinterContext = useContext(CodeHinterContext); const { suggestionList: paramHints } = createReferencesLookup(codeHinterContext, true); @@ -333,7 +342,8 @@ const EditorInput = ({ }, []); const handleOnBlur = () => { - setShowPreview(false); + !cursorInsidePreview && setShowPreview(false); + if (!delayOnChange) { setFirstTimeFocus(false); return onBlurUpdate(currentValue); diff --git a/frontend/src/AppBuilder/Header/EditorHeader.jsx b/frontend/src/AppBuilder/Header/EditorHeader.jsx index 5e1e639819..e4d5e16850 100644 --- a/frontend/src/AppBuilder/Header/EditorHeader.jsx +++ b/frontend/src/AppBuilder/Header/EditorHeader.jsx @@ -15,13 +15,16 @@ import UpdatePresenceMultiPlayer from './UpdatePresenceMultiPlayer'; import { ModuleEditorBanner } from '@/modules/Modules/components'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -export const EditorHeader = ({ darkMode }) => { +import Steps from './Steps'; + +export const EditorHeader = ({ darkMode, isUserInZeroToOneFlow }) => { const { moduleId, isModuleEditor } = useModuleContext(); - const { isSaving, saveError, isVersionReleased } = useStore( + const { isSaving, saveError, isVersionReleased, aiGenerationMetadata } = useStore( (state) => ({ isSaving: state.appStore.modules[moduleId].app.isSaving, saveError: state.appStore.modules[moduleId].app.saveError, isVersionReleased: state.isVersionReleased, + aiGenerationMetadata: state.appStore.modules[moduleId].app?.aiGenerationMetadata, }), shallow ); @@ -80,44 +83,64 @@ export const EditorHeader = ({ darkMode }) => {
- -
-
- - {getSaveIndicator()} - -
- {shouldEnableMultiplayer && ( -
- -
- )} - {shouldEnableMultiplayer && } -
-
- {!isModuleEditor &&
} -
-
-
- {!isModuleEditor && ( + + {isUserInZeroToOneFlow && ( + ({ label: step.name, value: step.id })) ?? []} + activeStep={aiGenerationMetadata?.active_step} + /> + )} + + {!isUserInZeroToOneFlow && ( <> - - - + +
+
+ + {getSaveIndicator()} + +
+ {shouldEnableMultiplayer && ( +
+ +
+ )} + {shouldEnableMultiplayer && } +
)}
+ {!isModuleEditor && !isUserInZeroToOneFlow &&
}
+ + {!isUserInZeroToOneFlow && ( +
+
+ {!isModuleEditor && ( + <> + + + + + )} +
+
+ )}
- - + + {!isUserInZeroToOneFlow && ( + <> + + + + )}
diff --git a/frontend/src/AppBuilder/Header/Steps.jsx b/frontend/src/AppBuilder/Header/Steps.jsx new file mode 100644 index 0000000000..3abb9a0953 --- /dev/null +++ b/frontend/src/AppBuilder/Header/Steps.jsx @@ -0,0 +1,64 @@ +import React, { Children } from 'react'; + +import { cn } from '@/lib/utils'; +import CheckCircle from '@/_ui/Icon/solidIcons/CheckCircle'; +import SolidArrow from '@/_ui/Icon/solidIcons/SolidArrow'; +import DottedArrow from '@/_ui/Icon/solidIcons/DottedArrow'; + +function Step({ stepNo, label, active, completed }) { + return ( +
+ {completed ? ( + + ) : ( + + {stepNo} + + )} + +

+ {label} +

+
+ ); +} + +function Connector({ completed }) { + if (completed) return ; + + return ; +} + +// sequential steps +export default function Steps({ steps, activeStep }) { + const activeStepIndex = steps.findIndex((step) => step.value === activeStep); + const currentStepIdx = activeStepIndex === -1 ? 0 : activeStepIndex; + + return ( +
+ {Children.toArray( + steps.map((step, index) => { + const isActive = index === currentStepIdx; + const isCompleted = index < currentStepIdx; + + return ( + <> + + + {index < steps.length - 1 && } + + ); + }) + )} +
+ ); +} diff --git a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx index bcd90bee33..6d2a15a501 100644 --- a/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx +++ b/frontend/src/AppBuilder/LeftSidebar/LeftSidebar.jsx @@ -24,6 +24,7 @@ export const BaseLeftSidebar = ({ switchDarkMode, renderAISideBarTrigger = () => null, renderAIChat = () => null, + isUserInZeroToOneFlow, }) => { const { moduleId, isModuleEditor, appType } = useModuleContext(); const [ @@ -72,6 +73,11 @@ export const BaseLeftSidebar = ({ }; useEffect(() => { + if (isUserInZeroToOneFlow) { + setPopoverContentHeight(((window.innerHeight - 48) / window.innerHeight) * 100); + return; + } + if (!isDraggingQueryPane) { setPopoverContentHeight( ((window.innerHeight - (queryPanelHeight == 0 ? 40 : queryPanelHeight) - 45) / window.innerHeight) * 100 @@ -80,7 +86,7 @@ export const BaseLeftSidebar = ({ setPopoverContentHeight(100); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [queryPanelHeight, isDraggingQueryPane]); + }, [isUserInZeroToOneFlow, queryPanelHeight, isDraggingQueryPane]); const renderPopoverContent = () => { if (selectedSidebarItem === null || !isSidebarOpen) return null; @@ -111,7 +117,7 @@ export const BaseLeftSidebar = ({ /> ); case 'tooljetai': - return renderAIChat({ darkMode }); + return renderAIChat({ darkMode, isUserInZeroToOneFlow }); // case 'datasource': // return ( // handleSelectedSidebarItem('settings')} - className={`left-sidebar-item left-sidebar-layout`} - badge={true} - tip="Settings" - ref={setSideBarBtnRefs('settings')} - isModuleEditor={isModuleEditor} - /> + + {!isUserInZeroToOneFlow && ( + <> + {renderCommonItems()} + handleSelectedSidebarItem('settings')} + className={`left-sidebar-item left-sidebar-layout`} + badge={true} + tip="Settings" + ref={setSideBarBtnRefs('settings')} + isModuleEditor={isModuleEditor} + /> + + )} ); }; diff --git a/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx b/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx index f0a3bbe811..ed40086216 100644 --- a/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx +++ b/frontend/src/AppBuilder/QueryManager/Components/DataSourceSelect.jsx @@ -34,7 +34,6 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc const createDataQuery = useStore((state) => state.dataQuery.createDataQuery); const setPreviewData = useStore((state) => state.queryPanel.setPreviewData); const handleChangeDataSource = (source) => { - console.log({ source }); createDataQuery(source); setPreviewData(null); closePopup(); diff --git a/frontend/src/AppBuilder/_hooks/useAppData.js b/frontend/src/AppBuilder/_hooks/useAppData.js index 8e5d8d0246..a7ce7e0e3d 100644 --- a/frontend/src/AppBuilder/_hooks/useAppData.js +++ b/frontend/src/AppBuilder/_hooks/useAppData.js @@ -351,9 +351,24 @@ const useAppData = ( homePageId: homePageId, isPublic: appData.is_public, creationMode: appData.creation_mode, + appGeneratedFromPrompt: appData.app_generated_from_prompt, + aiGenerationMetadata: appData.ai_generation_metadata || {}, + appBuilderMode: appData.app_builder_mode || 'visual', }, moduleId ); + + if (appData.app_builder_mode === 'ai') { + setSelectedSidebarItem('tooljetai'); + toggleLeftSidebar(true); + + // If the app builder mode is AI + // - Do not show zero state - if there is some conversation already done or if route state has prompt + setConversationZeroState( + state?.prompt ? true : Boolean(appData.ai_conversation?.aiConversationMessages?.length) + ); + } + if (!moduleMode) { setIsEditorFreezed(appData.should_freeze_editor); const global_settings = mapKeys( @@ -483,17 +498,14 @@ const useAppData = ( setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))), moduleId); initDependencyGraph(moduleId); setCurrentMode(mode, moduleId); // TODO: set mode based on the slug/appDef + if ( !moduleMode && - state.ai && state?.prompt && initialLoadRef.current && (conversation?.aiConversationMessages || []).length === 0 ) { - setSelectedSidebarItem('tooljetai'); - toggleLeftSidebar('true'); sendMessage(state.prompt); - setConversationZeroState(true); showWalkthrough = false; } // fetchDataSources(appData.editing_version.id, editorEnvironment.id); diff --git a/frontend/src/AppBuilder/_stores/slices/appSlice.js b/frontend/src/AppBuilder/_stores/slices/appSlice.js index 24cd1b80cf..b39fe7a581 100644 --- a/frontend/src/AppBuilder/_stores/slices/appSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/appSlice.js @@ -5,8 +5,8 @@ import DependencyGraph from './DependencyClass'; import { getWorkspaceId } from '@/_helpers/utils'; import { navigate } from '@/AppBuilder/_utils/misc'; import queryString from 'query-string'; -import { replaceEntityReferencesWithIds, baseTheme } from '../utils'; -import _ from 'lodash'; +import { convertKeysToCamelCase, replaceEntityReferencesWithIds, baseTheme } from '../utils'; +import _, { isEmpty } from 'lodash'; const initialState = { isSaving: false, @@ -301,4 +301,36 @@ export const createAppSlice = (set, get) => ({ set((state) => { state.appPermission.selectedUsers = users; }), + + updateAppGenerationMetadata: (dataToUpdate, moduleId = 'canvas') => { + set((state) => { + if (isEmpty(dataToUpdate) || !state.appStore.modules[moduleId].app?.aiGenerationMetadata) return; + + // Any value at the top level of aiGenerationMetadata can be updated using this, for nested keys either send complete data or need to add separate logic to handle it + Object.keys(dataToUpdate).forEach((key) => { + if (dataToUpdate[key] !== undefined) { + state.appStore.modules[moduleId].app.aiGenerationMetadata[key] = dataToUpdate[key]; + } + }); + }); + }, + + updateAppData: (dataToUpdate, moduleId = 'canvas') => { + set((state) => { + state.appStore.modules[moduleId].app = { ...state.appStore.modules[moduleId].app, ...dataToUpdate }; + }); + }, + + updateAppInfoInDB: async (payload, moduleId = 'canvas') => { + const { appId } = get().appStore.modules[moduleId].app; + + if (!appId || isEmpty(payload)) return; + + try { + await appsService.saveApp(appId, payload); + get().updateAppData(convertKeysToCamelCase(payload), moduleId); + } catch (error) { + console.log(error); + } + }, }); diff --git a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js index c3efb5cd36..b096d8d920 100644 --- a/frontend/src/AppBuilder/_stores/slices/componentsSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/componentsSlice.js @@ -2057,6 +2057,7 @@ export const createComponentsSlice = (set, get) => ({ validation: componentDefinition.component.definition?.validation, }, name: componentName, + displayName: componentDefinition.component.displayName, parent: componentDefinition.component.parent, }, layouts: componentDefinition.layouts, diff --git a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js index 34bea1bf84..cdd9291aa5 100644 --- a/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js +++ b/frontend/src/AppBuilder/_stores/slices/dataQuerySlice.js @@ -57,10 +57,13 @@ export const createDataQuerySlice = (set, get) => ({ ); }); }, - createDataQuery: (selectedDataSource, shouldRunQuery, customOptions = {}, moduleId = 'canvas') => { + createDataQuery: (selectedDataSource, shouldRunQuery, customOptions = {}, moduleId = 'canvas', queryName) => { + let name; const appVersionId = get().currentVersionId; const appId = get().appStore.modules[moduleId].app.appId; - const { options: defaultOptions, name } = getDefaultOptions(selectedDataSource); + const { options: defaultOptions, name: nameFromDefaultOptions } = getDefaultOptions(selectedDataSource); + if (!queryName) name = nameFromDefaultOptions; + else name = queryName; const options = { ...defaultOptions, ...customOptions }; const kind = selectedDataSource.kind; const tempId = uuidv4(); @@ -77,6 +80,7 @@ export const createDataQuerySlice = (set, get) => ({ setIsAppSaving(true); const dataQueries = get().dataQuery.queries.modules[moduleId]; const currDataQueries = [...dataQueries]; + const runOnCreate = options.runOnCreate; set((state) => { state.dataQuery.queries.modules[moduleId] = [ { @@ -111,7 +115,7 @@ export const createDataQuerySlice = (set, get) => ({ return query; }); }); - setSelectedQuery(data.id, moduleId); + setSelectedQuery(data.id, data); if (shouldRunQuery) setQueryToBeRun(data); /** Checks if there is an API call cached. If yes execute it */ @@ -141,6 +145,10 @@ export const createDataQuerySlice = (set, get) => ({ }, moduleId ); + + if (runOnCreate) { + get().queryPanel.runQuery(data.id, data.name, undefined, undefined, {}, true, false, moduleId); + } }) .catch((error) => { set((state) => { diff --git a/frontend/src/AppBuilder/_stores/slices/fixWithAi.js b/frontend/src/AppBuilder/_stores/slices/fixWithAi.js new file mode 100644 index 0000000000..0030d57f6b --- /dev/null +++ b/frontend/src/AppBuilder/_stores/slices/fixWithAi.js @@ -0,0 +1,5 @@ +import { getEditionSpecificSlice } from '../../../modules/common/helpers/getEditionSpecificSlice'; + +const createFixWithAiSlice = getEditionSpecificSlice('createFixWithAiSlice'); + +export { createFixWithAiSlice }; diff --git a/frontend/src/AppBuilder/_stores/store.js b/frontend/src/AppBuilder/_stores/store.js index 02d97051e6..c094d33105 100644 --- a/frontend/src/AppBuilder/_stores/store.js +++ b/frontend/src/AppBuilder/_stores/store.js @@ -27,6 +27,7 @@ import { createCodeHinterSlice } from './slices/codeHinterSlice'; import { createDebuggerSlice } from './slices/debuggerSlice'; import { createGitSyncSlice } from './slices/gitSyncSlice'; import { createAiSlice } from './slices/aiSlice'; +import { createFixWithAiSlice } from './slices/fixWithAi'; import { createWhiteLabellingSlice } from './slices/whiteLabellingSlice'; import { createFormComponentSlice } from './slices/formComponentSlice'; import { createInspectorSlice } from './slices/inspectorSlice'; @@ -62,6 +63,7 @@ export default create( ...createDebuggerSlice(...state), ...createGitSyncSlice(...state), ...createAiSlice(...state), + ...createFixWithAiSlice(...state), ...createWhiteLabellingSlice(...state), ...createFormComponentSlice(...state), ...createInspectorSlice(...state), diff --git a/frontend/src/AppBuilder/_stores/utils.js b/frontend/src/AppBuilder/_stores/utils.js index 7c6e83928b..c96521a099 100644 --- a/frontend/src/AppBuilder/_stores/utils.js +++ b/frontend/src/AppBuilder/_stores/utils.js @@ -578,6 +578,16 @@ export function convertAllKeysToSnakeCase(o) { return o; } +export function convertKeysToCamelCase(object) { + if (_.isEmpty(object)) return null; + + return Object.keys(object).reduce((acc, key) => { + acc[_.camelCase(key)] = object[key]; + + return acc; + }, {}); +} + // export function createReferencesLookup(refState, forQueryParams = false, initalLoad = false) { // if (forQueryParams && _.isEmpty(refState['parameters'])) { // return { suggestionList: [] }; diff --git a/frontend/src/AppBuilder/_utils/component-properties-validation.js b/frontend/src/AppBuilder/_utils/component-properties-validation.js index a85319dfdc..126e4aba80 100644 --- a/frontend/src/AppBuilder/_utils/component-properties-validation.js +++ b/frontend/src/AppBuilder/_utils/component-properties-validation.js @@ -178,7 +178,7 @@ export const validateProperty = (resolvedProperty, propertyDefinitions, paramNam return [_valid, errors, newValue]; }; -function findDefault(definition, value) { +export function findDefault(definition, value) { switch (definition.type) { case 'string': return ''; diff --git a/frontend/src/HomePage/HomePage.jsx b/frontend/src/HomePage/HomePage.jsx index 55d6bd42df..8f387a1650 100644 --- a/frontend/src/HomePage/HomePage.jsx +++ b/frontend/src/HomePage/HomePage.jsx @@ -12,7 +12,7 @@ import { } from '@/_services'; import { ConfirmDialog, AppModal, ToolTip } from '@/_components'; import Select from '@/_ui/Select'; -import _, { sample, isEmpty } from 'lodash'; +import _, { sample, isEmpty, capitalize } from 'lodash'; import { Folders } from './Folders'; import { BlankPage } from './BlankPage'; import { toast } from 'react-hot-toast'; @@ -259,19 +259,21 @@ class HomePageComponent extends React.Component { return this.props.appType === 'module' ? 'Module' : this.props.appType === 'workflow' ? 'Workflow' : 'App'; }; - createApp = async (appName) => { + createApp = async (appName, type, prompt) => { let _self = this; _self.setState({ creatingApp: true }); - try { const data = await appsService.createApp({ icon: sample(iconList), name: appName, type: this.props.appType, + prompt, }); const workspaceId = getWorkspaceId(); - _self.props.navigate(`/${workspaceId}/apps/${data.id}`, { state: { commitEnabled: this.state.commitEnabled } }); - toast.success(`${this.getAppType()} created successfully!`); + _self.props.navigate(`/${workspaceId}/apps/${data.id}`, { + state: { commitEnabled: this.state.commitEnabled, prompt }, + }); + this.props.appType !== 'front-end' && toast.success(`${capitalize(this.getAppType())} created successfully!`); _self.setState({ creatingApp: false }); return true; } catch (errorResponse) { diff --git a/frontend/src/_components/CopyToClipboard/CopyToClipboard.jsx b/frontend/src/_components/CopyToClipboard/CopyToClipboard.jsx index c700ec5d29..0aa8f21cea 100644 --- a/frontend/src/_components/CopyToClipboard/CopyToClipboard.jsx +++ b/frontend/src/_components/CopyToClipboard/CopyToClipboard.jsx @@ -3,7 +3,7 @@ import { CopyToClipboard } from 'react-copy-to-clipboard'; import { toast } from 'react-hot-toast'; import { ToolTip } from '@/_components/ToolTip'; -export const CopyToClipboardComponent = ({ data, callback, useCopyIcon }) => { +export const CopyToClipboardComponent = ({ children, data, callback, useCopyIcon }) => { const [copied, setCopied] = React.useState(false); const dataToCopy = callback(data); const message = 'Copied to clipboard'; @@ -30,12 +30,14 @@ export const CopyToClipboardComponent = ({ data, callback, useCopyIcon }) => { toast.success(message, { position: 'top-center' }); }} > - - {useCopyIcon ? : } - + {children ?? ( + + {useCopyIcon ? : } + + )} ); diff --git a/frontend/src/_services/ai.service.js b/frontend/src/_services/ai.service.js index 918d58c8fa..94f273d27d 100644 --- a/frontend/src/_services/ai.service.js +++ b/frontend/src/_services/ai.service.js @@ -21,8 +21,15 @@ export const aiService = { approvePrd, getCopilotSuggestion, getCreditBalance, + fixWithAI, + fixLayout, }; +async function fixLayout(body) { + const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; + return fetch(`${config.apiUrl}/ai/fixLayout`, requestOptions).then(handleResponse); +} + function enrichPrompt(prompt) { const body = { prompt, @@ -225,10 +232,15 @@ async function approvePrd(body, onMessage) { async function getCopilotSuggestion(body) { const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; - return fetch(`${config.apiUrl}/agents/copilot`, requestOptions).then(handleResponse); + return fetch(`${config.apiUrl}/ai/copilot`, requestOptions).then(handleResponse); } async function getCreditBalance() { const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' }; return fetch(`${config.apiUrl}/ai/get-credits-balance`, requestOptions).then(handleResponse); } + +async function fixWithAI(body) { + const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; + return fetch(`${config.apiUrl}/ai/fix-with-ai`, requestOptions).then(handleResponse); +} diff --git a/frontend/src/_styles/componentdesign.scss b/frontend/src/_styles/componentdesign.scss index 52278e374b..b5879cd2f9 100644 --- a/frontend/src/_styles/componentdesign.scss +++ b/frontend/src/_styles/componentdesign.scss @@ -129,7 +129,7 @@ --background-warning-weak: #301100; --background-error-strong: #D03F43; --background-error-weak: #390809; - --background-Inverse: #FAFCFF; + --background-inverse: #FAFCFF; //text diff --git a/frontend/src/_styles/popover.scss b/frontend/src/_styles/popover.scss index 7f7c4385bf..d82d25ee21 100644 --- a/frontend/src/_styles/popover.scss +++ b/frontend/src/_styles/popover.scss @@ -12,6 +12,7 @@ &.tooljetai { width: 440px; + overflow: auto !important; } select { diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss index 1e77060ba1..d3b556bba8 100644 --- a/frontend/src/_styles/theme.scss +++ b/frontend/src/_styles/theme.scss @@ -207,6 +207,30 @@ } } +@layer utilities { + .tw-hide-scrollbar { + // scrollbar-color: transparent transparent; + + &::-webkit-scrollbar-thumb { + background: transparent; + } + &::-webkit-scrollbar-track { + background: transparent; + } + } + + .tw-show-scrollbar { + // scrollbar-color: var(--slate8); + + &::-webkit-scrollbar-thumb { + background: var(--slate8); + } + &::-webkit-scrollbar-track { + background: transparent; + } + } +} + // variables $border-radius: 4px; @@ -16847,65 +16871,6 @@ fieldset:disabled { } - - div.actions { - margin-top: 12px; - display: flex; - align-items: center; - justify-content: space-between; - - .left { - display: flex; - } - - .left button { - background: red; - height: 28px; - width: 28px; - background-color: transparent !important; - border: none; - outline: none; - display: flex; - align-items: center; - - &:hover { - background-color: #f1f1f1 !important; - } - - // &:focus { - // border: 1px solid var(--border-default, #CCD1D5); - // background: var(--button-outline, #FFF); - // box-shadow: 0px 0px 0px 2px var(--Interactive-focusActive, #4368E3); - // } - - } - - button.submit { - height: 28px; - width: 28px; - border-radius: 6px; - border: 1px solid var(--border-weak, #E4E7EB); - background: var(--button-secondary, #FFF); - - /* Elevations/100 */ - box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10)); - display: flex; - align-items: center; - justify-content: center; - - &:hover { - border: 1px solid var(--border-default, #CCD1D5); - background: var(--button-outline-hover, rgba(136, 144, 153, 0.12)); - - } - - // &:focus { - // border: 1px solid var(--border-default, #CCD1D5); - // background: var(--button-outline, #FFF); - // box-shadow: 0px 0px 0px 2px var(--Interactive-focusActive, #4368E3); - // } - } - } } .example-prompts { @@ -16922,47 +16887,10 @@ fieldset:disabled { font-weight: 400; line-height: 18px; } - - .right { - display: flex; - gap: 8px; - - button { - border: none; - outline: none; - display: flex; - gap: 6px; - align-items: center; - padding: 5px 10px; - background: transparent; - border-radius: 6px; - border: 1px solid var(--border-weak, #E4E7EB); - background: var(--button-secondary, #FFF); - box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10)); - - span { - color: var(--text-default, #1B1F24); - - /* base/medium */ - font-family: "IBM Plex Sans"; - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 18px; - /* 150% */ - } - - &:hover { - border: 1px solid var(--border-default, #CCD1D5); - background: linear-gradient(0deg, var(--button-outline-hover, rgba(136, 144, 153, 0.12)) 0%, var(--button-outline-hover, rgba(136, 144, 153, 0.12)) 100%), var(--button-outline, #FFF); - } - } - } } } section.ai-message-prompt-input-wrapper { - border-radius: 6px; // border: 1px solid var(--border-accent-strong, #4368E3); background: var(--background-surface-layer-01, #FFFFFF); @@ -16970,10 +16898,8 @@ section.ai-message-prompt-input-wrapper { padding: 12px; &.inside-appbuilder { - border: none; - background: var(--slate2); + border: 1px solid var(--border-weak, #4368E3); margin: 16px; - border-radius: 6px; /* Elevations/300 */ box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 4px 8px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10)); @@ -17013,71 +16939,6 @@ section.ai-message-prompt-input-wrapper { .input:not(:empty)::before { display: none; } - - div.actions { - margin-top: 12px; - display: flex; - align-items: center; - justify-content: space-between; - - .left { - display: flex; - } - - .left button { - background: red; - height: 28px; - width: 28px; - background-color: transparent !important; - border: none; - outline: none; - display: flex; - align-items: center; - - &:hover { - background-color: #f1f1f1 !important; - } - - // &:focus { - // border: 1px solid var(--border-default, #CCD1D5); - // background: var(--button-outline, #FFF); - // box-shadow: 0px 0px 0px 2px var(--Interactive-focusActive, #4368E3); - // } - - } - - button.submit { - height: 28px; - width: 28px; - border-radius: 6px; - border: 1px solid var(--border-weak, #E4E7EB); - background: var(--button-secondary, #FFF); - - /* Elevations/100 */ - box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10)); - display: flex; - align-items: center; - justify-content: center; - - &:hover { - border: 1px solid var(--border-default, #CCD1D5); - background: var(--button-outline-hover, rgba(136, 144, 153, 0.12)); - } - - // when disabled - &:disabled { - border-radius: 6px; - border: 1px solid var(--border-weak, #E4E7EB); - background: linear-gradient(0deg, var(--button-outline-disabled, #FFF) 0%, var(--button-outline-disabled, #FFF) 100%), var(--button-secondary, #FFF); - } - - // &:focus { - // border: 1px solid var(--border-default, #CCD1D5); - // background: var(--button-outline, #FFF); - // box-shadow: 0px 0px 0px 2px var(--Interactive-focusActive, #4368E3); - // } - } - } } @@ -17597,12 +17458,6 @@ section.ai-message-prompt-input-wrapper { margin-top: 6px; } - button { - border: none; - outline: none; - } - - &.dark-theme { background-color: #1f2936; @@ -17619,6 +17474,7 @@ section.ai-message-prompt-input-wrapper { justify-content: center; background-color: transparent; margin-right: 5px; + border: none; svg { transition: ease-in-out 0.2s; @@ -17647,42 +17503,10 @@ section.ai-message-prompt-input-wrapper { z-index: 2; background-color: #fff; - padding: 8px 16px 0 16px; + padding: 0.5rem 1rem; border-bottom: 1px solid var(--border-weak, #E4E7EB); - .conversation-type-toggle { - margin-top: 4px; - display: flex; - align-items: flex-start; - gap: 4px; - - button { - padding: 6px 7px; - color: var(--text-default, #1B1F24); - background: transparent; - position: relative; - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 18px; - - /* 150% */ - &.active { - &::after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 2px; - background-color: var(--primary-accent-strong); - } - - } - } - } - section { display: flex; align-items: center; @@ -17696,10 +17520,6 @@ section.ai-message-prompt-input-wrapper { font-weight: 500; line-height: 20px; margin-bottom: 0; - - .highlight { - color: #FF5F6D - } } button { @@ -17728,8 +17548,9 @@ section.ai-message-prompt-input-wrapper { .conversation-wrapper { overflow-y: auto; flex-grow: 1; - padding: 32px; + padding: 1rem; z-index: 2; + scroll-behavior: smooth; // remote the scrollbar &::-webkit-scrollbar { @@ -17989,6 +17810,7 @@ section.ai-message-prompt-input-wrapper { border-radius: 5px; margin-top: 2px; align-self: flex-start; + border: none; &.selected { background: var(--button-primary, #4368E3); @@ -18192,59 +18014,6 @@ section.ai-message-prompt-input-wrapper { } } - .action-buttons { - margin-top: 8px; - display: flex; - align-items: center; - justify-content: center; - gap: 4px; - - button { - border: none; - outline: none; - background-color: transparent; - } - - .secondary-btn { - padding: 5px 10px; - display: flex; - align-items: center; - gap: 6px; - - span { - color: var(--text-default, #1B1F24); - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 18px; - } - - &:hover { - background: var(--interactive-hover, rgba(136, 144, 153, 0.12)); - } - } - - .primary-btn { - padding: 5px 10px; - display: flex; - align-items: center; - gap: 6px; - color: var(--text-default, #1B1F24); - font-size: 12px; - font-style: normal; - font-weight: 500; - line-height: 18px; - border-radius: 6px; - border: 1px solid var(--border-weak, #E4E7EB); - background: var(--button-secondary, #FFF); - box-shadow: 0px 0px 1px 0px var(--dropshadow-100700-layer-1, rgba(48, 50, 51, 0.05)), 0px 1px 1px 0px var(--dropshadow-100400-layer-2, rgba(48, 50, 51, 0.10)); - - &:hover { - background: var(--interactive-hover, rgba(136, 144, 153, 0.12)); - } - } - } - .options { display: flex; padding: 4px; @@ -18297,6 +18066,8 @@ section.ai-message-prompt-input-wrapper { &.user { display: flex; + max-width: 90%; + margin-left: auto; /* 150% */ .user-message { diff --git a/frontend/src/_ui/Icon/solidIcons/AddApp.jsx b/frontend/src/_ui/Icon/solidIcons/AddApp.jsx new file mode 100644 index 0000000000..b76c1c7a83 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/AddApp.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export default function AddAppIcon({ width = 15, height = 15, fill = '#ACB2B9' }) { + return ( + + + + ); +} diff --git a/frontend/src/_ui/Icon/solidIcons/Bug.jsx b/frontend/src/_ui/Icon/solidIcons/Bug.jsx index 291a6b1212..05e31f9389 100644 --- a/frontend/src/_ui/Icon/solidIcons/Bug.jsx +++ b/frontend/src/_ui/Icon/solidIcons/Bug.jsx @@ -1,14 +1,21 @@ import React from 'react'; -const Bug = ({ fill = '#000', width = '25', className = '', viewBox = '0 0 25 25' }) => ( +const Bug = ({ fill = '#ACB2B9', width = '14', height = '14', className = '' }) => ( + width={width} + height={height} + viewBox="0 0 14 14" + fill="none" + className={className} + > + + ); export default Bug; diff --git a/frontend/src/_ui/Icon/solidIcons/Bulb.jsx b/frontend/src/_ui/Icon/solidIcons/Bulb.jsx new file mode 100644 index 0000000000..3b7baac142 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/Bulb.jsx @@ -0,0 +1,16 @@ +import React from 'react'; + +export default function BulbIcon({ width = 15, height = 14, fill = '#6A727C' }) { + return ( + + + + + ); +} diff --git a/frontend/src/_ui/Icon/solidIcons/CheckCircle.jsx b/frontend/src/_ui/Icon/solidIcons/CheckCircle.jsx new file mode 100644 index 0000000000..ead3e47f70 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/CheckCircle.jsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export default function CheckCircle({ width = '14', height = '14', fill = '#1E823B' }) { + return ( + + + + ); +} diff --git a/frontend/src/_ui/Icon/solidIcons/DottedArrow.jsx b/frontend/src/_ui/Icon/solidIcons/DottedArrow.jsx new file mode 100644 index 0000000000..a83f168262 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/DottedArrow.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export default function DottedArrow({ width = '37', height = '8', fill = '#ACB2B9' }) { + return ( + + + + ); +} diff --git a/frontend/src/_ui/Icon/solidIcons/LoadingCircle.jsx b/frontend/src/_ui/Icon/solidIcons/LoadingCircle.jsx new file mode 100644 index 0000000000..29a730ce11 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/LoadingCircle.jsx @@ -0,0 +1,24 @@ +import React from 'react'; + +export default function LoadingCircleIcon({ + className, + width = '13', + height = '13', + fill = 'var(--icon-strong, #6A727C)', +}) { + return ( + + + + ); +} diff --git a/frontend/src/_ui/Icon/solidIcons/PageIcon.jsx b/frontend/src/_ui/Icon/solidIcons/PageIcon.jsx new file mode 100644 index 0000000000..786c193514 --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/PageIcon.jsx @@ -0,0 +1,18 @@ +import React from 'react'; + +export default function PageIcon({ width = 15, height = 15, fill = '#ACB2B9' }) { + return ( + + + + + ); +} diff --git a/frontend/src/_ui/Icon/solidIcons/Retry.jsx b/frontend/src/_ui/Icon/solidIcons/Retry.jsx index efaae36bbd..72b2898fcd 100644 --- a/frontend/src/_ui/Icon/solidIcons/Retry.jsx +++ b/frontend/src/_ui/Icon/solidIcons/Retry.jsx @@ -1,13 +1,13 @@ import React from 'react'; -const Retry = () => { +const Retry = ({ fill = '#ACB2B9' }) => { return ( ); diff --git a/frontend/src/_ui/Icon/solidIcons/SolidArrow.jsx b/frontend/src/_ui/Icon/solidIcons/SolidArrow.jsx new file mode 100644 index 0000000000..e01b35d1ad --- /dev/null +++ b/frontend/src/_ui/Icon/solidIcons/SolidArrow.jsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export default function SolidArrow({ width = '37', height = '8', fill = 'black' }) { + return ( + + + + ); +} diff --git a/frontend/src/_ui/Icon/solidIcons/index.js b/frontend/src/_ui/Icon/solidIcons/index.js index 9f0254d79a..6e0f40c9c9 100644 --- a/frontend/src/_ui/Icon/solidIcons/index.js +++ b/frontend/src/_ui/Icon/solidIcons/index.js @@ -1,5 +1,6 @@ import React from 'react'; import Apps from './Apps.jsx'; +import AddAppIcon from './AddApp.jsx'; import Archive from './Archive.jsx'; import ArrowBack from './ArrowBack.jsx'; import ArrowDown from './ArrowDown.jsx'; @@ -11,8 +12,10 @@ import ArrowSortRectangle from './ArrowSortRectangle.jsx'; import AddNavItemURL from './AddNavItemURL.jsx'; import ArrowTransfer from './ArrowTransfer.jsx'; import ArrowUp from './ArrowUp.jsx'; +import BulbIcon from './Bulb.jsx'; import BookSearch from './BookSearch.jsx'; import Branch from './Branch.jsx'; +import Bug from './Bug.jsx'; import Debugger from './Debugger.jsx'; import Calender from './Calender.jsx'; import CheckRectangle from './CheckRectangle.jsx'; @@ -104,6 +107,7 @@ import RemoveRectangle from './RemoveRectangle.jsx'; import RightArrow from './RightArrow.jsx'; import RightOuterJoin from './RightOuterJoin.jsx'; import Row from './Row.jsx'; +import Retry from './Retry.jsx'; import SadRectangle from './SadRectangle.jsx'; import Search from './Search.jsx'; import SearchMinus from './SearchMinus.jsx'; @@ -219,6 +223,7 @@ import SectionExpand from './SectionExpand.jsx'; import Reset from './Reset.jsx'; import Outbound from './Outbound.jsx'; import AddPageGroupIcon from './AddPageGroup.jsx'; +import PageIcon from './PageIcon.jsx'; import EnterpriseNew from './EnterpriseNew.jsx'; import ArrowReturn01 from './ArrowReturn01.jsx'; import ArrowUp01 from './ArrowUp01.jsx'; @@ -286,6 +291,8 @@ const Icon = (props) => { return ; case 'apps': return ; + case 'add-app': + return ; case 'archive': return ; case 'arrowback': @@ -316,10 +323,14 @@ const Icon = (props) => { return ; case 'auditlogs': return ; + case 'bulb': + return ; case 'booksearch': return ; case 'branch': return ; + case 'bug': + return ; case 'debugger': return ; case 'calender': @@ -516,6 +527,8 @@ const Icon = (props) => { return ; case 'pageAdd': return ; + case 'page-icon': + return ; case 'pageUpload': return ; case 'pin': @@ -556,6 +569,8 @@ const Icon = (props) => { return ; case 'reset': return ; + case 'retry': + return ; case 'sadrectangle': return ; case 'search': diff --git a/frontend/src/lib/utils.js b/frontend/src/lib/utils.js index 6f111fecb7..0573925c97 100644 --- a/frontend/src/lib/utils.js +++ b/frontend/src/lib/utils.js @@ -1,5 +1,9 @@ import cx from 'classnames'; -import { twMerge } from 'tailwind-merge'; +import { extendTailwindMerge } from 'tailwind-merge'; + +const twMerge = extendTailwindMerge({ + prefix: 'tw-', +}); export function cn(...inputs) { return twMerge(cx(inputs)); diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 21ab8a8727..902f7d1aa2 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,7 +1,13 @@ /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ['class'], - content: ['./pages/**/*.{js,jsx}', './components/**/*.{js,jsx}', './app/**/*.{js,jsx}', './src/**/*.{js,jsx}'], + content: [ + './pages/**/*.{js,jsx}', + './components/**/*.{js,jsx}', + './app/**/*.{js,jsx}', + './src/**/*.{js,jsx}', + './ee/**/*.{js,jsx}', + ], prefix: 'tw-', corePlugins: { preflight: false, @@ -22,7 +28,7 @@ module.exports = { 'background-error-weak': 'var(--background-error-weak)', 'background-warning-stong': 'var(--background-warning-stong)', 'background-warning-weak': 'var(--background-warning-weak)', - 'background-inverse': 'var(--background-Inverse)', + 'background-inverse': 'var(--background-inverse)', 'text-default': 'var(--text-default)', 'text-medium': 'var(--text-medium)', 'text-placeholder': 'var(--text-placeholder)', diff --git a/server/data-migrations/1750927057649-AddAiGenerationFlagsInApp.ts b/server/data-migrations/1750927057649-AddAiGenerationFlagsInApp.ts new file mode 100644 index 0000000000..3205c9979b --- /dev/null +++ b/server/data-migrations/1750927057649-AddAiGenerationFlagsInApp.ts @@ -0,0 +1,62 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class AddAiGenerationFlagsInApp1750927057649 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + // Add is_initialised_from_prompt column + await queryRunner.addColumn( + 'apps', + new TableColumn({ + name: 'is_initialised_from_prompt', + type: 'boolean', + default: false, + isNullable: false, + }) + ); + + // Add app_generated_from_prompt column + await queryRunner.addColumn( + 'apps', + new TableColumn({ + name: 'app_generated_from_prompt', + type: 'boolean', + default: false, + isNullable: false, + }) + ); + + // Add ai_generation_metadata column + await queryRunner.addColumn( + 'apps', + new TableColumn({ + name: 'ai_generation_metadata', + type: 'jsonb', + isNullable: true, + }) + ); + + // Create app_builder_mode enum type + await queryRunner.query(`CREATE TYPE "app_builder_mode" AS ENUM ('ai', 'visual')`); + + // Add app_builder_mode column + await queryRunner.addColumn( + 'apps', + new TableColumn({ + name: 'app_builder_mode', + type: 'app_builder_mode', + default: "'visual'", + isNullable: false, + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + // Remove columns in reverse order + await queryRunner.dropColumn('apps', 'app_builder_mode'); + await queryRunner.query('DROP TYPE "app_builder_mode"'); + await queryRunner.dropColumn('apps', 'ai_generation_metadata'); + await queryRunner.dropColumn('apps', 'app_generated_from_prompt'); + await queryRunner.dropColumn('apps', 'is_initialised_from_prompt'); + } + +} diff --git a/server/data-migrations/1750927083207-AddArtifactsTable.ts b/server/data-migrations/1750927083207-AddArtifactsTable.ts new file mode 100644 index 0000000000..078c3412aa --- /dev/null +++ b/server/data-migrations/1750927083207-AddArtifactsTable.ts @@ -0,0 +1,81 @@ +import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm"; + +export class AddArtifactsTable1750927083207 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.createTable( + new Table({ + name: 'artifacts', + columns: [ + { + name: 'id', + type: 'uuid', + isPrimary: true, + isGenerated: true, + generationStrategy: 'uuid', + }, + { + name: 'conversation_id', + type: 'uuid', + isNullable: false, + }, + { + name: 'message_id', + type: 'uuid', + isNullable: false, + }, + { + name: 'content', + type: 'jsonb', + isNullable: false, + }, + { + name: 'identifier', + type: 'varchar', + isNullable: false, + }, + { + name: 'created_at', + type: 'timestamp', + default: 'now()', + }, + { + name: 'updated_at', + type: 'timestamp', + default: 'now()', + }, + ], + }), + true, + ); + + await queryRunner.createForeignKey( + 'artifacts', + new TableForeignKey({ + columnNames: ['conversation_id'], + referencedColumnNames: ['id'], + referencedTableName: 'ai_conversations', + onDelete: 'CASCADE', + }), + ); + + await queryRunner.createForeignKey( + 'artifacts', + new TableForeignKey({ + columnNames: ['message_id'], + referencedColumnNames: ['id'], + referencedTableName: 'ai_conversation_messages', + onDelete: 'CASCADE', + }), + ); + } + + public async down(queryRunner: QueryRunner): Promise { + const table = await queryRunner.getTable('artifacts'); + if (table) { + const foreignKeys = table.foreignKeys; + await Promise.all(foreignKeys.map(foreignKey => queryRunner.dropForeignKey('artifacts', foreignKey))); + } + await queryRunner.dropTable('artifacts'); + } +} diff --git a/server/ee b/server/ee index cc864000dd..dcb4173b2d 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit cc864000dd03cc345e53ae9fc43821d3174f4c64 +Subproject commit dcb4173b2db2a6ee3d96b21e0b32f2f0b0f2c4b0 diff --git a/server/package-lock.json b/server/package-lock.json index e043463804..874f55d288 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -62,11 +62,13 @@ "isolated-vm": "^5.0.4", "joi": "^17.4.1", "js-base64": "^3.7.2", + "jsonrepair": "^3.12.0", "jszip": "^3.10.1", "ldapjs": "^3.0.7", "lodash": "^4.17.21", "module-from-string": "^3.3.1", "moment": "^2.29.4", + "neo4j-driver": "^5.28.1", "nest-winston": "^1.9.4", "nestjs-pino": "^1.4.0", "node-sql-parser": "^5.3.1", @@ -14281,6 +14283,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonrepair": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.12.0.tgz", + "integrity": "sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w==", + "license": "ISC", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -15566,6 +15577,67 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, + "node_modules/neo4j-driver": { + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/neo4j-driver/-/neo4j-driver-5.28.1.tgz", + "integrity": "sha512-jbyBwyM0a3RLGcP43q3hIxPUPxA+1bE04RovOKdNAS42EtBMVCKcPSeOvWiHxgXp1ZFd0a8XqK+7LtguInOLUg==", + "license": "Apache-2.0", + "dependencies": { + "neo4j-driver-bolt-connection": "5.28.1", + "neo4j-driver-core": "5.28.1", + "rxjs": "^7.8.1" + } + }, + "node_modules/neo4j-driver-bolt-connection": { + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/neo4j-driver-bolt-connection/-/neo4j-driver-bolt-connection-5.28.1.tgz", + "integrity": "sha512-nY8GBhjOW7J0rDtpiyJn6kFdk2OiNVZZhZrO8//mwNXnf5VQJ6HqZQTDthH/9pEaX0Jvbastz1xU7ZL8xzqY0w==", + "license": "Apache-2.0", + "dependencies": { + "buffer": "^6.0.3", + "neo4j-driver-core": "5.28.1", + "string_decoder": "^1.3.0" + } + }, + "node_modules/neo4j-driver-bolt-connection/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/neo4j-driver-bolt-connection/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/neo4j-driver-core": { + "version": "5.28.1", + "resolved": "https://registry.npmjs.org/neo4j-driver-core/-/neo4j-driver-core-5.28.1.tgz", + "integrity": "sha512-14vN8TlxC0JvJYfjWic5PwjsZ38loQLOKFTXwk4fWLTbCk6VhrhubB2Jsy9Rz+gM6PtTor4+6ClBEFDp1q/c8g==", + "license": "Apache-2.0" + }, "node_modules/nest-winston": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/nest-winston/-/nest-winston-1.10.2.tgz", diff --git a/server/package.json b/server/package.json index e0941f0b75..3a3d98e980 100644 --- a/server/package.json +++ b/server/package.json @@ -100,11 +100,13 @@ "isolated-vm": "^5.0.4", "joi": "^17.4.1", "js-base64": "^3.7.2", + "jsonrepair": "^3.12.0", "jszip": "^3.10.1", "ldapjs": "^3.0.7", "lodash": "^4.17.21", "module-from-string": "^3.3.1", "moment": "^2.29.4", + "neo4j-driver": "^5.28.1", "nest-winston": "^1.9.4", "nestjs-pino": "^1.4.0", "node-sql-parser": "^5.3.1", diff --git a/server/src/entities/ai_conversation_message.entity.ts b/server/src/entities/ai_conversation_message.entity.ts index 856adb8727..d58b2d53b6 100644 --- a/server/src/entities/ai_conversation_message.entity.ts +++ b/server/src/entities/ai_conversation_message.entity.ts @@ -7,9 +7,11 @@ import { ManyToOne, JoinColumn, OneToOne, + OneToMany, } from 'typeorm'; import { AiConversation } from '@entities/ai_conversation.entity'; import { AiResponseVote } from '@entities/ai_response_vote.entity'; +import { Artifact } from './artifact.entity'; @Entity('ai_conversation_messages') export class AiConversationMessage { @@ -64,4 +66,7 @@ export class AiConversationMessage { @OneToOne(() => AiResponseVote, (aiResponseVote) => aiResponseVote.aiConversationMessage) aiResponseVote: AiResponseVote; + + @OneToMany(() => Artifact, (artifact) => artifact.message) + artifacts: Artifact[]; } diff --git a/server/src/entities/app.entity.ts b/server/src/entities/app.entity.ts index 7449af495c..49862a332a 100644 --- a/server/src/entities/app.entity.ts +++ b/server/src/entities/app.entity.ts @@ -69,6 +69,36 @@ export class App extends BaseEntity { @Column({ name: 'workflow_enabled', default: false }) workflowEnabled: boolean; + + @Column({ name: 'is_initialised_from_prompt', default: false }) + isInitialisedFromPrompt: boolean; + + @Column({ name: 'app_generated_from_prompt', default: false }) + appGeneratedFromPrompt: boolean; + + @Column({ + type: 'enum', + enumName: 'app_builder_mode', + name: 'app_builder_mode', + enum: ['ai', 'visual'], + default: 'visual', + }) + appBuilderMode: string; + + @Column({ name: 'ai_generation_metadata', type: 'jsonb', nullable: true }) + aiGenerationMetadata: { + steps: { + name: string; + id: string; + loadingStates: string[]; + appInitialisationPrompt?: string; + }[]; + appName?: string; + completedSteps: string[]; + activeStep: string; + version: string; + }; + @CreateDateColumn({ default: () => 'now()', name: 'created_at' }) createdAt: Date; diff --git a/server/src/entities/artifact.entity.ts b/server/src/entities/artifact.entity.ts new file mode 100644 index 0000000000..a98bab84aa --- /dev/null +++ b/server/src/entities/artifact.entity.ts @@ -0,0 +1,51 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { AiConversation } from './ai_conversation.entity'; +import { AiConversationMessage } from './ai_conversation_message.entity'; + +@Entity('artifacts') +export class Artifact { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + name: 'conversation_id', + type: 'uuid', + nullable: false, + }) + conversationId: string; + + @Column({ + name: 'message_id', + type: 'uuid', + nullable: false, + }) + messageId: string; + + @Column({ type: 'jsonb', nullable: false }) + content: any; + + @Column({ type: 'varchar', nullable: false }) + identifier: string; + + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; + + @ManyToOne(() => AiConversation, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'conversation_id' }) + conversation: AiConversation; + + @ManyToOne(() => AiConversationMessage, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'message_id' }) + message: AiConversationMessage; +} diff --git a/server/src/modules/ai/interfaces/IUtilService.ts b/server/src/modules/ai/interfaces/IUtilService.ts index 0b11234f36..83dd53d357 100644 --- a/server/src/modules/ai/interfaces/IUtilService.ts +++ b/server/src/modules/ai/interfaces/IUtilService.ts @@ -1,4 +1,5 @@ export interface IAiUtilService { + getColorScheme(prd: any): any; getAgentAssetPath(filename: string): any; mergeSteps(componentsJson: any, newStepsJson: any): any; diff --git a/server/src/modules/ai/module.ts b/server/src/modules/ai/module.ts index b52433ce45..af11dcb518 100644 --- a/server/src/modules/ai/module.ts +++ b/server/src/modules/ai/module.ts @@ -1,38 +1,53 @@ import { DynamicModule } from '@nestjs/common'; +import { getImportPath } from '@modules/app/constants'; import { AiConversationRepository } from './repositories/ai-conversation.repository'; import { AiConversationMessageRepository } from './repositories/ai-conversation-message.repository'; import { AiResponseVoteRepository } from './repositories/ai-response-vote.repository'; +import { AppsRepository } from '@modules/apps/repository'; import { FeatureAbilityFactory } from './ability'; import { TooljetDbModule } from '@modules/tooljet-db/module'; +import { DataQueriesModule } from '@modules/data-queries/module'; +import { LicenseModule } from '@modules/licensing/module'; +import { AppPermissionsModule } from '@modules/app-permissions/module'; +import { ImportExportResourcesModule } from '@modules/import-export-resources/module'; +import { ArtifactRepository } from './repositories/artifact.repository'; import { SubModule } from '@modules/app/sub-module'; export class AiModule extends SubModule { static async register(configs: { IS_GET_CONTEXT: boolean }): Promise { - const { AiController, AiService, AiUtilService, AgentsService } = await this.getProviders(configs, 'ai', [ - 'controller', - 'service', - 'util.service', - 'services/agents.service', - ]); - - const { ComponentsService, EventsService } = await this.getProviders(configs, 'apps', [ - 'services/component.service', - 'services/event.service', - ]); + const importPath = await getImportPath(configs?.IS_GET_CONTEXT); + const { AiController } = await import(`${importPath}/ai/controller`); + const { AiService } = await import(`${importPath}/ai/service`); + const { AiUtilService } = await import(`${importPath}/ai/util.service`); + const { AgentsService } = await import(`${importPath}/ai/services/agents.service`); + const { ComponentsService } = await import(`${importPath}/apps/services/component.service`); + const { GraphService } = await import(`${importPath}/ai/services/graph.service`); + const { EventsService } = await import(`${importPath}/apps/services/event.service`); return { module: AiModule, - imports: [await TooljetDbModule.register(configs)], + imports: [ + await TooljetDbModule.register(configs), + await DataQueriesModule.register(configs), + await LicenseModule.forRoot(configs), + await AppPermissionsModule.register(configs), + await ImportExportResourcesModule.register(configs), + ], controllers: [AiController], providers: [ AiService, AiUtilService, + GraphService, AgentsService, ComponentsService, + // ImportExportResourcesService, AiConversationRepository, AiConversationMessageRepository, + AppsRepository, AiResponseVoteRepository, FeatureAbilityFactory, + ArtifactRepository, + EventsService, ], exports: [AiUtilService], diff --git a/server/src/modules/ai/repositories/ai-conversation-message.repository.ts b/server/src/modules/ai/repositories/ai-conversation-message.repository.ts index 1aacc9bf81..2879882758 100644 --- a/server/src/modules/ai/repositories/ai-conversation-message.repository.ts +++ b/server/src/modules/ai/repositories/ai-conversation-message.repository.ts @@ -18,7 +18,7 @@ export class AiConversationMessageRepository extends Repository { + constructor(private dataSource: DataSource) { + super(Artifact, dataSource.createEntityManager()); + } +} diff --git a/server/src/modules/ai/services/graph.service.ts b/server/src/modules/ai/services/graph.service.ts new file mode 100644 index 0000000000..ca0dca8f09 --- /dev/null +++ b/server/src/modules/ai/services/graph.service.ts @@ -0,0 +1,27 @@ +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; + +@Injectable() +export class GraphService implements OnModuleInit, OnModuleDestroy { + constructor() {} + + async onModuleInit() { + // No-op for CE version + } + + async onModuleDestroy() { + // No-op for CE version + } + + // Placeholder methods that would be implemented in EE version + async getRelatedComponents(): Promise { + throw new Error('GraphService is not available in Community Edition'); + } + + async analyzeDependencies(): Promise { + throw new Error('GraphService is not available in Community Edition'); + } + + async getComponentGraph(): Promise { + throw new Error('GraphService is not available in Community Edition'); + } +} diff --git a/server/src/modules/ai/util.service.ts b/server/src/modules/ai/util.service.ts index 8d093825d9..e90233d5e2 100644 --- a/server/src/modules/ai/util.service.ts +++ b/server/src/modules/ai/util.service.ts @@ -2,6 +2,7 @@ import { IAiUtilService } from './interfaces/IUtilService'; export class AiUtilService implements IAiUtilService { constructor() {} + public getAgentAssetPath(filename) { throw new Error('Method not implemented.'); } diff --git a/server/src/modules/apps/dto/index.ts b/server/src/modules/apps/dto/index.ts index 532740273a..871dd3ebb5 100644 --- a/server/src/modules/apps/dto/index.ts +++ b/server/src/modules/apps/dto/index.ts @@ -3,6 +3,11 @@ import { IsString, IsOptional, IsNotEmpty, MaxLength, IsBoolean, IsUUID, IsEnum import { Exclude, Expose, Transform } from 'class-transformer'; import { APP_TYPES } from '../constants'; + +export enum AppBuilderMode { + AI = 'ai', + VISUAL = 'visual', +} export class AppCreateDto { @IsNotEmpty() @IsString() @@ -18,6 +23,10 @@ export class AppCreateDto { @IsString() @IsEnum(APP_TYPES, { message: 'Invalid app type.' }) type: string; + + @IsOptional() + @IsString() + prompt?: string; } export class AppUpdateDto { @@ -52,6 +61,10 @@ export class AppUpdateDto { @IsOptional() @Transform(({ value }) => sanitizeInput(value)) icon: string; + + @IsOptional() + @IsEnum(AppBuilderMode, { message: 'app_builder_mode must be either "ai" or "visual"' }) + app_builder_mode?: AppBuilderMode; } export class ValidateAppAccessDto { diff --git a/server/src/modules/apps/interfaces/IUtilService.ts b/server/src/modules/apps/interfaces/IUtilService.ts index 7c22fdbfc7..01352fffbe 100644 --- a/server/src/modules/apps/interfaces/IUtilService.ts +++ b/server/src/modules/apps/interfaces/IUtilService.ts @@ -6,7 +6,7 @@ import { AppUpdateDto } from '../dto'; import { AppEnvironment } from '@entities/app_environments.entity'; import { AppBase } from '@entities/app_base.entity'; export interface IAppsUtilService { - create(name: string, user: User, type: string, manager: EntityManager): Promise; + create(name: string, user: User, type: string, isInitialisedFromPrompt: boolean, manager: EntityManager): Promise; findAppWithIdOrSlug(slug: string, organizationId: string): Promise; validateVersionEnvironment( environmentName: string, diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts index 46e62ca0e2..2f57c6d737 100644 --- a/server/src/modules/apps/service.ts +++ b/server/src/modules/apps/service.ts @@ -62,9 +62,9 @@ export class AppsService implements IAppsService { protected readonly appGitRepository: AppGitRepository ) { } async create(user: User, appCreateDto: AppCreateDto) { - const { name, icon, type } = appCreateDto; + const { name, icon, type, prompt } = appCreateDto; return await dbTransactionWrap(async (manager: EntityManager) => { - const app = await this.appsUtilService.create(name, user, type as APP_TYPES, manager); + const app = await this.appsUtilService.create(name, user, type as APP_TYPES, !!prompt, manager); const appUpdateDto = new AppUpdateDto(); appUpdateDto.name = name; diff --git a/server/src/modules/apps/services/component.service.ts b/server/src/modules/apps/services/component.service.ts index 73997f08fc..357720aeba 100644 --- a/server/src/modules/apps/services/component.service.ts +++ b/server/src/modules/apps/services/component.service.ts @@ -20,6 +20,23 @@ export class ComponentsService implements IComponentsService { }); } + async findOneWithLayouts(id: string): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const component = await manager + .createQueryBuilder(Component, 'component') + .leftJoinAndSelect('component.layouts', 'layout') + .where('component.id = :id', { id }) + .getOne(); + + if (!component) { + throw new Error(`Component with id ${id} not found`); + } + + return component; + }); + } + + async create(componentDiff: object, pageId: string, appVersionId: string) { return dbTransactionForAppVersionAssociationsUpdate(async (manager: EntityManager) => { await this.createComponentsAndLayouts(componentDiff, pageId, appVersionId, manager); diff --git a/server/src/modules/apps/services/event.service.ts b/server/src/modules/apps/services/event.service.ts index a93d6cdfe1..1502082060 100644 --- a/server/src/modules/apps/services/event.service.ts +++ b/server/src/modules/apps/services/event.service.ts @@ -8,6 +8,15 @@ import { IEventsService } from '../interfaces/services/IEventService'; @Injectable() export class EventsService implements IEventsService { + async findEventById(eventId: string): Promise { + return dbTransactionWrap(async (manager: EntityManager) => { + const event = await manager.findOne(EventHandler, { + where: { id: eventId }, + }); + return event; + }); + } + async findEventsForVersion(appVersionId: string, manager?: EntityManager): Promise { return dbTransactionWrap(async (manager: EntityManager) => { const allEvents = await manager.find(EventHandler, { diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts index 7e925d033d..963a37c06b 100644 --- a/server/src/modules/apps/util.service.ts +++ b/server/src/modules/apps/util.service.ts @@ -52,7 +52,7 @@ export class AppsUtilService implements IAppsUtilService { protected readonly organizationRepository: OrganizationRepository, protected readonly abilityService: AbilityService ) {} - async create(name: string, user: User, type: APP_TYPES, manager: EntityManager): Promise { + async create(name: string, user: User, type: APP_TYPES, isInitialisedFromPrompt: boolean = false, manager: EntityManager): Promise { return await dbTransactionWrap(async (manager: EntityManager) => { const app = await catchDbException(() => { return manager.save( @@ -64,6 +64,37 @@ export class AppsUtilService implements IAppsUtilService { organizationId: user.organizationId, userId: user.id, isMaintenanceOn: type === APP_TYPES.WORKFLOW ? true : false, + ...(isInitialisedFromPrompt && { + aiGenerationMetadata: { + steps: [ + { + name: 'Describe app', + id: 'describe_app', + loadingStates: ['Generating PRD', 'PRD generated successfully'], + }, + { + name: 'Define specs', + id: 'define_specs', + loadingStates: ['Generating app', 'App generated successfully'], + }, + { + name: 'Setup database', + id: 'setup_database', + loadingStates: ['Generating app', 'App generated successfully'], + }, + { + name: 'Generate app', + id: 'generate_app', + loadingStates: ['Generating app', 'App generated successfully'], + }, + ], + activeStep: 'describe_app', + completedSteps: [], + version: 'v1', + }, + }), + isInitialisedFromPrompt: isInitialisedFromPrompt, + appBuilderMode: isInitialisedFromPrompt ? 'ai' : 'visual', ...(type === APP_TYPES.WORKFLOW && { workflowApiToken: uuidv4() }), }) ); diff --git a/server/src/modules/data-queries/module.ts b/server/src/modules/data-queries/module.ts index e749cd7959..aad66d3218 100644 --- a/server/src/modules/data-queries/module.ts +++ b/server/src/modules/data-queries/module.ts @@ -37,7 +37,7 @@ export class DataQueriesModule extends SubModule { AppFeatureAbilityFactory, DataSourceFeatureAbilityFactory, ], - exports: [DataQueriesUtilService], + exports: [DataQueriesUtilService, DataQueriesService], controllers: [DataQueriesController], }; } diff --git a/server/src/modules/data-queries/service.ts b/server/src/modules/data-queries/service.ts index fc71a93b6e..34df6c8cd4 100644 --- a/server/src/modules/data-queries/service.ts +++ b/server/src/modules/data-queries/service.ts @@ -24,6 +24,13 @@ export class DataQueriesService implements IDataQueriesService { protected readonly dataSourceRepository: DataSourcesRepository ) { } + async findOne(dataQueryId: string): Promise { + return await this.dataQueryRepository.findOne({ + where: { id: dataQueryId }, + relations: ['dataSource', 'apps', 'dataSource.apps', 'plugins'], + }); + } + async getAll(user: User, versionId: string, mode?: string) { const queries = await this.dataQueryRepository.getAll(versionId); const serializedQueries = []; From a77c54c73646ef01a436880b583b9803ebf62465 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 7 Jul 2025 19:02:51 +0530 Subject: [PATCH 18/33] Fix ghost not coming when resizing --- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 27 ++++++++----------- .../AppBuilder/AppCanvas/Grid/gridUtils.js | 24 +++++++++++------ .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 5 ++-- server/ee | 2 +- 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index d58cd1ed39..8afd3c303c 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -23,7 +23,7 @@ import { computeScrollDelta, computeScrollDeltaOnDrag, getDraggingWidgetWidth, - positionDragGhostWidget, + positionGhostElement, findNewParentIdFromMousePosition, } from './gridUtils'; import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; @@ -536,9 +536,8 @@ export default function Grid({ gridWidth, currentLayout }) { const _top = originalBox.top; // Apply transform to return to original position - ev.target.style.transform = `translate(${Math.round(_left / _gridWidth) * _gridWidth}px, ${ - Math.round(_top / GRID_HEIGHT) * GRID_HEIGHT - }px)`; + ev.target.style.transform = `translate(${Math.round(_left / _gridWidth) * _gridWidth}px, ${Math.round(_top / GRID_HEIGHT) * GRID_HEIGHT + }px)`; } }); @@ -648,12 +647,8 @@ export default function Grid({ gridWidth, currentLayout }) { let _gridWidth = useGridStore.getState().subContainerWidths[currentWidget.component?.parent] || gridWidth; // Show grid during resize - if (currentWidget.component?.parent) { - document.getElementById('canvas-' + currentWidget.component?.parent)?.classList.add('show-grid'); - setDragParentId(currentWidget.component?.parent); - } else { - document.getElementById('real-canvas').classList.add('show-grid'); - } + showGridLines(); + handleActivateTargets(currentWidget.component?.parent); const currentWidth = currentWidget.width * _gridWidth; @@ -697,6 +692,7 @@ export default function Grid({ gridWidth, currentLayout }) { e.target.style.transform = `translate(${transformX}px, ${transformY}px)`; if (e.width > 0) e.target.style.width = `${e.width}px`; if (e.height > 0) e.target.style.height = `${e.height}px`; + positionGhostElement(e.target, 'resize-ghost-widget'); }} onResizeStart={(e) => { if ( @@ -752,9 +748,8 @@ export default function Grid({ gridWidth, currentLayout }) { const roundedTransformY = Math.round(transformY / GRID_HEIGHT) * GRID_HEIGHT; transformY = transformY % GRID_HEIGHT === 5 ? roundedTransformY - GRID_HEIGHT : roundedTransformY; - e.target.style.transform = `translate(${Math.round(transformX / _gridWidth) * _gridWidth}px, ${ - Math.round(transformY / GRID_HEIGHT) * GRID_HEIGHT - }px)`; + e.target.style.transform = `translate(${Math.round(transformX / _gridWidth) * _gridWidth}px, ${Math.round(transformY / GRID_HEIGHT) * GRID_HEIGHT + }px)`; if (!maxWidthHit || e.width < e.target.clientWidth) { e.target.style.width = `${Math.round(e.lastEvent.width / _gridWidth) * _gridWidth}px`; } @@ -1094,7 +1089,7 @@ export default function Grid({ gridWidth, currentLayout }) { `translate: ${e.translate[0]} | Round: ${Math.round(e.translate[0] / gridWidth) * gridWidth} | ${gridWidth}` ); - positionDragGhostWidget(e.target); + positionGhostElement(e.target, 'moveable-drag-ghost'); }} onDragGroup={(ev) => { const { events } = ev; @@ -1179,8 +1174,8 @@ export default function Grid({ gridWidth, currentLayout }) { }} // snapGridAll={true} scrollable={true} - // snapContainer={snapContainer} - // snapGridWidth={100} + // snapContainer={snapContainer} + // snapGridWidth={100} /> ); diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js index ba42d4152f..1a3bc36b64 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js @@ -530,28 +530,36 @@ export const getDraggingWidgetWidth = (canvasParentId, widgetWidth) => { return draggingWidgetWidth; }; -export const positionDragGhostWidget = (draggedElement) => { - const ghostElement = document.getElementById('moveable-drag-ghost'); +/** + * Positions a ghost/feedback element relative to the main canvas + * @param {HTMLElement} targetElement - The element being dragged/resized + * @param {string} ghostElementId - The ID of the ghost element to position + * @param {Object} options - Additional positioning options + * @param {boolean} options.includeSize - Whether to update width/height of ghost element + */ +export const positionGhostElement = (targetElement, ghostElementId) => { + const ghostElement = document.getElementById(ghostElementId); - if (!ghostElement || !draggedElement) return; + if (!ghostElement || !targetElement) return; const mainCanvas = document.getElementById('real-canvas'); if (!mainCanvas) return; const mainCanvasRect = mainCanvas.getBoundingClientRect(); - const draggedRect = draggedElement.getBoundingClientRect(); + const targetRect = targetElement.getBoundingClientRect(); // Calculate position relative to main canvas - const relativeLeft = draggedRect.left - mainCanvasRect.left; - const relativeTop = draggedRect.top - mainCanvasRect.top; + const relativeLeft = targetRect.left - mainCanvasRect.left; + const relativeTop = targetRect.top - mainCanvasRect.top; // Apply the position ghostElement.style.left = `${relativeLeft}px`; ghostElement.style.top = `${relativeTop}px`; - ghostElement.style.width = `${draggedRect.width}px`; - ghostElement.style.height = `${draggedRect.height}px`; + ghostElement.style.width = `${targetRect.width}px`; + ghostElement.style.height = `${targetRect.height}px`; }; + /** * Finds the new parent ID based on the current mouse position during drag operations * @param {number} clientX - The X coordinate of the mouse position diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 057781f8ec..954be5607a 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -1,9 +1,8 @@ import React, { memo } from 'react'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { DragGhostWidget, ResizeGhostWidget } from './GhostWidgets'; +import { ResizeGhostWidget } from './GhostWidgets'; import { ConfigHandle } from './ConfigHandle/ConfigHandle'; -import { useGridStore } from '@/_stores/gridStore'; import cx from 'classnames'; import RenderWidget from './RenderWidget'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; @@ -35,7 +34,7 @@ const WidgetWrapper = memo( const temporaryLayouts = useStore((state) => state.temporaryLayouts?.[id], shallow); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); - const isResizing = useGridStore((state) => state.resizingComponentId === id); + const isResizing = useStore((state) => state.resizingComponentId === id); const componentType = useStore( (state) => state.getComponentDefinition(id, moduleId)?.component?.component, shallow diff --git a/server/ee b/server/ee index 3bd72a7575..cc864000dd 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 3bd72a7575446d120a3b548e8819be271072407a +Subproject commit cc864000dd03cc345e53ae9fc43821d3174f4c64 From bff1ec980771ea9f4632ace4eeee923de29b9bbe Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 7 Jul 2025 19:08:23 +0530 Subject: [PATCH 19/33] Clean code --- frontend/src/AppBuilder/AppCanvas/Container.jsx | 6 +++--- frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js | 2 +- .../RightSideBar/ComponentManagerTab/DragLayer.jsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 31bac368a7..1f37c2bb5b 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -100,7 +100,7 @@ export const Container = React.memo( } }, drop: (item, monitor) => { - handleDrop(item, monitor, id); + handleDrop(item, id); }, }); @@ -174,8 +174,8 @@ export const Container = React.memo( currentMode === 'view' ? computeViewerBackgroundColor(darkMode, canvasBgColor) : id === 'canvas' - ? canvasBgColor - : '#f0f0f0', + ? canvasBgColor + : '#f0f0f0', width: '100%', maxWidth: (() => { // For Main Canvas diff --git a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js index a13571b933..05a9a36502 100644 --- a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js +++ b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js @@ -24,7 +24,7 @@ export const useCanvasDropHandler = ({ appType }) => { const currentLayout = useStore((state) => state.currentLayout, shallow); const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); - const handleDrop = async ({ componentType: draggedComponentType, component }, monitor, canvasId) => { + const handleDrop = async ({ componentType: draggedComponentType, component }, canvasId) => { const realCanvasRef = !canvasId || canvasId === 'canvas' ? document.getElementById(`real-canvas`) diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index a3175ce64e..c736292e38 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -31,7 +31,7 @@ export const DragLayer = ({ index, component, isModuleTab = false }) => { const clientOffset = monitor.getClientOffset(); const currentDragCanvasId = useGridStore.getState().currentDragCanvasId; if (clientOffset) { - handleDrop(item, monitor, currentDragCanvasId); + handleDrop(item, currentDragCanvasId); } }, }), From ca826d585662f63c676c033bb7b814cb6221ddd9 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Tue, 8 Jul 2025 10:10:29 +0530 Subject: [PATCH 20/33] fix feedbacks --- server/ee | 2 +- server/src/modules/ai/module.ts | 2 ++ server/src/modules/data-queries/module.ts | 2 +- server/src/modules/data-queries/service.ts | 10 +--------- 4 files changed, 5 insertions(+), 11 deletions(-) diff --git a/server/ee b/server/ee index dcb4173b2d..a2f4d8b321 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit dcb4173b2db2a6ee3d96b21e0b32f2f0b0f2c4b0 +Subproject commit a2f4d8b32155754ad04a2ab82e5379939d0e41cb diff --git a/server/src/modules/ai/module.ts b/server/src/modules/ai/module.ts index af11dcb518..108ccf6c53 100644 --- a/server/src/modules/ai/module.ts +++ b/server/src/modules/ai/module.ts @@ -12,6 +12,7 @@ import { AppPermissionsModule } from '@modules/app-permissions/module'; import { ImportExportResourcesModule } from '@modules/import-export-resources/module'; import { ArtifactRepository } from './repositories/artifact.repository'; import { SubModule } from '@modules/app/sub-module'; +import { DataQueryRepository } from '@modules/data-queries/repository'; export class AiModule extends SubModule { static async register(configs: { IS_GET_CONTEXT: boolean }): Promise { @@ -47,6 +48,7 @@ export class AiModule extends SubModule { AiResponseVoteRepository, FeatureAbilityFactory, ArtifactRepository, + DataQueryRepository, EventsService, ], diff --git a/server/src/modules/data-queries/module.ts b/server/src/modules/data-queries/module.ts index aad66d3218..e749cd7959 100644 --- a/server/src/modules/data-queries/module.ts +++ b/server/src/modules/data-queries/module.ts @@ -37,7 +37,7 @@ export class DataQueriesModule extends SubModule { AppFeatureAbilityFactory, DataSourceFeatureAbilityFactory, ], - exports: [DataQueriesUtilService, DataQueriesService], + exports: [DataQueriesUtilService], controllers: [DataQueriesController], }; } diff --git a/server/src/modules/data-queries/service.ts b/server/src/modules/data-queries/service.ts index 34df6c8cd4..2ec6ea2e08 100644 --- a/server/src/modules/data-queries/service.ts +++ b/server/src/modules/data-queries/service.ts @@ -3,7 +3,6 @@ import { EntityManager, In } from 'typeorm'; import { User } from 'src/entities/user.entity'; import { DataSource } from 'src/entities/data_source.entity'; import { dbTransactionWrap } from 'src/helpers/database.helper'; -import { DataSourceTypes } from '@modules/data-sources/constants'; import { Response } from 'express'; import { DataQueryRepository } from './repository'; import { decode } from 'js-base64'; @@ -22,14 +21,7 @@ export class DataQueriesService implements IDataQueriesService { protected readonly dataQueryRepository: DataQueryRepository, protected readonly dataQueryUtilService: DataQueriesUtilService, protected readonly dataSourceRepository: DataSourcesRepository - ) { } - - async findOne(dataQueryId: string): Promise { - return await this.dataQueryRepository.findOne({ - where: { id: dataQueryId }, - relations: ['dataSource', 'apps', 'dataSource.apps', 'plugins'], - }); - } + ) {} async getAll(user: User, versionId: string, mode?: string) { const queries = await this.dataQueryRepository.getAll(versionId); From d49bbfd68bf314f49003ec311760584e5cc9cb8b Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Tue, 8 Jul 2025 11:06:54 +0530 Subject: [PATCH 21/33] update submodule --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index a2f4d8b321..7047172a8d 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit a2f4d8b32155754ad04a2ab82e5379939d0e41cb +Subproject commit 7047172a8dd20115522d0a508055e4905a9425b4 From 72ce46488b2f27b81f1ee91d787eeacca2852443 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Tue, 8 Jul 2025 11:09:39 +0530 Subject: [PATCH 22/33] update submodule --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index 7047172a8d..1fa38b9fce 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 7047172a8dd20115522d0a508055e4905a9425b4 +Subproject commit 1fa38b9fce1e19ea9048ea53b3167a84dfad86ab From 6c28d3f52248db33844fa671c23316cd59841a27 Mon Sep 17 00:00:00 2001 From: Shaurya Sharma Date: Tue, 8 Jul 2025 12:09:16 +0530 Subject: [PATCH 23/33] Fixed query breaking due to nested proxy --- frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js index 903b548ca2..7a2cf6d495 100644 --- a/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/queryPanelSlice.js @@ -1015,8 +1015,6 @@ export const createQueryPanelSlice = (set, get) => ({ }, createProxy: (obj, path = '') => { - const { queryPanel } = get(); - const { createProxy } = queryPanel; return new Proxy(obj, { get(target, prop) { @@ -1027,7 +1025,7 @@ export const createQueryPanelSlice = (set, get) => ({ } const value = target[prop]; - return typeof value === 'object' && value !== null ? createProxy(value, fullPath) : value; + return value; }, }); }, From d82ee5d307bade472fe07c9fef01361a2344cb58 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Tue, 8 Jul 2025 12:59:42 +0530 Subject: [PATCH 24/33] cloud changes --- frontend/src/_services/ai.service.js | 9 ------- server/ee | 2 +- .../modules/ai/interfaces/IAgentsService.ts | 12 ---------- .../src/modules/ai/interfaces/IUtilService.ts | 4 ---- .../src/modules/ai/services/agents.service.ts | 24 ------------------- server/src/modules/ai/util.service.ts | 10 +------- .../controllers/components.controller.ts | 2 +- 7 files changed, 3 insertions(+), 60 deletions(-) diff --git a/frontend/src/_services/ai.service.js b/frontend/src/_services/ai.service.js index 94f273d27d..98f7f10698 100644 --- a/frontend/src/_services/ai.service.js +++ b/frontend/src/_services/ai.service.js @@ -4,7 +4,6 @@ import { fetchEventSource } from '@microsoft/fetch-event-source'; export const aiService = { generateApp, - createComponent, createQuery, updateComponent, createEvent, @@ -60,14 +59,6 @@ function generateApp(prompt) { return fetch(`${config.apiUrl}/ai/generateApp`, requestOptions).then(handleResponse); } -function createComponent(prompt) { - const body = { - prompt, - }; - const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; - return fetch(`${config.apiUrl}/agents/create-components`, requestOptions).then(handleResponse); -} - function createQuery(prompt) { const body = { prompt, diff --git a/server/ee b/server/ee index 1fa38b9fce..be52da988c 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 1fa38b9fce1e19ea9048ea53b3167a84dfad86ab +Subproject commit be52da988c1d90c2f9bc1c92642d581d0b6aa238 diff --git a/server/src/modules/ai/interfaces/IAgentsService.ts b/server/src/modules/ai/interfaces/IAgentsService.ts index 924ada16ed..f31947f7a4 100644 --- a/server/src/modules/ai/interfaces/IAgentsService.ts +++ b/server/src/modules/ai/interfaces/IAgentsService.ts @@ -1,16 +1,4 @@ export interface IAgentsService { - createComponent(prompt: string, organizationId: string): Promise; - - createQuery(prompt: string, tableName: string, columns: string, organizationId: string): Promise; - - createEvent(prompt: string, pageId: string[], organizationId: string): Promise; - - Agentic(prompt: string, organizationId: string): Promise; - - PromptEnrichment(prd_data: { content: string; metadata?: any }, organizationId: string): Promise; - - PromptEnrichmentChat(prompt: string, oldContext: any[], organizationId: string): Promise; - CreateTable(organizationId: string, tables: any): Promise; docs(prompt: string, organizationId: string): Promise; diff --git a/server/src/modules/ai/interfaces/IUtilService.ts b/server/src/modules/ai/interfaces/IUtilService.ts index 83dd53d357..cb98cfdb16 100644 --- a/server/src/modules/ai/interfaces/IUtilService.ts +++ b/server/src/modules/ai/interfaces/IUtilService.ts @@ -34,10 +34,6 @@ export interface IAiUtilService { getQueriesfromsteps(steps: any): Promise; - createQuerySteps(prd: string, lld: string, tableName: any, components: any, organizationId: any): Promise; - - createEventSteps(prd: string, Query: any, components: any, organizationId: any): Promise; - convertToSteps(jsonData: any): Promise; getColorScheme(prd: any): any; diff --git a/server/src/modules/ai/services/agents.service.ts b/server/src/modules/ai/services/agents.service.ts index 67fb9dd963..ad6059363b 100644 --- a/server/src/modules/ai/services/agents.service.ts +++ b/server/src/modules/ai/services/agents.service.ts @@ -4,30 +4,6 @@ import { IAgentsService } from '../interfaces/IAgentsService'; @Injectable() export class AgentsService implements IAgentsService { constructor() {} - // Agents methods - async createComponent(prompt: string, organizationId): Promise { - throw new Error('Method not implemented.'); - } - - async createQuery(prompt: string, tableName: string, columns: string, organizationId): Promise { - throw new Error('Method not implemented.'); - } - - async createEvent(prompt: string, pageId: string[], organizationId): Promise { - throw new Error('Method not implemented.'); - } - - async Agentic(prompt: string, organizationId): Promise { - throw new Error('Method not implemented.'); - } - - async PromptEnrichment(prd_data: { content: string; metadata?: any }, organizationId: string): Promise { - throw new Error('Method not implemented.'); - } - - async PromptEnrichmentChat(prompt: string, oldContext: any[], organizationId): Promise { - throw new Error('Method not implemented.'); - } async CreateTable(organizationId: string, tables): Promise { throw new Error('Method not implemented.'); diff --git a/server/src/modules/ai/util.service.ts b/server/src/modules/ai/util.service.ts index e90233d5e2..157fb97396 100644 --- a/server/src/modules/ai/util.service.ts +++ b/server/src/modules/ai/util.service.ts @@ -2,7 +2,7 @@ import { IAiUtilService } from './interfaces/IUtilService'; export class AiUtilService implements IAiUtilService { constructor() {} - + public getAgentAssetPath(filename) { throw new Error('Method not implemented.'); } @@ -35,14 +35,6 @@ export class AiUtilService implements IAiUtilService { throw new Error('Method not implemented.'); } - async createQuerySteps(prd: string, lld: string, tableName, components, organizationId) { - throw new Error('Method not implemented.'); - } - - async createEventSteps(prd: string, Query: any, components: any, organizationId: any): Promise { - throw new Error('Method not implemented.'); - } - async convertToSteps(jsonData: any): Promise { throw new Error('Method not implemented.'); } diff --git a/server/src/modules/versions/controllers/components.controller.ts b/server/src/modules/versions/controllers/components.controller.ts index e91e30e791..22c7c22e91 100644 --- a/server/src/modules/versions/controllers/components.controller.ts +++ b/server/src/modules/versions/controllers/components.controller.ts @@ -24,7 +24,7 @@ import { IComponentsController } from '../interfaces/controllers/IComponentsCont version: '2', }) export class ComponentsController implements IComponentsController { - constructor(protected readonly componentsService: ComponentsService) {} + constructor(protected readonly componentsService: ComponentsService) { } @InitFeature(FEATURE_KEY.CREATE_COMPONENTS) @UseGuards(JwtAuthGuard, ValidAppGuard, FeatureAbilityGuard) From 1c5ff28263ca478c64df95e36fbe18f6a3727380 Mon Sep 17 00:00:00 2001 From: Kartik Gupta Date: Tue, 8 Jul 2025 14:02:07 +0530 Subject: [PATCH 25/33] cloud changes --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index be52da988c..64700ebb0b 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit be52da988c1d90c2f9bc1c92642d581d0b6aa238 +Subproject commit 64700ebb0b4e55f0df0992cd6085231898989490 From 890fc2aa4e871ff34dd8ea9fab2cca483a41052e Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Tue, 8 Jul 2025 15:03:45 +0530 Subject: [PATCH 26/33] chore: update submodule reference --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index 1a14db61e1..3297d43038 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 1a14db61e117145def63bf66e9225e049a2f5818 +Subproject commit 3297d4303806594bd3f5b614df9057c8ceaa92b3 diff --git a/server/ee b/server/ee index cc864000dd..1aaf0d63c9 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit cc864000dd03cc345e53ae9fc43821d3174f4c64 +Subproject commit 1aaf0d63c9b55034503696a746167b3f32152360 From 489c8aa950e9c36b7c63dc40b9551efb1f58f6f2 Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Tue, 8 Jul 2025 15:31:01 +0530 Subject: [PATCH 27/33] fix:: updated filepicker default size --- frontend/ee | 2 +- server/ee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/ee b/frontend/ee index 6708f78aa5..3297d43038 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit 6708f78aa593343813563ff39ee784e26cd97f1f +Subproject commit 3297d4303806594bd3f5b614df9057c8ceaa92b3 diff --git a/server/ee b/server/ee index dcb4173b2d..1aaf0d63c9 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit dcb4173b2db2a6ee3d96b21e0b32f2f0b0f2c4b0 +Subproject commit 1aaf0d63c9b55034503696a746167b3f32152360 From 4a45a8523aec5561425091a274ddb17f9a6201a6 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 8 Jul 2025 17:05:49 +0530 Subject: [PATCH 28/33] chore: update server submodule reference --- server/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/ee b/server/ee index 1aaf0d63c9..1355aafd03 160000 --- a/server/ee +++ b/server/ee @@ -1 +1 @@ -Subproject commit 1aaf0d63c9b55034503696a746167b3f32152360 +Subproject commit 1355aafd032a55f8b028d4996ec86e7ebdd4cb6e From ee9ac0820ba91d563ae09c3eff3bd23f67add1ff Mon Sep 17 00:00:00 2001 From: Nishidh Jain Date: Tue, 8 Jul 2025 18:06:10 +0530 Subject: [PATCH 29/33] Disable error overlay --- frontend/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 6fedc3b217..3d9576b333 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -79,7 +79,7 @@ if (process.env.APM_VENDOR === 'sentry') { } if (isDevEnv) { - plugins.push(new ReactRefreshWebpackPlugin()); + plugins.push(new ReactRefreshWebpackPlugin({ overlay: false })); } module.exports = { From e2034073ae80407dedfc4e431e4958c4fc9104eb Mon Sep 17 00:00:00 2001 From: devanshu052000 Date: Tue, 8 Jul 2025 18:53:38 +0530 Subject: [PATCH 30/33] Update frontend submodule reference --- frontend/ee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/ee b/frontend/ee index d5ff0d0136..77ead4835e 160000 --- a/frontend/ee +++ b/frontend/ee @@ -1 +1 @@ -Subproject commit d5ff0d01362128917dbfa01972f871236438cdb3 +Subproject commit 77ead4835e7795ac82c338cb09215cf32469058f From 0b282f45d03536bc4e2583bdef0d67a1bf653eed Mon Sep 17 00:00:00 2001 From: Johnson Cherian Date: Wed, 9 Jul 2025 00:14:28 +0530 Subject: [PATCH 31/33] Revert "Add support to show guidelines on component drop" --- .vscode/settings.json | 3 - frontend/package-lock.json | 11 +- frontend/package.json | 2 +- .../src/AppBuilder/AppCanvas/Container.jsx | 173 +++++++++++++----- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 158 ++++++++-------- .../AppBuilder/AppCanvas/Grid/gridUtils.js | 50 +---- .../Grid/hooks/useElementGudelines.js | 72 -------- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 5 +- .../AppBuilder/AppCanvas/appCanvasUtils.js | 23 ++- .../AppCanvas/useCanvasDropHandler.js | 113 ------------ .../ComponentManagerTab/DragLayer.jsx | 85 +++++++-- .../_hooks/useDropVirtualMoveableGhost.js | 101 ---------- .../src/AppBuilder/_hooks/useGhostMoveable.js | 107 ----------- .../AppBuilder/_stores/slices/gridSlice.js | 6 - frontend/src/_stores/gridStore.js | 21 +-- .../BaseColorSwatches/BaseColorSwatches.jsx | 1 + 16 files changed, 301 insertions(+), 630 deletions(-) delete mode 100644 frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js delete mode 100644 frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js delete mode 100644 frontend/src/AppBuilder/_hooks/useDropVirtualMoveableGhost.js delete mode 100644 frontend/src/AppBuilder/_hooks/useGhostMoveable.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 16c9f25d4d..ac6e6079cc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,9 +10,6 @@ ], "eslint.format.enable": true, "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": "explicit" - }, "json.schemas": [ { "fileMatch": [ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e09315a4b6..b165f8fb62 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -121,7 +121,7 @@ "react-loading-skeleton": "^3.1.1", "react-markdown": "^9.0.0", "react-mentions": "^4.4.7", - "react-moveable": "^0.56.0", + "react-moveable": "^0.54.1", "react-multi-select-component": "^4.3.4", "react-pdf": "^6.2.2", "react-phone-input-2": "^2.15.1", @@ -30697,9 +30697,10 @@ } }, "node_modules/react-moveable": { - "version": "0.56.0", - "resolved": "https://registry.npmjs.org/react-moveable/-/react-moveable-0.56.0.tgz", - "integrity": "sha512-FmJNmIOsOA36mdxbrc/huiE4wuXSRlmon/o+/OrfNhSiYYYL0AV5oObtPluEhb2Yr/7EfYWBHTxF5aWAvjg1SA==", + "version": "0.54.2", + "resolved": "https://registry.npmjs.org/react-moveable/-/react-moveable-0.54.2.tgz", + "integrity": "sha512-NGaVLbn0i9pb3+BWSKGWFqI/Mgm4+WMeWHxXXQ4Qi1tHxWCXrUrbGvpxEpt69G/hR7dez+/m68ex+fabjnvcUg==", + "license": "MIT", "dependencies": { "@daybrush/utils": "^1.13.0", "@egjs/agent": "^2.2.1", @@ -30710,7 +30711,7 @@ "@scena/matrix": "^1.1.1", "css-to-mat": "^1.1.1", "framework-utils": "^1.1.0", - "gesto": "^1.19.3", + "gesto": "^1.19.0", "overlap-area": "^1.1.0", "react-css-styled": "^1.1.9", "react-selecto": "^1.25.0" diff --git a/frontend/package.json b/frontend/package.json index ffe5da76ac..6a65305bb8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -116,7 +116,7 @@ "react-loading-skeleton": "^3.1.1", "react-markdown": "^9.0.0", "react-mentions": "^4.4.7", - "react-moveable": "^0.56.0", + "react-moveable": "^0.54.1", "react-multi-select-component": "^4.3.4", "react-pdf": "^6.2.2", "react-phone-input-2": "^2.15.1", diff --git a/frontend/src/AppBuilder/AppCanvas/Container.jsx b/frontend/src/AppBuilder/AppCanvas/Container.jsx index 1f37c2bb5b..808a29f90c 100644 --- a/frontend/src/AppBuilder/AppCanvas/Container.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Container.jsx @@ -4,17 +4,32 @@ import cx from 'classnames'; import WidgetWrapper from './WidgetWrapper'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { useDrop, useDragLayer } from 'react-dnd'; -import { computeViewerBackgroundColor, getSubContainerWidthAfterPadding } from './appCanvasUtils'; -import { CANVAS_WIDTHS, NO_OF_GRIDS, GRID_HEIGHT } from './appCanvasConstants'; +import { useDrop } from 'react-dnd'; +import { + addChildrenWidgetsToParent, + addNewWidgetToTheEditor, + computeViewerBackgroundColor, + getSubContainerWidthAfterPadding, + addDefaultButtonIdToForm, +} from './appCanvasUtils'; +import { + CANVAS_WIDTHS, + NO_OF_GRIDS, + WIDGETS_WITH_DEFAULT_CHILDREN, + GRID_HEIGHT, + CONTAINER_FORM_CANVAS_PADDING, + SUBCONTAINER_CANVAS_BORDER_WIDTH, + BOX_PADDING, +} from './appCanvasConstants'; import { useGridStore } from '@/_stores/gridStore'; import NoComponentCanvasContainer from './NoComponentCanvasContainer'; +import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; +import { isPDFSupported } from '@/_helpers/appUtils'; +import toast from 'react-hot-toast'; import { ModuleContainerBlank } from '@/modules/Modules/components'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import useSortedComponents from '../_hooks/useSortedComponents'; -import { useDropVirtualMoveableGhost } from '@/AppBuilder/_hooks/useDropVirtualMoveableGhost'; -import { useCanvasDropHandler } from './useCanvasDropHandler'; -import { findNewParentIdFromMousePosition } from './Grid/gridUtils'; +import { noop } from 'lodash'; //TODO: Revisit the logic of height (dropRef) @@ -36,72 +51,112 @@ export const Container = React.memo( columns, darkMode, canvasMaxWidth, + isViewerSidebarPinned, + pageSidebarStyle, + pagePositionType, componentType, appType, }) => { const { moduleId } = useModuleContext(); const realCanvasRef = useRef(null); const components = useStore((state) => state.getContainerChildrenMapping(id, moduleId), shallow); + + const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow); + const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow); const setLastCanvasClickPosition = useStore((state) => state.setLastCanvasClickPosition, shallow); const canvasBgColor = useStore( (state) => (id === 'canvas' ? state.getCanvasBackgroundColor('canvas', darkMode) : ''), shallow ); + const isPagesSidebarHidden = useStore((state) => state.getPagesSidebarVisibility('canvas'), shallow); const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); const currentLayout = useStore((state) => state.currentLayout, shallow); const setFocusedParentId = useStore((state) => state.setFocusedParentId, shallow); - - // Initialize ghost moveable hook - const { activateMoveableGhost, deactivateMoveableGhost } = useDropVirtualMoveableGhost(); - - // // Monitor drag layer to update ghost position continuously - const { isDragging } = useDragLayer((monitor) => ({ - isDragging: monitor.isDragging(), - })); - - // // // Cleanup ghost when drag ends - useEffect(() => { - if (!isDragging) { - deactivateMoveableGhost(); - } - }, [id, isDragging, deactivateMoveableGhost]); + const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; const isContainerReadOnly = useMemo(() => { return (index !== 0 && (componentType === 'Listview' || componentType === 'Kanban')) || currentMode === 'view'; }, [index, componentType, currentMode]); - const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); - - const { handleDrop } = useCanvasDropHandler({ - appType, - }); - const [{ isOverCurrent }, drop] = useDrop({ accept: 'box', - hover: (item, monitor) => { - const clientOffset = monitor.getClientOffset(); + hover: (item) => { + item.canvasRef = realCanvasRef?.current; + item.canvasId = id; + item.canvasWidth = getContainerCanvasWidth(); + }, + drop: async ({ componentType, component }, monitor) => { + setShowModuleBorder(false); + if (currentMode === 'view' || (appType === 'module' && componentType !== 'ModuleContainer')) return; - const appCanvasWidth = realCanvasRef?.current?.offsetWidth || 0; + const didDrop = monitor.didDrop(); + if (didDrop) return; - if (clientOffset) { - const canvasId = findNewParentIdFromMousePosition(clientOffset.x, clientOffset.y, id); - if (canvasId === id) { - setCurrentDragCanvasId(id); + const moduleInfo = component?.moduleId + ? { + moduleId: component.moduleId, + versionId: component.versionId, + environmentId: component.environmentId, + moduleName: component.displayName, + moduleContainer: component.moduleContainer, + } + : undefined; + + let addedComponent; + + if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(componentType)) { + let parentComponent = addNewWidgetToTheEditor( + componentType, + monitor, + currentLayout, + realCanvasRef, + id, + moduleInfo + ); + const childComponents = addChildrenWidgetsToParent(componentType, parentComponent?.id, currentLayout); + if (componentType === 'Form') { + parentComponent = addDefaultButtonIdToForm(parentComponent, childComponents); } + addedComponent = [parentComponent, ...childComponents]; + await addComponentToCurrentPage(addedComponent); + } else { + const newComponent = addNewWidgetToTheEditor( + componentType, + monitor, + currentLayout, + realCanvasRef, + id, + moduleInfo + ); + addedComponent = [newComponent]; + await addComponentToCurrentPage(addedComponent); } - // Calculate width based on the app canvas's grid - let width = (appCanvasWidth * item.component?.defaultSize?.width) / NO_OF_GRIDS; - const componentSize = { - width, - height: item.component?.defaultSize?.height, - }; - if (clientOffset && id === 'canvas') { - activateMoveableGhost(componentSize, clientOffset, realCanvasRef); + + setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); + + const canvas = document.querySelector('.canvas-container'); + const sidebar = document.querySelector('.editor-sidebar'); + const droppedElem = document.getElementById(addedComponent?.[0]?.id); + + if (!canvas || !sidebar || !droppedElem) return; + + const droppedRect = droppedElem.getBoundingClientRect(); + const sidebarRect = sidebar.getBoundingClientRect(); + + const isOverlapping = droppedRect.right > sidebarRect.left && droppedRect.left < sidebarRect.right; + + if (isOverlapping) { + const overlap = droppedRect.right - sidebarRect.left; + canvas.scrollTo({ + left: canvas.scrollLeft + overlap, + behavior: 'smooth', + }); } }, - drop: (item, monitor) => { - handleDrop(item, id); - }, + + collect: (monitor) => ({ + isOverCurrent: monitor.isOver({ shallow: true }), + }), }); const showEmptyContainer = @@ -120,12 +175,34 @@ export const Container = React.memo( } const gridWidth = getContainerCanvasWidth() / NO_OF_GRIDS; - useEffect(() => { useGridStore.getState().actions.setSubContainerWidths(id, gridWidth); // eslint-disable-next-line react-hooks/exhaustive-deps }, [canvasWidth, listViewMode, columns]); + const getCanvasWidth = useCallback(() => { + // if ( + // id === 'canvas' && + // !isPagesSidebarHidden && + // isViewerSidebarPinned && + // currentLayout !== 'mobile' && + // pagePositionType == 'side' && + // appType !== 'module' + // ) { + // return `calc(100% - ${pageSidebarStyle === 'icon' ? '85px' : '226px'})`; + // } + // if ( + // id === 'canvas' && + // !isPagesSidebarHidden && + // !isViewerSidebarPinned && + // currentLayout !== 'mobile' && + // pagePositionType == 'side' + // ) { + // return `calc(100% - ${'44px'})`; + // } + return '100%'; + }, [id, isPagesSidebarHidden, isViewerSidebarPinned, currentLayout, pagePositionType, pageSidebarStyle]); + const handleCanvasClick = useCallback( (e) => { const realCanvas = e.target.closest('.real-canvas'); @@ -174,8 +251,8 @@ export const Container = React.memo( currentMode === 'view' ? computeViewerBackgroundColor(darkMode, canvasBgColor) : id === 'canvas' - ? canvasBgColor - : '#f0f0f0', + ? canvasBgColor + : '#f0f0f0', width: '100%', maxWidth: (() => { // For Main Canvas diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 8afd3c303c..dbe9ed941d 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -1,5 +1,5 @@ // import '@/Editor/wdyr'; -import React, { useEffect, useState, useRef, useCallback, useMemo } from 'react'; +import React, { useEffect, useState, useRef, useCallback } from 'react'; // eslint-disable-next-line import/no-unresolved import Moveable from 'react-moveable'; import { shallow } from 'zustand/shallow'; @@ -10,8 +10,10 @@ import { useGridStore, useIsGroupHandleHoverd, useOpenModalWidgetId } from '@/_s import toast from 'react-hot-toast'; import { individualGroupableProps, + getMouseDistanceFromParentDiv, findChildrenAndGrandchildren, findHighestLevelofSelection, + getOffset, hasParentWithClass, getPositionForGroupDrag, adjustWidth, @@ -23,8 +25,7 @@ import { computeScrollDelta, computeScrollDeltaOnDrag, getDraggingWidgetWidth, - positionGhostElement, - findNewParentIdFromMousePosition, + positionDragGhostWidget, } from './gridUtils'; import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd'; import useStore from '@/AppBuilder/_stores/store'; @@ -32,8 +33,6 @@ import './Grid.css'; import { useGroupedTargetsScrollHandler } from './hooks/useGroupedTargetsScrollHandler'; import { DROPPABLE_PARENTS, NO_OF_GRIDS, SUBCONTAINER_WIDGETS } from '../appCanvasConstants'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -import { useElementGudelines } from './hooks/useElementGudelines'; - const CANVAS_BOUNDS = { left: 0, top: 0, right: 0, position: 'css' }; const RESIZABLE_CONFIG = { edge: ['nw', 'n', 'ne', 'w', 'e', 'sw', 's', 'se'], @@ -74,15 +73,10 @@ export default function Grid({ gridWidth, currentLayout }) { const getTemporaryLayouts = useStore((state) => state.getTemporaryLayouts, shallow); const updateContainerAutoHeight = useStore((state) => state.updateContainerAutoHeight, shallow); const [canvasBounds, setCanvasBounds] = useState(CANVAS_BOUNDS); + const draggingComponentId = useGridStore((state) => state.draggingComponentId, shallow); + const resizingComponentId = useGridStore((state) => state.resizingComponentId, shallow); const [dragParentId, setDragParentId] = useState(null); - const virtualTarget = useGridStore((state) => state.virtualTarget, shallow); - const { elementGuidelines } = useElementGudelines( - boxList, - selectedComponents, - dragParentId, - getResolvedValue, - virtualTarget - ); + const [elementGuidelines, setElementGuidelines] = useState([]); const componentsSnappedTo = useRef(null); const prevDragParentId = useRef(null); const newDragParentId = useRef(null); @@ -90,39 +84,42 @@ export default function Grid({ gridWidth, currentLayout }) { const checkIfAnyWidgetVisibilityChanged = useStore((state) => state.checkIfAnyWidgetVisibilityChanged(), shallow); const getExposedValueOfComponent = useStore((state) => state.getExposedValueOfComponent, shallow); const setReorderContainerChildren = useStore((state) => state.setReorderContainerChildren, shallow); - const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId, shallow); - const groupedTargets = [...findHighestLevelofSelection().map((component) => '.ele-' + component.id)]; const [isVerticalExpansionRestricted, setIsVerticalExpansionRestricted] = useState(false); const toggleRightSidebar = useStore((state) => state.toggleRightSidebar, shallow); - const draggingComponentId = useStore((state) => state.draggingComponentId, shallow); - const resizingComponentId = useStore((state) => state.resizingComponentId, shallow); - const snapContainer = useMemo(() => { - if (currentDragCanvasId) { - return `#canvas-${currentDragCanvasId}`; - } - if (dragParentId) { - return `#canvas-${dragParentId}`; - } - return '#real-canvas'; - }, [currentDragCanvasId, dragParentId]); - - const getMoveableTarget = () => { - if (virtualTarget) { - return '#moveable-ghost-element'; - } - return groupedTargets?.length > 1 ? groupedTargets : '.target'; - }; - - // Set moveable reference in grid store for access by other components useEffect(() => { - if (moveableRef.current) { - useGridStore.getState().setMoveableRef(moveableRef.current); - } - return () => { - useGridStore.getState().setMoveableRef(null); - }; - }, []); + const selectedSet = new Set(selectedComponents); + const draggingOrResizingId = draggingComponentId || resizingComponentId; + const isGrouped = findHighestLevelofSelection().length > 1; + const firstSelectedParent = + selectedComponents.length > 0 ? boxList.find((b) => b.id === selectedComponents[0])?.parent : null; + const selectedParent = dragParentId || firstSelectedParent; + + const guidelines = boxList + .filter((box) => { + const isVisible = + getResolvedValue(box?.component?.definition?.properties?.visibility?.value) || + getResolvedValue(box?.component?.definition?.styles?.visibility?.value); + + // Early return for non-visible elements + if (!isVisible) return false; + + if (isGrouped) { + // If component is selected, don't show its guidelines + if (selectedSet.has(box.id)) return false; + return selectedParent ? box.parent === selectedParent : !box.parent; + } + + if (draggingOrResizingId) { + if (box.id === draggingOrResizingId) return false; + return dragParentId ? box.parent === dragParentId : !box.parent; + } + + return true; + }) + .map((box) => `.ele-${box.id}`); + setElementGuidelines(guidelines); + }, [boxList, dragParentId, draggingComponentId, resizingComponentId, selectedComponents, getResolvedValue]); useEffect(() => { setBoxList( @@ -344,12 +341,13 @@ export default function Grid({ gridWidth, currentLayout }) { }); }, [selectedComponents]); + const groupedTargets = [...findHighestLevelofSelection().map((component) => '.ele-' + component.id)]; + useEffect(() => { if (moveableRef.current) { moveableRef.current.updateTarget(); } }, [temporaryHeight]); - useEffect(() => { reloadGrid(); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -536,8 +534,9 @@ export default function Grid({ gridWidth, currentLayout }) { const _top = originalBox.top; // Apply transform to return to original position - ev.target.style.transform = `translate(${Math.round(_left / _gridWidth) * _gridWidth}px, ${Math.round(_top / GRID_HEIGHT) * GRID_HEIGHT - }px)`; + ev.target.style.transform = `translate(${Math.round(_left / _gridWidth) * _gridWidth}px, ${ + Math.round(_top / GRID_HEIGHT) * GRID_HEIGHT + }px)`; } }); @@ -600,16 +599,18 @@ export default function Grid({ gridWidth, currentLayout }) { const components = Array.from(document.querySelectorAll('.active-target')).filter( (component) => !selectedComponents.includes(component.getAttribute('widgetid')) ); - const draggingOrResizingComponentId = draggingComponentId || resizingComponentId; - if (!draggingOrResizingComponentId && components.length > 0 && !virtualTarget) { + const draggingOrResizing = draggingComponentId || resizingComponentId; + if (!draggingOrResizing && components.length > 0) { for (const component of components) { component?.classList?.remove('active-target'); } } - }, [draggingComponentId, resizingComponentId, isGroupDragging, selectedComponents, virtualTarget]); + }, [draggingComponentId, resizingComponentId, isGroupDragging, selectedComponents]); useGroupedTargetsScrollHandler(groupedTargets, boxList, moveableRef); + if (mode !== 'edit') return null; + return ( <> 1, }} flushSync={flushSync} - target={getMoveableTarget()} + target={groupedTargets?.length > 1 ? groupedTargets : '.target'} origin={false} - individualGroupable={virtualTarget ? false : groupedTargets.length <= 1} + individualGroupable={groupedTargets.length <= 1} draggable={!shouldFreeze && mode !== 'view'} resizable={ !shouldFreeze @@ -638,7 +639,7 @@ export default function Grid({ gridWidth, currentLayout }) { onResize={(e) => { const temporaryLayouts = getTemporaryLayouts(); if (resizingComponentId !== e.target.id) { - useStore.getState().setResizingComponentId(e.target.id); + useGridStore.getState().actions.setResizingComponentId(e.target.id); showGridLines(); } @@ -647,8 +648,12 @@ export default function Grid({ gridWidth, currentLayout }) { let _gridWidth = useGridStore.getState().subContainerWidths[currentWidget.component?.parent] || gridWidth; // Show grid during resize - showGridLines(); - + if (currentWidget.component?.parent) { + document.getElementById('canvas-' + currentWidget.component?.parent)?.classList.add('show-grid'); + setDragParentId(currentWidget.component?.parent); + } else { + document.getElementById('real-canvas').classList.add('show-grid'); + } handleActivateTargets(currentWidget.component?.parent); const currentWidth = currentWidget.width * _gridWidth; @@ -692,7 +697,6 @@ export default function Grid({ gridWidth, currentLayout }) { e.target.style.transform = `translate(${transformX}px, ${transformY}px)`; if (e.width > 0) e.target.style.width = `${e.width}px`; if (e.height > 0) e.target.style.height = `${e.height}px`; - positionGhostElement(e.target, 'resize-ghost-widget'); }} onResizeStart={(e) => { if ( @@ -711,7 +715,7 @@ export default function Grid({ gridWidth, currentLayout }) { }} onResizeEnd={(e) => { try { - useStore.getState().setResizingComponentId(null); + useGridStore.getState().actions.setResizingComponentId(null); const currentWidget = boxList.find(({ id }) => { return id === e.target.id; }); @@ -748,8 +752,9 @@ export default function Grid({ gridWidth, currentLayout }) { const roundedTransformY = Math.round(transformY / GRID_HEIGHT) * GRID_HEIGHT; transformY = transformY % GRID_HEIGHT === 5 ? roundedTransformY - GRID_HEIGHT : roundedTransformY; - e.target.style.transform = `translate(${Math.round(transformX / _gridWidth) * _gridWidth}px, ${Math.round(transformY / GRID_HEIGHT) * GRID_HEIGHT - }px)`; + e.target.style.transform = `translate(${Math.round(transformX / _gridWidth) * _gridWidth}px, ${ + Math.round(transformY / GRID_HEIGHT) * GRID_HEIGHT + }px)`; if (!maxWidthHit || e.width < e.target.clientWidth) { e.target.style.width = `${Math.round(e.lastEvent.width / _gridWidth) * _gridWidth}px`; } @@ -862,9 +867,6 @@ export default function Grid({ gridWidth, currentLayout }) { }} checkInput onDragStart={(e) => { - if (e.target.id === 'moveable-ghost-element') { - return true; - } // This is to prevent parent component from being dragged and the stop the propagation of the event if (getHoveredComponentForGrid() !== e.target.id) { return false; @@ -917,9 +919,6 @@ export default function Grid({ gridWidth, currentLayout }) { }} onDragEnd={(e) => { handleDeactivateTargets(); - if (e.target.id === 'moveable-ghost-element') { - return; - } try { if (isDraggingRef.current) { useStore.getState().setDraggingComponentId(null); @@ -932,6 +931,7 @@ export default function Grid({ gridWidth, currentLayout }) { setDragParentId(null); if (!e.lastEvent) return; + // Build the drag context from the event const dragContext = dragContextBuilder({ event: e, widgets: boxList, isModuleEditor }); const { target, source, dragged } = dragContext; @@ -978,21 +978,6 @@ export default function Grid({ gridWidth, currentLayout }) { toggleCanvasUpdater(); }} onDrag={(e) => { - if (e.target.id === 'moveable-ghost-element') { - showGridLines(); - const _gridWidth = useGridStore.getState().subContainerWidths[currentDragCanvasId] || gridWidth; - let left = e.translate[0]; - let top = e.translate[1]; - - if (currentDragCanvasId === 'canvas') { - left = Math.round(e.translate[0] / _gridWidth) * _gridWidth; - top = Math.round(e.translate[1] / GRID_HEIGHT) * GRID_HEIGHT; - } - - useGridStore.getState().actions.setGhostDragPosition({ left, top, e }); - e.target.style.transform = `translate(${left}px, ${top}px)`; - return false; - } // Since onDrag is called multiple times when dragging, hence we are using isDraggingRef to prevent setting state again and again if (!isDraggingRef.current) { useStore.getState().setDraggingComponentId(e.target.id); @@ -1061,7 +1046,16 @@ export default function Grid({ gridWidth, currentLayout }) { // This block is to show grid lines on the canvas when the dragged element is over a new canvas if (document.elementFromPoint(e.clientX, e.clientY)) { - let newParentId = findNewParentIdFromMousePosition(e.clientX, e.clientY, e.target.id); + const targetElems = document.elementsFromPoint(e.clientX, e.clientY); + const draggedOverElements = targetElems.filter( + (ele) => + (ele.id !== e.target.id && ele.classList.contains('target')) || ele.classList.contains('real-canvas') + ); + const draggedOverElem = draggedOverElements.find((ele) => ele.classList.contains('target')); + const draggedOverContainer = draggedOverElements.find((ele) => ele.classList.contains('real-canvas')); + + // Determine potential new parent + let newParentId = draggedOverContainer?.getAttribute('data-parentId') || draggedOverElem?.id; if (newParentId === e.target.id) { newParentId = boxList.find((box) => box.id === e.target.id)?.component?.parent; @@ -1089,7 +1083,7 @@ export default function Grid({ gridWidth, currentLayout }) { `translate: ${e.translate[0]} | Round: ${Math.round(e.translate[0] / gridWidth) * gridWidth} | ${gridWidth}` ); - positionGhostElement(e.target, 'moveable-drag-ghost'); + positionDragGhostWidget(e.target); }} onDragGroup={(ev) => { const { events } = ev; @@ -1172,10 +1166,8 @@ export default function Grid({ gridWidth, currentLayout }) { component.element.classList.add('active-target'); } }} - // snapGridAll={true} + snapGridAll={true} scrollable={true} - // snapContainer={snapContainer} - // snapGridWidth={100} /> ); diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js index 1a3bc36b64..07a56a3c4c 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js @@ -530,58 +530,24 @@ export const getDraggingWidgetWidth = (canvasParentId, widgetWidth) => { return draggingWidgetWidth; }; -/** - * Positions a ghost/feedback element relative to the main canvas - * @param {HTMLElement} targetElement - The element being dragged/resized - * @param {string} ghostElementId - The ID of the ghost element to position - * @param {Object} options - Additional positioning options - * @param {boolean} options.includeSize - Whether to update width/height of ghost element - */ -export const positionGhostElement = (targetElement, ghostElementId) => { - const ghostElement = document.getElementById(ghostElementId); +export const positionDragGhostWidget = (draggedElement) => { + const ghostElement = document.getElementById('moveable-drag-ghost'); - if (!ghostElement || !targetElement) return; + if (!ghostElement || !draggedElement) return; const mainCanvas = document.getElementById('real-canvas'); if (!mainCanvas) return; const mainCanvasRect = mainCanvas.getBoundingClientRect(); - const targetRect = targetElement.getBoundingClientRect(); + const draggedRect = draggedElement.getBoundingClientRect(); // Calculate position relative to main canvas - const relativeLeft = targetRect.left - mainCanvasRect.left; - const relativeTop = targetRect.top - mainCanvasRect.top; + const relativeLeft = draggedRect.left - mainCanvasRect.left; + const relativeTop = draggedRect.top - mainCanvasRect.top; // Apply the position ghostElement.style.left = `${relativeLeft}px`; ghostElement.style.top = `${relativeTop}px`; - ghostElement.style.width = `${targetRect.width}px`; - ghostElement.style.height = `${targetRect.height}px`; -}; - - -/** - * Finds the new parent ID based on the current mouse position during drag operations - * @param {number} clientX - The X coordinate of the mouse position - * @param {number} clientY - The Y coordinate of the mouse position - * @param {string} currentTargetId - The ID of the currently dragged element to exclude from search - * @returns {string|null} - The new parent ID or null if no valid parent is found - */ -export const findNewParentIdFromMousePosition = (clientX, clientY, currentTargetId) => { - if (!document.elementFromPoint(clientX, clientY)) { - return null; - } - - const targetElems = document.elementsFromPoint(clientX, clientY); - const draggedOverElements = targetElems.filter( - (ele) => (ele.id !== currentTargetId && ele.classList.contains('target')) || ele.classList.contains('real-canvas') - ); - - const draggedOverElem = draggedOverElements.find((ele) => ele.classList.contains('target')); - const draggedOverContainer = draggedOverElements.find((ele) => ele.classList.contains('real-canvas')); - - // Determine potential new parent - const newParentId = draggedOverContainer?.getAttribute('data-parentId') || draggedOverElem?.id; - - return newParentId || null; + ghostElement.style.width = `${draggedRect.width}px`; + ghostElement.style.height = `${draggedRect.height}px`; }; diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js b/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js deleted file mode 100644 index 55587b33fd..0000000000 --- a/frontend/src/AppBuilder/AppCanvas/Grid/hooks/useElementGudelines.js +++ /dev/null @@ -1,72 +0,0 @@ -import { useEffect, useState } from 'react'; -import { findHighestLevelofSelection } from '../gridUtils'; -import { useGridStore } from '@/_stores/gridStore'; -import useStore from '@/AppBuilder/_stores/store'; - -export const useElementGudelines = (boxList, selectedComponents, dragParentId, getResolvedValue, virtualTarget) => { - const [elementGuidelines, setElementGuidelines] = useState([]); - const draggingComponentId = useStore((state) => state.draggingComponentId); - const resizingComponentId = useStore((state) => state.resizingComponentId); - const currentDragCanvasId = useGridStore((state) => state.currentDragCanvasId); - - useEffect(() => { - const selectedSet = new Set(selectedComponents); - const draggingOrResizingId = draggingComponentId || resizingComponentId; - const isGrouped = findHighestLevelofSelection().length > 1; - const firstSelectedParent = - selectedComponents.length > 0 ? boxList.find((b) => b.id === selectedComponents[0])?.parent : null; - const selectedParent = dragParentId || firstSelectedParent; - const isAnyModalOpen = document.querySelector('#modal-container') ? true : false; - - const guidelines = boxList - .filter((box) => { - const isVisible = - getResolvedValue(box?.component?.definition?.properties?.visibility?.value) || - getResolvedValue(box?.component?.definition?.styles?.visibility?.value); - - // Early return for non-visible elements - if (!isVisible) return false; - - // Don't show guidelines for components which are outside the modal specially on main canvas - if (virtualTarget && isAnyModalOpen) { - if (box.parent === 'canvas' || !box.parent) return false; - } - - // This block is for first time drop using react-dnd - if (virtualTarget && currentDragCanvasId !== null) { - if (currentDragCanvasId === 'canvas') { - if (box.parent && box.parent !== 'canvas') return false; - } else { - // For sub-containers, only show components whose parent matches the canvasId - if (box.parent !== currentDragCanvasId) return false; - } - } - - if (isGrouped) { - // If component is selected, don't show its guidelines - if (selectedSet.has(box.id)) return false; - return selectedParent ? box.parent === selectedParent : !box.parent; - } - - if (draggingOrResizingId) { - if (box.id === draggingOrResizingId) return false; - return dragParentId ? box.parent === dragParentId : !box.parent; - } - - return true; - }) - .map((box) => `.ele-${box.id}`); - setElementGuidelines(guidelines); - }, [ - boxList, - dragParentId, - draggingComponentId, - resizingComponentId, - selectedComponents, - getResolvedValue, - currentDragCanvasId, - virtualTarget, - ]); - - return { elementGuidelines }; -}; diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 954be5607a..057781f8ec 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -1,8 +1,9 @@ import React, { memo } from 'react'; import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; -import { ResizeGhostWidget } from './GhostWidgets'; +import { DragGhostWidget, ResizeGhostWidget } from './GhostWidgets'; import { ConfigHandle } from './ConfigHandle/ConfigHandle'; +import { useGridStore } from '@/_stores/gridStore'; import cx from 'classnames'; import RenderWidget from './RenderWidget'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; @@ -34,7 +35,7 @@ const WidgetWrapper = memo( const temporaryLayouts = useStore((state) => state.temporaryLayouts?.[id], shallow); const isWidgetActive = useStore((state) => state.selectedComponents.find((sc) => sc === id) && !readOnly, shallow); const isDragging = useStore((state) => state.draggingComponentId === id); - const isResizing = useStore((state) => state.resizingComponentId === id); + const isResizing = useGridStore((state) => state.resizingComponentId === id); const componentType = useStore( (state) => state.getComponentDefinition(id, moduleId)?.component?.component, shallow diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index debc47c61e..c689adb51c 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -5,7 +5,7 @@ import useStore from '@/AppBuilder/_stores/store'; import { toast } from 'react-hot-toast'; import _, { debounce } from 'lodash'; import { useGridStore } from '@/_stores/gridStore'; -import { findHighestLevelofSelection, getMouseDistanceFromParentDiv } from './Grid/gridUtils'; +import { findHighestLevelofSelection } from './Grid/gridUtils'; import { CANVAS_WIDTHS, NO_OF_GRIDS, @@ -29,32 +29,32 @@ export function snapToGrid(canvasWidth, x, y) { //TODO: componentTypes should be a key value pair and get the definition directly by passing the componentType export const addNewWidgetToTheEditor = ( componentType, + eventMonitorObject, currentLayout, realCanvasRef, parentId, moduleInfo = undefined ) => { - const canvasBoundingRect = realCanvasRef?.getBoundingClientRect(); + const canvasBoundingRect = realCanvasRef?.current?.getBoundingClientRect(); const componentMeta = componentTypes.find((component) => component.component === componentType); const componentName = computeComponentName(componentType, useStore.getState().getCurrentPageComponents()); - const parentCanvasType = realCanvasRef?.getAttribute('component-type'); + const componentData = deepClone(componentMeta); const defaultWidth = componentData.defaultSize.width; const defaultHeight = componentData.defaultSize.height; - const { e } = useGridStore.getState().getGhostDragPosition(); + const offsetFromTopOfWindow = canvasBoundingRect?.top; + const offsetFromLeftOfWindow = canvasBoundingRect?.left; + const currentOffset = eventMonitorObject?.getSourceClientOffset(); const subContainerWidth = canvasBoundingRect?.width; - const { left: _left, top: _top } = getMouseDistanceFromParentDiv( - e, - parentId === 'canvas' ? 'real-canvas' : parentId, - parentCanvasType - ); - let [left, top] = snapToGrid(subContainerWidth, _left, _top); + let left = Math.round(currentOffset?.x - offsetFromLeftOfWindow); + let top = Math.round(currentOffset?.y - offsetFromTopOfWindow); + + [left, top] = snapToGrid(subContainerWidth, left, top); const gridWidth = subContainerWidth / NO_OF_GRIDS; left = Math.round(left / gridWidth); - // Adjust widget width based on the dropping canvas width const mainCanvasWidth = useGridStore.getState().subContainerWidths['canvas']; let width = Math.round((defaultWidth * mainCanvasWidth) / gridWidth); @@ -85,7 +85,6 @@ export const addNewWidgetToTheEditor = ( left = Math.max(0, NO_OF_GRIDS - width); width = Math.min(width, NO_OF_GRIDS); } - if (currentLayout === 'mobile') { componentData.definition.others.showOnDesktop.value = `{{false}}`; componentData.definition.others.showOnMobile.value = `{{true}}`; diff --git a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js b/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js deleted file mode 100644 index 05a9a36502..0000000000 --- a/frontend/src/AppBuilder/AppCanvas/useCanvasDropHandler.js +++ /dev/null @@ -1,113 +0,0 @@ -import useStore from '@/AppBuilder/_stores/store'; -import { useGridStore } from '@/_stores/gridStore'; -import { shallow } from 'zustand/shallow'; -import { noop } from 'lodash'; -import { - addChildrenWidgetsToParent, - addNewWidgetToTheEditor, - addDefaultButtonIdToForm, -} from '../AppCanvas/appCanvasUtils'; -import { WIDGETS_WITH_DEFAULT_CHILDREN } from '../AppCanvas/appCanvasConstants'; -import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants'; -import { isPDFSupported } from '@/_helpers/appUtils'; -import toast from 'react-hot-toast'; -import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; -import { handleDeactivateTargets, hideGridLines } from '../AppCanvas/Grid/gridUtils'; - -export const useCanvasDropHandler = ({ appType }) => { - const { moduleId } = useModuleContext(); - - const addComponentToCurrentPage = useStore((state) => state.addComponentToCurrentPage, shallow); - const setActiveRightSideBarTab = useStore((state) => state.setActiveRightSideBarTab, shallow); - const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; - const currentMode = useStore((state) => state.modeStore.modules[moduleId].currentMode, shallow); - const currentLayout = useStore((state) => state.currentLayout, shallow); - const setCurrentDragCanvasId = useGridStore((state) => state.actions.setCurrentDragCanvasId); - - const handleDrop = async ({ componentType: draggedComponentType, component }, canvasId) => { - const realCanvasRef = - !canvasId || canvasId === 'canvas' - ? document.getElementById(`real-canvas`) - : document.getElementById(`canvas-${canvasId}`); - - handleDeactivateTargets(); - hideGridLines(); - - setShowModuleBorder(false); // Hide the module border when dropping - - if (currentMode === 'view' || (appType === 'module' && draggedComponentType !== 'ModuleContainer')) { - return; - } - - if (draggedComponentType === 'PDF' && !isPDFSupported()) { - toast.error( - 'PDF is not supported in this version of browser. We recommend upgrading to the latest version for full support.' - ); - return; - } - - // IMPORTANT: This logic needs to be changed when we implement the module versioning - const moduleInfo = component?.moduleId - ? { - moduleId: component.moduleId, - versionId: component.versionId, - environmentId: component.environmentId, - moduleName: component.displayName, - moduleContainer: component.moduleContainer, - } - : undefined; - - let addedComponent; - - if (WIDGETS_WITH_DEFAULT_CHILDREN.includes(draggedComponentType)) { - let parentComponent = addNewWidgetToTheEditor( - draggedComponentType, - currentLayout, - realCanvasRef, - canvasId, - moduleInfo - ); - const childComponents = addChildrenWidgetsToParent(draggedComponentType, parentComponent?.id, currentLayout); - if (draggedComponentType === 'Form') { - parentComponent = addDefaultButtonIdToForm(parentComponent, childComponents); - } - addedComponent = [parentComponent, ...childComponents]; - await addComponentToCurrentPage(addedComponent); - } else { - const newComponent = addNewWidgetToTheEditor( - draggedComponentType, - currentLayout, - realCanvasRef, - canvasId, - moduleInfo - ); - addedComponent = [newComponent]; - await addComponentToCurrentPage(addedComponent); - } - - setActiveRightSideBarTab(RIGHT_SIDE_BAR_TAB.CONFIGURATION); - - const canvas = document.querySelector('.canvas-container'); - const sidebar = document.querySelector('.editor-sidebar'); - const droppedElem = document.getElementById(addedComponent?.[0]?.id); - - if (!canvas || !sidebar || !droppedElem) return; - - const droppedRect = droppedElem.getBoundingClientRect(); - const sidebarRect = sidebar.getBoundingClientRect(); - - const isOverlapping = droppedRect.right > sidebarRect.left && droppedRect.left < sidebarRect.right; - - if (isOverlapping) { - const overlap = droppedRect.right - sidebarRect.left; - canvas.scrollTo({ - left: canvas.scrollLeft + overlap, - behavior: 'smooth', - }); - } - // Reset canvas ID when dropping - setCurrentDragCanvasId(null); - }; - - return { handleDrop }; -}; diff --git a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx index 73ba1cda16..16b8a74f77 100644 --- a/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx +++ b/frontend/src/AppBuilder/RightSideBar/ComponentManagerTab/DragLayer.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react'; +import React, { useEffect } from 'react'; import { WidgetBox } from '../WidgetBox'; import { ModuleWidgetBox } from '@/modules/Modules/components'; import { useDrag, useDragLayer } from 'react-dnd'; @@ -9,8 +9,6 @@ import useStore from '@/AppBuilder/_stores/store'; import { shallow } from 'zustand/shallow'; import { useModuleContext } from '@/AppBuilder/_contexts/ModuleContext'; import { noop } from 'lodash'; -import { useGridStore } from '@/_stores/gridStore'; -import { useCanvasDropHandler } from '@/AppBuilder/AppCanvas/useCanvasDropHandler'; export const DragLayer = ({ index, component, isModuleTab = false, disabled = false }) => { const [isRightSidebarOpen, toggleRightSidebar] = useStore( @@ -20,20 +18,11 @@ export const DragLayer = ({ index, component, isModuleTab = false, disabled = fa const isRightSidebarPinned = useStore((state) => state.isRightSidebarPinned); const { isModuleEditor } = useModuleContext(); const setShowModuleBorder = useStore((state) => state.setShowModuleBorder, shallow) || noop; - const { handleDrop } = useCanvasDropHandler({ appType: isModuleTab ? 'module' : 'app' }) || noop; - const [{ isDragging }, drag, preview] = useDrag( () => ({ type: 'box', item: { componentType: component.component, component }, collect: (monitor) => ({ isDragging: monitor.isDragging() }), - end: (item, monitor) => { - const clientOffset = monitor.getClientOffset(); - const currentDragCanvasId = useGridStore.getState().currentDragCanvasId; - if (clientOffset) { - handleDrop(item, currentDragCanvasId); - } - }, }), [component.component] ); @@ -57,14 +46,80 @@ export const DragLayer = ({ index, component, isModuleTab = false, disabled = fa // ? component.module_container.layouts[currentLayout] // : component.defaultSize || { width: 30, height: 40 }; + const size = component.defaultSize || { width: 30, height: 40 }; + return ( <> + {isDragging && }
- {isModuleTab ? : } + style={{ height: '100%', width: isModuleTab && '100%' }} + > + {isModuleTab ? ( + + ) : ( + + )}
); -}; \ No newline at end of file +}; + +const CustomDragLayer = ({ size }) => { + const { currentOffset, item } = useDragLayer((monitor) => ({ + currentOffset: monitor.getSourceClientOffset(), + item: monitor.getItem(), + })); + console.log(currentOffset, 'currentOffset'); + if (!currentOffset) return null; + + const canvasWidth = item?.canvasWidth; + const canvasBounds = item?.canvasRef?.getBoundingClientRect(); + const height = size.height; + + const appCanvasWidth = document.getElementById('real-canvas')?.offsetWidth || 0; + + // Calculate width based on the app canvas's grid + let width = (appCanvasWidth * size.width) / NO_OF_GRIDS; + + // Calculate position relative to the current canvas (parent or child) + const left = currentOffset.x - (canvasBounds?.left || 0); + const top = currentOffset.y - (canvasBounds?.top || 0); + + // Ensure width doesn't exceed the current container's width + if (width > canvasWidth) { + width = canvasWidth; + } + + // Snap width to grid (round to nearest grid unit) + const gridUnitWidth = canvasWidth / NO_OF_GRIDS; + const gridUnits = Math.round(width / gridUnitWidth); + width = gridUnits * gridUnitWidth; + + const [x, y] = snapToGrid(canvasWidth, left, top); + return ( +
+
+
+ ); +}; diff --git a/frontend/src/AppBuilder/_hooks/useDropVirtualMoveableGhost.js b/frontend/src/AppBuilder/_hooks/useDropVirtualMoveableGhost.js deleted file mode 100644 index e2795235eb..0000000000 --- a/frontend/src/AppBuilder/_hooks/useDropVirtualMoveableGhost.js +++ /dev/null @@ -1,101 +0,0 @@ -import { useRef } from 'react'; -import { useGridStore } from '@/_stores/gridStore'; - -export const useDropVirtualMoveableGhost = () => { - const ghostElementRef = useRef(null); - const isActiveRef = useRef(false); - - const getMoveableRef = useGridStore((state) => state.moveableRef); - const setVirtualTarget = useGridStore((state) => state.actions.setVirtualTarget); - - const createGhostMoveElement = (componentSize) => { - if (ghostElementRef.current) return; - - const ghost = document.createElement('div'); - ghost.id = 'moveable-ghost-element'; - ghost.className = 'moveable-ghost target'; - ghost.style.cssText = ` - position: absolute; - width: ${componentSize.width || 100}px; - height: ${componentSize.height || 40}px; - background: #D9E2FC; - opacity: 0.7; - pointer-events: none; - z-index: 9998; - box-sizing: border-box; - top: 0; - left: 0; - `; - - const container = document.getElementById('real-canvas'); - container.appendChild(ghost); - ghostElementRef.current = ghost; - - return ghost; - }; - - const updateMoveableGhostPosition = (mousePosition, canvasRef) => { - if (!ghostElementRef.current || !canvasRef?.current || !mousePosition) return; - - const canvasRect = canvasRef.current.getBoundingClientRect(); - const relativeX = mousePosition.x - canvasRect.left; - const relativeY = mousePosition.y - canvasRect.top; - - ghostElementRef.current.style.transform = `translate(${relativeX}px, ${relativeY}px)`; - }; - - const activateMoveableGhost = (componentSize, mousePosition, canvasRef) => { - if (isActiveRef.current) return; - - isActiveRef.current = true; - - const ghost = createGhostMoveElement(componentSize, canvasRef); - if (ghost && mousePosition) { - updateMoveableGhostPosition(mousePosition, canvasRef); - - // Trigger moveable drag on the ghost element to show guidelines - const moveableInstance = getMoveableRef; - if (moveableInstance && ghost) { - try { - const fakeEvent = new MouseEvent('mousedown', { - clientX: mousePosition.x, - clientY: mousePosition.y, - bubbles: true, - cancelable: true, - view: window, - button: 0, - buttons: 1, - }); - moveableInstance.waitToChangeTarget().then((e) => { - moveableInstance.dragStart(fakeEvent, ghost); - }); - setVirtualTarget(ghost); - } catch (error) { - console.warn('Failed to trigger moveable dragStart:', error); - } - } - } - }; - - const deactivateMoveableGhost = () => { - if (!isActiveRef.current) return; - - isActiveRef.current = false; - - const moveableInstance = getMoveableRef; - if (moveableInstance && ghostElementRef.current) { - try { - setVirtualTarget(null); - ghostElementRef.current.remove(); - ghostElementRef.current = null; - } catch (error) { - console.warn('Failed to trigger moveable dragEnd:', error); - } - } - }; - - return { - activateMoveableGhost, - deactivateMoveableGhost, - }; -}; diff --git a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js b/frontend/src/AppBuilder/_hooks/useGhostMoveable.js deleted file mode 100644 index 0e5adaa9c1..0000000000 --- a/frontend/src/AppBuilder/_hooks/useGhostMoveable.js +++ /dev/null @@ -1,107 +0,0 @@ -import { useRef } from 'react'; -import { useGridStore } from '@/_stores/gridStore'; -import { NO_OF_GRIDS, GRID_HEIGHT } from '@/AppBuilder/AppCanvas/appCanvasConstants'; -import { snapToGrid } from '@/AppBuilder/AppCanvas/appCanvasUtils'; - -export const useGhostMoveable = () => { - const ghostElementRef = useRef(null); - const isActiveRef = useRef(false); - - const getMoveableRef = useGridStore((state) => state.moveableRef); - const setVirtualTarget = useGridStore((state) => state.actions.setVirtualTarget); - - const createGhostElement = (componentSize) => { - if (ghostElementRef.current) return; - - const ghost = document.createElement('div'); - ghost.id = 'moveable-ghost-element'; - ghost.className = 'moveable-ghost target'; - ghost.style.cssText = ` - position: absolute; - width: ${componentSize.width || 100}px; - height: ${componentSize.height || 40}px; - background: #D9E2FC; - opacity: 0.7; - pointer-events: none; - z-index: 9998; - box-sizing: border-box; - top: 0; - left: 0; - `; - - const container = document.getElementById('real-canvas'); - container.appendChild(ghost); - ghostElementRef.current = ghost; - - return ghost; - }; - - const updateGhostPosition = (mousePosition, canvasRef) => { - if (!ghostElementRef.current || !canvasRef?.current || !mousePosition) return; - - const canvasRect = canvasRef.current.getBoundingClientRect(); - const relativeX = mousePosition.x - canvasRect.left; - const relativeY = mousePosition.y - canvasRect.top; - - // Apply grid snapping - // const gridWidth = canvasRef.current.offsetWidth / NO_OF_GRIDS; - // const snappedX = Math.round(relativeX / gridWidth) * gridWidth; - // const snappedY = Math.round(relativeY / GRID_HEIGHT) * GRID_HEIGHT; - ghostElementRef.current.style.transform = `translate(${relativeX}px, ${relativeY}px)`; - }; - - const activateGhost = (componentSize, mousePosition, canvasRef) => { - if (isActiveRef.current) return; - - isActiveRef.current = true; - - const ghost = createGhostElement(componentSize, canvasRef); - if (ghost && mousePosition) { - updateGhostPosition(mousePosition, canvasRef); - - // Trigger moveable drag on the ghost element to show guidelines - const moveableInstance = getMoveableRef; - if (moveableInstance && ghost) { - try { - const fakeEvent = new MouseEvent('mousedown', { - clientX: mousePosition.x, - clientY: mousePosition.y, - bubbles: true, - cancelable: true, - view: window, - button: 0, - buttons: 1, - }); - moveableInstance.waitToChangeTarget().then((e) => { - moveableInstance.dragStart(fakeEvent, ghost); - }); - setVirtualTarget(ghost); - } catch (error) { - console.warn('Failed to trigger moveable dragStart:', error); - } - } - } - }; - - const deactivateGhost = () => { - if (!isActiveRef.current) return; - - isActiveRef.current = false; - - const moveableInstance = getMoveableRef; - if (moveableInstance && ghostElementRef.current) { - try { - setVirtualTarget(null); - ghostElementRef.current.remove(); - ghostElementRef.current = null; - } catch (error) { - console.warn('Failed to trigger moveable dragEnd:', error); - } - } - }; - - return { - activateGhost, - deactivateGhost, - }; -}; diff --git a/frontend/src/AppBuilder/_stores/slices/gridSlice.js b/frontend/src/AppBuilder/_stores/slices/gridSlice.js index 6a74c12049..9c4d0e1203 100644 --- a/frontend/src/AppBuilder/_stores/slices/gridSlice.js +++ b/frontend/src/AppBuilder/_stores/slices/gridSlice.js @@ -10,12 +10,10 @@ const initialState = { lastCanvasClickPosition: null, temporaryLayouts: {}, draggingComponentId: null, - resizingComponentId: null, reorderContainerChildren: { containerId: null, triggerUpdate: 0, }, - shouldPreventDrop: false, }; export const createGridSlice = (set, get) => ({ @@ -37,7 +35,6 @@ export const createGridSlice = (set, get) => ({ get().toggleCanvasUpdater(); }, 200), setDraggingComponentId: (id) => set(() => ({ draggingComponentId: id })), - setResizingComponentId: (id) => set(() => ({ resizingComponentId: id })), moveComponentPosition: (direction) => { const { setComponentLayout, currentLayout, getSelectedComponentsDefinition, debouncedToggleCanvasUpdater } = get(); let layouts = {}; @@ -473,7 +470,4 @@ export const createGridSlice = (set, get) => ({ reorderContainerChildren: { containerId, triggerUpdate: state.reorderContainerChildren.triggerUpdate + 1 }, })); }, - setShouldPreventDrop: (shouldPreventDrop) => { - set(() => ({ shouldPreventDrop })); - }, }); diff --git a/frontend/src/_stores/gridStore.js b/frontend/src/_stores/gridStore.js index 6b9822f6dd..213f07ac16 100644 --- a/frontend/src/_stores/gridStore.js +++ b/frontend/src/_stores/gridStore.js @@ -12,15 +12,11 @@ const initialState = { idGroupDragged: false, openModalWidgetId: null, subContainerWidths: {}, - moveableRef: null, - virtualTarget: null, - currentDragCanvasId: null, - ghostDragPosition: null, }; export const useGridStore = create( zustandDevTools( - (set, get) => ({ + (set) => ({ ...initialState, actions: { setResizingComponentId: (id) => set({ resizingComponentId: id }), @@ -30,22 +26,7 @@ export const useGridStore = create( setOpenModalWidgetId: (openModalWidgetId) => set({ openModalWidgetId }), setSubContainerWidths: (id, width) => set((state) => ({ subContainerWidths: { ...state.subContainerWidths, [id]: width } })), - setVirtualTarget: (target) => set({ virtualTarget: target }), - setCurrentDragCanvasId: (canvasId) => set({ currentDragCanvasId: canvasId }), - setGhostDragPosition: (position) => set({ ghostDragPosition: position }), }, - addToElementGuidelines: (selector) => - set((state) => ({ - dynamicElementGuidelines: [...state.dynamicElementGuidelines, selector], - })), - removeFromElementGuidelines: (selector) => - set((state) => ({ - dynamicElementGuidelines: state.dynamicElementGuidelines.filter((item) => item !== selector), - })), - clearDynamicElementGuidelines: () => set({ dynamicElementGuidelines: [] }), - setMoveableRef: (ref) => set({ moveableRef: ref }), - - getGhostDragPosition: () => get().ghostDragPosition, }), { name: 'Grid Store' } ) diff --git a/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx b/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx index 0da388a0eb..160799dc9d 100644 --- a/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx +++ b/frontend/src/modules/common/components/BaseColorSwatches/BaseColorSwatches.jsx @@ -116,6 +116,7 @@ const BaseColorSwatches = ({ ); }; const ColorPickerInputBox = () => { + console.log('onReset', onReset); return (
Date: Wed, 9 Jul 2025 01:49:01 +0530 Subject: [PATCH 32/33] Commented the automation from platfoem and marketplace. --- cypress-tests/cypress-marketplace.config.js | 7 ++++--- cypress-tests/cypress-platform.config.js | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cypress-tests/cypress-marketplace.config.js b/cypress-tests/cypress-marketplace.config.js index b8ffbeaa26..d00572d8a9 100644 --- a/cypress-tests/cypress-marketplace.config.js +++ b/cypress-tests/cypress-marketplace.config.js @@ -75,9 +75,10 @@ module.exports = defineConfig({ experimentalModfyObstructiveThirdPartyCode: true, experimentalRunAllSpecs: true, baseUrl: "http://localhost:8082", - specPattern: [ - "cypress/e2e/happyPath/marketplace/commonTestcases/**/*.cy.js", - ], + // specPattern: [ + // "cypress/e2e/happyPath/marketplace/commonTestcases/**/*.cy.js", + // ] + specPattern: [ ], numTestsKeptInMemory: 1, redirectionLimit: 7, experimentalRunAllSpecs: true, diff --git a/cypress-tests/cypress-platform.config.js b/cypress-tests/cypress-platform.config.js index cb3575906a..d5642be785 100644 --- a/cypress-tests/cypress-platform.config.js +++ b/cypress-tests/cypress-platform.config.js @@ -96,16 +96,17 @@ module.exports = defineConfig({ experimentalModfyObstructiveThirdPartyCode: true, baseUrl: environment.baseUrl, configFile: environment.configFile, - specPattern: [ - "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js", - // Exclude specific files from ceTestcases/apps and ceTestcases/workspace - "cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js", - "cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug|appImport|privateAndpublicApps|version).cy.js", - // Exclude workspaceConstants.cy.js explicitly - "cypress/e2e/happyPath/platform/ceTestcases/workspace/!(*groupDuplication|workspaceConstants).cy.js", - "!cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js", - "cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js", - ], + // specPattern: [ + // "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js", + // // Exclude specific files from ceTestcases/apps and ceTestcases/workspace + // "cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js", + // "cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug|appImport|privateAndpublicApps|version).cy.js", + // // Exclude workspaceConstants.cy.js explicitly + // "cypress/e2e/happyPath/platform/ceTestcases/workspace/!(*groupDuplication|workspaceConstants).cy.js", + // "!cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js", + // "cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js", + // ] + specPattern: [ ], numTestsKeptInMemory: 1, redirectionLimit: 15, experimentalMemoryManagement: true, From 56dee6dc4355e321f094fb2c333f0e3108e91cad Mon Sep 17 00:00:00 2001 From: Midhun Kumar E Date: Wed, 9 Jul 2025 10:10:03 +0530 Subject: [PATCH 33/33] Reveredt automation skip. --- cypress-tests/cypress-marketplace.config.js | 7 +++---- cypress-tests/cypress-platform.config.js | 21 ++++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/cypress-tests/cypress-marketplace.config.js b/cypress-tests/cypress-marketplace.config.js index d00572d8a9..ce955b3c66 100644 --- a/cypress-tests/cypress-marketplace.config.js +++ b/cypress-tests/cypress-marketplace.config.js @@ -75,10 +75,9 @@ module.exports = defineConfig({ experimentalModfyObstructiveThirdPartyCode: true, experimentalRunAllSpecs: true, baseUrl: "http://localhost:8082", - // specPattern: [ - // "cypress/e2e/happyPath/marketplace/commonTestcases/**/*.cy.js", - // ] - specPattern: [ ], + specPattern: [ + "cypress/e2e/happyPath/marketplace/commonTestcases/**/*.cy.js", + ] numTestsKeptInMemory: 1, redirectionLimit: 7, experimentalRunAllSpecs: true, diff --git a/cypress-tests/cypress-platform.config.js b/cypress-tests/cypress-platform.config.js index d5642be785..cb3575906a 100644 --- a/cypress-tests/cypress-platform.config.js +++ b/cypress-tests/cypress-platform.config.js @@ -96,17 +96,16 @@ module.exports = defineConfig({ experimentalModfyObstructiveThirdPartyCode: true, baseUrl: environment.baseUrl, configFile: environment.configFile, - // specPattern: [ - // "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js", - // // Exclude specific files from ceTestcases/apps and ceTestcases/workspace - // "cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js", - // "cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug|appImport|privateAndpublicApps|version).cy.js", - // // Exclude workspaceConstants.cy.js explicitly - // "cypress/e2e/happyPath/platform/ceTestcases/workspace/!(*groupDuplication|workspaceConstants).cy.js", - // "!cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js", - // "cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js", - // ] - specPattern: [ ], + specPattern: [ + "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js", + // Exclude specific files from ceTestcases/apps and ceTestcases/workspace + "cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js", + "cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug|appImport|privateAndpublicApps|version).cy.js", + // Exclude workspaceConstants.cy.js explicitly + "cypress/e2e/happyPath/platform/ceTestcases/workspace/!(*groupDuplication|workspaceConstants).cy.js", + "!cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js", + "cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js", + ], numTestsKeptInMemory: 1, redirectionLimit: 15, experimentalMemoryManagement: true,