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, + }; +};