From 7b0fda5037d92b62bc736cae6fd0779b8b478981 Mon Sep 17 00:00:00 2001 From: Nakul Nagargade Date: Thu, 27 Feb 2025 15:04:25 +0530 Subject: [PATCH] [feat] : Highlight when the component will be placed inside the container based components --- .../src/AppBuilder/AppCanvas/Grid/Grid.css | 69 ++++------------- .../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 19 ++++- .../AppBuilder/AppCanvas/Grid/gridUtils.js | 76 ++++++++++++++++++- .../AppBuilder/AppCanvas/WidgetWrapper.jsx | 1 + .../AppBuilder/AppCanvas/appCanvasUtils.js | 16 ++++ .../src/AppBuilder/AppCanvas/selecto.scss | 19 +++-- 6 files changed, 135 insertions(+), 65 deletions(-) diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css index 5abcc430d4..e1c6bb3baa 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css @@ -1,17 +1,6 @@ .target, .nested-target { position: absolute; - /* width: 100px; - height: 100px; */ - /* top: 150px; - left: 100px; */ - /* line-height: 100px; */ - /* text-align: center; */ - /* background: #ee8; */ - /* color: #333; */ - /* font-weight: bold; */ box-sizing: border-box; - /* transition: transform 0.1s; */ - /* z-index: 3001; */ } .target.hovered{ @@ -76,43 +65,6 @@ background: #8DA4EF !important; } - - -/* Hides all the control lines*/ -/* .moveable-line { - color: transparent !important; - --moveable-color: transparent !important; -} - -.moveable-control { - visibility: hidden; -} - -.target { - outline: 1px solid #4af; -} */ - - -.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover { - outline: 1px solid #4af; - z-index: 4 !important; -} - -.main-editor-canvas .widget-target:has(.nested-target:hover):hover { - outline: 0px solid #4af; -} - - -.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { - outline: 1px solid #4af; - z-index: 4 !important; -} - -.active-target, .resizing-target { - outline: 1px solid #4af !important; - /* z-index: 1000000 !important; */ -} - .moveable-control-box:not([data-able-groupable]) .moveable-control-box:not(:hover) { opacity: 0; } @@ -141,10 +93,6 @@ height: 0px !important; } -.resizing-target * { - opacity: 0; -} - .moveable-control { width: 8px !important; @@ -210,4 +158,19 @@ .moveable-guideline-group { z-index: 9999; -} \ No newline at end of file +} + +.dragging-component-canvas { + outline: 1px solid var(--border-accent-strong) !important; + outline-offset: 0px; /* Creates space between element and outline */ + z-index: 999 !important; +} + +.non-dragging-component { + outline: 1px dotted var(--border-accent-weak) !important; + outline-offset: 0px; /* Creates space between element and outline */ + z-index: 999 !important; +} + + + diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx index 9964b06542..44f148a000 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx +++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx @@ -19,6 +19,9 @@ import { adjustWidth, hideGridLines, showGridLines, + handleActivateTargets, + handleDeactivateTargets, + handleActivateNonDraggingComponents, } from './gridUtils'; import { useAppVersionStore } from '@/_stores/appVersionStore'; import { resolveWidgetFieldValue } from '@/_helpers/utils'; @@ -580,7 +583,7 @@ export default function Grid({ gridWidth, currentLayout }) { } else { document.getElementById('real-canvas').classList.add('show-grid'); } - + handleActivateTargets(currentWidget.component?.parent); const currentWidth = currentWidget.width * _gridWidth; const diffWidth = e.width - currentWidth; const diffHeight = e.height - currentWidget.height; @@ -632,6 +635,7 @@ export default function Grid({ gridWidth, currentLayout }) { if (!isComponentVisible(e.target.id)) { return false; } + handleActivateNonDraggingComponents(); useGridStore.getState().actions.setResizingComponentId(e.target.id); e.setMin([gridWidth, GRID_HEIGHT]); }} @@ -695,17 +699,19 @@ export default function Grid({ gridWidth, currentLayout }) { } catch (error) { console.error('ResizeEnd error ->', error); } + handleDeactivateTargets(); setDragParentId(null); toggleCanvasUpdater(); }} onResizeGroupStart={({ events }) => { showGridLines(); + handleActivateNonDraggingComponents(); }} onResizeGroup={({ events }) => { const parentElm = events[0].target.closest('.real-canvas'); const parentWidth = parentElm?.clientWidth; const parentHeight = parentElm?.clientHeight; - + handleActivateTargets(parentElm?.id?.replace('canvas-', '')); const { posRight, posLeft, posTop, posBottom } = getPositionForGroupDrag(events, parentWidth, parentHeight); events.forEach((ev) => { ev.target.style.width = `${ev.width}px`; @@ -774,6 +780,7 @@ export default function Grid({ gridWidth, currentLayout }) { } catch (error) { console.error('Error resizing group', error); } + handleDeactivateTargets(); toggleCanvasUpdater(); }} checkInput @@ -784,6 +791,7 @@ export default function Grid({ gridWidth, currentLayout }) { } newDragParentId.current = boxList.find((box) => box.id === e.target.id)?.parent; e?.moveable?.controlBox?.removeAttribute('data-off-screen'); + const box = boxList.find((box) => box.id === e.target.id); // Prevent drag if shift is pressed for SUBCONTAINER_WIDGETS if (SUBCONTAINER_WIDGETS.includes(box?.component?.component) && e.inputEvent.shiftKey) { @@ -817,7 +825,6 @@ export default function Grid({ gridWidth, currentLayout }) { container.contains(e.inputEvent.target) ); } - if (['RangeSlider', 'BoundedBox'].includes(box?.component?.component) || isDragOnInnerElement) { const targetElems = document.elementsFromPoint(e.clientX, e.clientY); const isHandle = targetElems.find((ele) => ele.classList.contains('handle-content')); @@ -825,8 +832,10 @@ export default function Grid({ gridWidth, currentLayout }) { return false; } } + handleActivateNonDraggingComponents(); }} onDragEnd={(e) => { + handleDeactivateTargets(); try { if (isDraggingRef.current) { useStore.getState().setDraggingComponentId(null); @@ -961,6 +970,7 @@ export default function Grid({ gridWidth, currentLayout }) { setDragParentId(newParentId === 'canvas' ? null : newParentId); newDragParentId.current = newParentId === 'canvas' ? null : newParentId; prevDragParentId.current = newParentId; + handleActivateTargets(newParentId); } } // Postion ghost element exactly as same at dragged element @@ -987,14 +997,17 @@ export default function Grid({ gridWidth, currentLayout }) { ev.target.style.transform = `translate(${left}px, ${top}px)`; }); + handleActivateTargets(parentElm?.id?.replace('canvas-', '')); updateNewPosition(events); }} onDragGroupStart={({ events }) => { showGridLines(); setIsGroupDragging(true); + handleActivateNonDraggingComponents(); }} onDragGroupEnd={(e) => { handleDragGroupEnd(e); + handleDeactivateTargets(); toggleCanvasUpdater(); }} onClickGroup={(e) => { diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js index 2889fc06db..da179bc11d 100644 --- a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js @@ -1,7 +1,7 @@ import { useGridStore } from '@/_stores/gridStore'; import { isEmpty } from 'lodash'; import useStore from '@/AppBuilder/_stores/store'; - +import { getTabId, getSubContainerIdWithSlots } from '../appCanvasUtils'; export function correctBounds(layout, bounds) { layout = scaleLayouts(layout); const collidesWith = []; @@ -414,3 +414,77 @@ export function hideGridLines() { document.getElementById('real-canvas')?.classList.remove('show-grid'); document.getElementById('real-canvas')?.classList.add('hide-grid'); } + +// Track previously active elements for efficient cleanup +let previousActiveWidgets = null; +let previousActiveCanvas = null; + +export const handleActivateNonDraggingComponents = () => { + // Only add non-dragging class to visible components in viewport + document.querySelectorAll('.moveable-box:not(.active-target)').forEach((component) => { + // Check if element is visible in viewport + const rect = component.getBoundingClientRect(); + const isVisible = + rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0; + + if (isVisible) { + component.classList.add('non-dragging-component'); + } + }); +}; + +export const handleActivateTargets = (parentId) => { + const WIDGETS_WITH_CANVAS_OUTLINE = ['Container', 'Modal', 'Form', 'Listview', 'Kanban']; + + const newParentType = document.getElementById('canvas-' + parentId)?.getAttribute('component-type'); + let _parentId = parentId; + if (newParentType === 'Tabs') { + _parentId = getTabId(parentId); + } else if (WIDGETS_WITH_CANVAS_OUTLINE.includes(newParentType)) { + _parentId = getSubContainerIdWithSlots(parentId); + } + + // Clean up previous active elements + if (previousActiveWidgets) { + previousActiveWidgets.classList.remove('dragging-component-canvas'); + previousActiveWidgets = null; + } + + if (previousActiveCanvas) { + previousActiveCanvas.classList.remove('dragging-component-canvas'); + previousActiveCanvas = null; + } + + const parentComponent = document.getElementById(_parentId); + if (!parentComponent) return; + + if (WIDGETS_WITH_CANVAS_OUTLINE?.includes(newParentType)) { + // If it's multiple canvas in single widget, highlight the specific canvas + const canvasElm = document.getElementById('canvas-' + parentId); + if (canvasElm) { + canvasElm.classList.add('dragging-component-canvas'); + previousActiveCanvas = canvasElm; + } + } else { + // Otherwise highlight the component box + parentComponent.classList.remove('non-dragging-component'); + parentComponent.classList.add('dragging-component-canvas'); + previousActiveWidgets = parentComponent; + } +}; + +export const handleDeactivateTargets = () => { + if (previousActiveWidgets) { + previousActiveWidgets.classList.remove('dragging-component-canvas'); + previousActiveWidgets = null; + } + + if (previousActiveCanvas) { + previousActiveCanvas.classList.remove('dragging-component-canvas'); + previousActiveCanvas = null; + } + + document.querySelectorAll('.non-dragging-component').forEach((component) => { + component.classList.remove('non-dragging-component'); + }); +}; diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx index 895697091f..0f1cb3359f 100644 --- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx +++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx @@ -69,6 +69,7 @@ const WidgetWrapper = memo( data-id={`${id}`} id={id} widgetid={id} + component-type={componentType} style={{ // zIndex: mode === 'view' && widget.component.component == 'Datepicker' ? 2 : null, ...styles, diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js index 41bc116ec3..8dae9e001c 100644 --- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js +++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js @@ -690,3 +690,19 @@ export const getParentWidgetFromId = (parentType, parentId) => { } return parentType; }; + +export const getTabId = (parentId) => { + return parentId.split('-').slice(0, -1).join('-'); +}; + +export const getSubContainerIdWithSlots = (parentId) => { + let cleanParentId = parentId; + if (parentId) { + if (parentId.includes('header')) { + cleanParentId = parentId.replace('-header', ''); + } else if (parentId.includes('footer')) { + cleanParentId = parentId.replace('-footer', ''); + } + } + return cleanParentId; +}; diff --git a/frontend/src/AppBuilder/AppCanvas/selecto.scss b/frontend/src/AppBuilder/AppCanvas/selecto.scss index 9ca8a37f41..5602b35d5a 100644 --- a/frontend/src/AppBuilder/AppCanvas/selecto.scss +++ b/frontend/src/AppBuilder/AppCanvas/selecto.scss @@ -3,15 +3,18 @@ } .main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover { - z-index: 4 !important; -} - -.main-editor-canvas .widget-target:has(.nested-target:hover):hover { - outline: 0px solid #4af; -} - -.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { outline: 1px solid #4af; z-index: 4 !important; } +.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover { + // outline: 1px solid #4af; + z-index: 4 !important; +} + +// .main-editor-canvas .widget-target:hover { +// outline: 1px solid #4af; +// } + + +