From 05cfbe033782e96ccbddc1130d568c140e3d6fbf Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 29 May 2025 20:39:39 +0530 Subject: [PATCH 01/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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 a77c54c73646ef01a436880b583b9803ebf62465 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Mon, 7 Jul 2025 19:02:51 +0530 Subject: [PATCH 15/18] 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 16/18] 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 890fc2aa4e871ff34dd8ea9fab2cca483a41052e Mon Sep 17 00:00:00 2001 From: johnsoncherian Date: Tue, 8 Jul 2025 15:03:45 +0530 Subject: [PATCH 17/18] 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 4a45a8523aec5561425091a274ddb17f9a6201a6 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Tue, 8 Jul 2025 17:05:49 +0530 Subject: [PATCH 18/18] 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