This commit is contained in:
Nakul Nagargade 2025-06-25 22:44:04 +05:30
parent 768cca40ac
commit 9a0430a94a
9 changed files with 176 additions and 226 deletions

View file

@ -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": [

View file

@ -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 }),

View file

@ -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}
/>
</>
);

View file

@ -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());

View file

@ -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;
};

View file

@ -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 && <CustomDragLayer size={size} />} */}
@ -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 ? <ModuleWidgetBox module={component} /> : <WidgetBox index={index} component={component} />}
</div>
</>
);
};
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 (
<div
style={{
position: 'fixed',
pointerEvents: 'none',
left: canvasBounds?.left || 0,
top: canvasBounds?.top || 0,
height: `${height}px`,
width: `${width}px`,
zIndex: -1,
}}
>
<div
style={{
transform: `translate(${x}px, ${y}px)`,
background: '#D9E2FC',
opacity: '0.7',
height: '100%',
width: '100%',
outline: '1px solid #4af',
}}
></div>
</div>
);
};

View file

@ -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);
}

View file

@ -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 }));
},
});

View file

@ -20645,4 +20645,4 @@
}
}
}
}
}