From 4ed27aed7d774cc2011363d71f0d668a7b2eefae Mon Sep 17 00:00:00 2001 From: Lucki2g Date: Mon, 18 Aug 2025 18:04:36 +0200 Subject: [PATCH 1/7] feat: selection changes. Multiple entity selection, adding and removing entities from selection via ctrl click. --- Website/components/diagram/DiagramCanvas.tsx | 59 ++- Website/components/diagram/DiagramView.tsx | 54 ++- Website/hooks/useDiagram.ts | 382 ++++++++++++++++++- 3 files changed, 480 insertions(+), 15 deletions(-) diff --git a/Website/components/diagram/DiagramCanvas.tsx b/Website/components/diagram/DiagramCanvas.tsx index 3fd0516..bdb9247 100644 --- a/Website/components/diagram/DiagramCanvas.tsx +++ b/Website/components/diagram/DiagramCanvas.tsx @@ -1,5 +1,5 @@ import { useDiagramViewContext } from '@/contexts/DiagramViewContext'; -import React, { useRef, useEffect } from 'react'; +import React, { useRef, useEffect, useState } from 'react'; interface DiagramCanvasProps { children?: React.ReactNode; @@ -7,6 +7,7 @@ interface DiagramCanvasProps { export const DiagramCanvas: React.FC = ({ children }) => { const canvasRef = useRef(null); + const [isCtrlPressed, setIsCtrlPressed] = useState(false); const { isPanning, @@ -14,6 +15,34 @@ export const DiagramCanvas: React.FC = ({ children }) => { destroyPaper } = useDiagramViewContext(); + // Track Ctrl key state + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.ctrlKey || e.metaKey) { + setIsCtrlPressed(true); + } + }; + + const handleKeyUp = (e: KeyboardEvent) => { + if (!e.ctrlKey && !e.metaKey) { + setIsCtrlPressed(false); + } + }; + + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + + // Handle window blur to reset ctrl state + const handleBlur = () => setIsCtrlPressed(false); + window.addEventListener('blur', handleBlur); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('keyup', handleKeyUp); + window.removeEventListener('blur', handleBlur); + }; + }, []); + useEffect(() => { if (canvasRef.current) { initializePaper(canvasRef.current, { @@ -28,20 +57,42 @@ export const DiagramCanvas: React.FC = ({ children }) => { } }, [initializePaper, destroyPaper]); + // Determine cursor based on state + const getCursor = () => { + if (isPanning) return 'cursor-grabbing'; + if (isCtrlPressed) return 'cursor-grab'; + return 'cursor-crosshair'; // Default to crosshair for area selection + }; + return (
- {/* Panning indicator */} + {/* Interaction mode indicators */} {isPanning && ( -
+
+
Panning...
)} + {isCtrlPressed && !isPanning && ( +
+ Ctrl + Hold + Drag to Pan +
+ )} + + {!isCtrlPressed && !isPanning && ( +
+
+ Drag to Select Area +
+ )} + {children}
); diff --git a/Website/components/diagram/DiagramView.tsx b/Website/components/diagram/DiagramView.tsx index 81f057e..c592c86 100644 --- a/Website/components/diagram/DiagramView.tsx +++ b/Website/components/diagram/DiagramView.tsx @@ -29,6 +29,7 @@ const DiagramContent = () => { currentEntities, zoom, mousePosition, + isPanning, selectGroup, fitToScreen, addAttributeToEntity, @@ -39,6 +40,7 @@ const DiagramContent = () => { const [selectedKey, setSelectedKey] = useState(); const [selectedEntityForActions, setSelectedEntityForActions] = useState(); + const [selectedArea, setSelectedArea] = useState<{ start: { x: number; y: number }; end: { x: number; y: number } }>({ start: { x: 0, y: 0 }, end: { x: 0, y: 0 } }); const [isLoading, setIsLoading] = useState(true); // Persistent tracking of entity positions across renders @@ -351,6 +353,12 @@ const DiagramContent = () => { const element = elementView.model; const elementType = element.get('type'); + // Check if Ctrl is pressed - if so, skip opening any panes (selection is handled in useDiagram) + const isCtrlPressed = (evt.originalEvent as MouseEvent)?.ctrlKey || (evt.originalEvent as MouseEvent)?.metaKey; + if (isCtrlPressed) { + return; + } + if (elementType === 'delegate.square') { const squareElement = element as SquareElement; @@ -659,24 +667,46 @@ const DiagramContent = () => { }; }, [isResizing, resizeData, paper]); - // Handle clicking outside to deselect squares + // Handle clicking outside to deselect squares and manage area selection useEffect(() => { if (!paper) return; - const handleBlankClick = () => { + const handleBlankClick = (evt: dia.Event, x: number, y: number) => { if (selectedSquare) { selectedSquare.hideResizeHandles(); setSelectedSquare(null); setIsSquarePropertiesSheetOpen(false); } + } + + const handleBlankPointerDown = (evt: dia.Event, x: number, y: number) => { + + // Don't set selected area if we were panning + if (!isPanning) { + setSelectedArea({ + ...selectedArea, + start: { x, y } + }); + } + }; + + const handleBlankPointerUp = (evt: dia.Event, x: number, y: number) => { + console.log('Blank pointer up at:', x, y); + if (!isPanning && Math.abs(selectedArea.start.x - x) > 10 && Math.abs(selectedArea.start.y - y) > 10) { + // TODO + } }; + paper.on('blank:pointerdown', handleBlankPointerDown); + paper.on('blank:pointerup', handleBlankPointerUp); paper.on('blank:pointerclick', handleBlankClick); return () => { + paper.off('blank:pointerdown', handleBlankPointerDown); + paper.off('blank:pointerup', handleBlankPointerUp); paper.off('blank:pointerclick', handleBlankClick); }; - }, [paper, selectedSquare]); + }, [paper, selectedSquare, isPanning, selectedArea]); const handleAddAttribute = (attribute: AttributeType) => { if (!selectedEntityForActions || !renderer) return; @@ -798,6 +828,24 @@ const DiagramContent = () => {

+ + {/* Interaction Help Banner */} +
+
+
+
+ Drag: Select area +
+
+ Ctrl + + Drag: Pan diagram +
+
+ 🖱️ + Scroll: Zoom +
+
+
{/* Diagram Area */}
void; zoomOut: () => void; resetView: () => void; fitToScreen: () => void; setZoom: (zoom: number) => void; + + // Pan setIsPanning: (isPanning: boolean) => void; + + // Select selectElement: (elementId: string) => void; + selectMultipleElements: (elementIds: string[]) => void; + toggleElementSelection: (elementId: string) => void; clearSelection: () => void; + + // Other initializePaper: (container: HTMLElement, options?: any) => void; destroyPaper: () => void; selectGroup: (group: GroupType) => void; @@ -57,6 +66,7 @@ export const useDiagram = (): DiagramState & DiagramActions => { const graphRef = useRef(null); const zoomRef = useRef(1); const isPanningRef = useRef(false); + const selectedElementsRef = useRef([]); const cleanupRef = useRef<(() => void) | null>(null); const isAddingAttributeRef = useRef(false); @@ -148,12 +158,28 @@ export const useDiagram = (): DiagramState & DiagramActions => { }, [updatePanningDisplay]); const selectElement = useCallback((elementId: string) => { - setSelectedElements(prev => - prev.includes(elementId) ? prev : [...prev, elementId] - ); + const newSelection = [elementId]; + selectedElementsRef.current = newSelection; + setSelectedElements(newSelection); + }, []); + + const selectMultipleElements = useCallback((elementIds: string[]) => { + selectedElementsRef.current = elementIds; + setSelectedElements(elementIds); + }, []); + + const toggleElementSelection = useCallback((elementId: string) => { + setSelectedElements(prev => { + const newSelection = prev.includes(elementId) + ? prev.filter(id => id !== elementId) + : [...prev, elementId]; + selectedElementsRef.current = newSelection; + return newSelection; + }); }, []); const clearSelection = useCallback(() => { + selectedElementsRef.current = []; setSelectedElements([]); }, []); @@ -733,21 +759,125 @@ export const useDiagram = (): DiagramState & DiagramActions => { paperRef.current = paper; setPaperInitialized(true); + // Centralized function to apply selection styling + const applySelectionStyling = (element: dia.Element, isSelected: boolean) => { + const foreignObject = element.findView(paper)?.el.querySelector('foreignObject'); + const htmlContent = foreignObject?.querySelector('[data-entity-schema]') as HTMLElement; + if (htmlContent) { + if (isSelected) { + // More prominent selection styling + htmlContent.style.border = '3px solid #3b82f6'; + htmlContent.style.borderRadius = '12px'; + htmlContent.style.boxShadow = '0 0 15px rgba(59, 130, 246, 0.6), 0 0 30px rgba(59, 130, 246, 0.3)'; + htmlContent.style.backgroundColor = 'rgba(59, 130, 246, 0.05)'; + htmlContent.style.transform = 'scale(1.02)'; + htmlContent.style.transition = 'all 0.2s ease-in-out'; + htmlContent.style.zIndex = '10'; + } else { + // Clear selection styling + htmlContent.style.border = 'none'; + htmlContent.style.borderRadius = ''; + htmlContent.style.boxShadow = 'none'; + htmlContent.style.backgroundColor = ''; + htmlContent.style.transform = ''; + htmlContent.style.transition = ''; + htmlContent.style.zIndex = ''; + } + } + }; + + // Function to update all entity selection styling + const updateAllSelectionStyling = () => { + if (graphRef.current) { + graphRef.current.getElements().forEach(element => { + const entityData = element.get('data'); + if (entityData?.entity) { + const elementId = element.id.toString(); + const isSelected = selectedElementsRef.current.includes(elementId); + applySelectionStyling(element, isSelected); + } + }); + } + }; + + // Area selection state tracking + let isSelecting = false; + let selectionStartX = 0; + let selectionStartY = 0; + let selectionElement: SVGRectElement | null = null; + let currentAreaSelection: string[] = []; // Track current area selection + + // Create selection overlay element + const createSelectionOverlay = (x: number, y: number, width: number, height: number) => { + const paperSvg = paper.el.querySelector('svg'); + if (!paperSvg) return null; + + const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); + rect.setAttribute('x', x.toString()); + rect.setAttribute('y', y.toString()); + rect.setAttribute('width', width.toString()); + rect.setAttribute('height', height.toString()); + rect.setAttribute('fill', 'rgba(59, 130, 246, 0.1)'); + rect.setAttribute('stroke', '#3b82f6'); + rect.setAttribute('stroke-width', '2'); + rect.setAttribute('stroke-dasharray', '5,5'); + rect.setAttribute('pointer-events', 'none'); + rect.style.zIndex = '1000'; + + paperSvg.appendChild(rect); + return rect; + }; + // Setup event listeners - paper.on('blank:pointerdown', () => { - updatePanningDisplay(true); - const paperEl = paper.el as HTMLElement; - paperEl.style.cursor = 'grabbing'; + paper.on('blank:pointerdown', (evt: any) => { + const isCtrlPressed = evt.originalEvent?.ctrlKey || evt.originalEvent?.metaKey; + + if (isCtrlPressed) { + // Ctrl + drag = pan + updatePanningDisplay(true); + const paperEl = paper.el as HTMLElement; + paperEl.style.cursor = 'grabbing'; + } else { + // Regular drag = area selection + const currentTranslate = paper.translate(); + const currentScale = paper.scale(); + const rect = (paper.el as HTMLElement).getBoundingClientRect(); + + // Calculate start position in diagram coordinates + selectionStartX = (evt.originalEvent.clientX - rect.left - currentTranslate.tx) / currentScale.sx; + selectionStartY = (evt.originalEvent.clientY - rect.top - currentTranslate.ty) / currentScale.sy; + + isSelecting = true; + const paperEl = paper.el as HTMLElement; + paperEl.style.cursor = 'crosshair'; + } }); paper.on('blank:pointerup', () => { - updatePanningDisplay(false); + if (isPanningRef.current) { + updatePanningDisplay(false); + } + + if (isSelecting) { + // Finalize selection and apply permanent visual feedback + updateAllSelectionStyling(); + + isSelecting = false; + currentAreaSelection = []; // Clear the area selection tracking + // Remove selection overlay + if (selectionElement) { + selectionElement.remove(); + selectionElement = null; + } + } + const paperEl = paper.el as HTMLElement; paperEl.style.cursor = 'default'; }); paper.on('blank:pointermove', (evt: any) => { if (isPanningRef.current) { + // Handle panning const currentTranslate = paper.translate(); const deltaX = evt.originalEvent.movementX || 0; const deltaY = evt.originalEvent.movementY || 0; @@ -755,6 +885,201 @@ export const useDiagram = (): DiagramState & DiagramActions => { const newTranslateY = currentTranslate.ty + deltaY; paper.translate(newTranslateX, newTranslateY); updatePanPosition({ x: newTranslateX, y: newTranslateY }); + } else if (isSelecting) { + // Handle area selection + const currentTranslate = paper.translate(); + const currentScale = paper.scale(); + const rect = (paper.el as HTMLElement).getBoundingClientRect(); + + // Calculate current mouse position in diagram coordinates + const currentX = (evt.originalEvent.clientX - rect.left - currentTranslate.tx) / currentScale.sx; + const currentY = (evt.originalEvent.clientY - rect.top - currentTranslate.ty) / currentScale.sy; + + // Calculate selection rectangle bounds + const x = Math.min(selectionStartX, currentX); + const y = Math.min(selectionStartY, currentY); + const width = Math.abs(currentX - selectionStartX); + const height = Math.abs(currentY - selectionStartY); + + // Convert to screen coordinates for overlay + const screenX = x * currentScale.sx + currentTranslate.tx; + const screenY = y * currentScale.sy + currentTranslate.ty; + const screenWidth = width * currentScale.sx; + const screenHeight = height * currentScale.sy; + + // Update or create selection overlay + if (selectionElement) { + selectionElement.setAttribute('x', screenX.toString()); + selectionElement.setAttribute('y', screenY.toString()); + selectionElement.setAttribute('width', screenWidth.toString()); + selectionElement.setAttribute('height', screenHeight.toString()); + } else { + selectionElement = createSelectionOverlay(screenX, screenY, screenWidth, screenHeight); + } + + // Select elements within the area and provide visual feedback + if (graphRef.current && width > 10 && height > 10) { // Minimum selection size + const elementsInArea = graphRef.current.getElements().filter(element => { + const bbox = element.getBBox(); + const elementType = element.get('type'); + + // Only select entity elements, not squares or text + if (elementType !== 'delegate.entity') return false; + + // Check if element center is within selection bounds + const elementCenterX = bbox.x + bbox.width / 2; + const elementCenterY = bbox.y + bbox.height / 2; + + return elementCenterX >= x && elementCenterX <= x + width && + elementCenterY >= y && elementCenterY <= y + height; + }); + + // Update selected elements in real-time during drag + const selectedIds = elementsInArea.map(el => el.id.toString()); + currentAreaSelection = selectedIds; // Store for use in pointerup + selectedElementsRef.current = selectedIds; + setSelectedElements(selectedIds); + + // Apply visual feedback to all entities + graphRef.current.getElements().forEach(element => { + const entityData = element.get('data'); + if (entityData?.entity) { + const elementId = element.id.toString(); + const isSelected = selectedIds.includes(elementId); + applySelectionStyling(element, isSelected); + } + }); + } + } + }); + + // Group dragging state + let isGroupDragging = false; + let groupDragStartPositions: { [id: string]: { x: number; y: number } } = {}; + let dragStartMousePos = { x: 0, y: 0 }; + + // Element interaction handlers + paper.on('element:pointerdown', (elementView: dia.ElementView, evt: any) => { + const element = elementView.model; + const elementType = element.get('type'); + + // Only handle entity elements for group dragging and selection + if (elementType !== 'delegate.entity') return; + + const elementId = element.id.toString(); + const isCtrlPressed = evt.originalEvent?.ctrlKey || evt.originalEvent?.metaKey; + const currentSelection = selectedElementsRef.current; + + if (isCtrlPressed) { + // Ctrl+click: toggle selection + toggleElementSelection(elementId); + evt.preventDefault(); + evt.stopPropagation(); + + // Update visual feedback after a short delay to let state update + setTimeout(() => { + const isNowSelected = selectedElementsRef.current.includes(elementId); + applySelectionStyling(element, isNowSelected); + }, 0); + } else if (currentSelection.includes(elementId) && currentSelection.length > 1) { + // Start group dragging if clicking on already selected element (and there are multiple selected) + isGroupDragging = true; + groupDragStartPositions = {}; + + // Store initial positions for all selected elements + currentSelection.forEach(id => { + const elem = graphRef.current?.getCell(id); + if (elem && elem.get('type') === 'delegate.entity') { + const pos = elem.position(); + groupDragStartPositions[id] = { x: pos.x, y: pos.y }; + } + }); + + // Store initial mouse position + const rect = (paper.el as HTMLElement).getBoundingClientRect(); + const currentTranslate = paper.translate(); + const currentScale = paper.scale(); + dragStartMousePos = { + x: (evt.originalEvent.clientX - rect.left - currentTranslate.tx) / currentScale.sx, + y: (evt.originalEvent.clientY - rect.top - currentTranslate.ty) / currentScale.sy + }; + + evt.preventDefault(); + evt.stopPropagation(); + } else if (currentSelection.includes(elementId) && currentSelection.length === 1) { + // Single selected element - allow normal JointJS dragging behavior + // Don't prevent default, let JointJS handle the dragging + return; + } else { + // Regular click: clear selection and select only this element + clearSelection(); + selectElement(elementId); + + // Update visual feedback + updateAllSelectionStyling(); + } + }); + + paper.on('element:pointermove', (elementView: dia.ElementView, evt: any) => { + if (isGroupDragging && Object.keys(groupDragStartPositions).length > 0) { + const rect = (paper.el as HTMLElement).getBoundingClientRect(); + const currentTranslate = paper.translate(); + const currentScale = paper.scale(); + + const currentMouseX = (evt.originalEvent.clientX - rect.left - currentTranslate.tx) / currentScale.sx; + const currentMouseY = (evt.originalEvent.clientY - rect.top - currentTranslate.ty) / currentScale.sy; + + const deltaX = currentMouseX - dragStartMousePos.x; + const deltaY = currentMouseY - dragStartMousePos.y; + + // Move all selected elements + Object.keys(groupDragStartPositions).forEach(id => { + const elem = graphRef.current?.getCell(id); + if (elem) { + const startPos = groupDragStartPositions[id]; + elem.set('position', { x: startPos.x + deltaX, y: startPos.y + deltaY }); + } + }); + + evt.preventDefault(); + evt.stopPropagation(); + } + }); + + paper.on('element:pointerup', () => { + isGroupDragging = false; + groupDragStartPositions = {}; + }); + + // Clear selection when clicking on blank area (unless Ctrl+dragging) + paper.on('blank:pointerdown', (evt: any) => { + const isCtrlPressed = evt.originalEvent?.ctrlKey || evt.originalEvent?.metaKey; + + if (isCtrlPressed) { + // Ctrl + drag = pan + updatePanningDisplay(true); + const paperEl = paper.el as HTMLElement; + paperEl.style.cursor = 'grabbing'; + } else { + // Clear selection before starting area selection + if (!isSelecting) { + clearSelection(); + // Clear visual feedback + updateAllSelectionStyling(); + } + + // Regular drag = area selection + const currentTranslate = paper.translate(); + const currentScale = paper.scale(); + const rect = (paper.el as HTMLElement).getBoundingClientRect(); + + // Calculate start position in diagram coordinates + selectionStartX = (evt.originalEvent.clientX - rect.left - currentTranslate.tx) / currentScale.sx; + selectionStartY = (evt.originalEvent.clientY - rect.top - currentTranslate.ty) / currentScale.sy; + + isSelecting = true; + const paperEl = paper.el as HTMLElement; + paperEl.style.cursor = 'crosshair'; } }); @@ -833,6 +1158,45 @@ export const useDiagram = (): DiagramState & DiagramActions => { setGraphInitialized(false); }, [setPaperInitialized, setGraphInitialized]); + // Update selection styling whenever selectedElements changes + useEffect(() => { + if (paperRef.current && graphRef.current) { + const paper = paperRef.current; + if (graphRef.current) { + graphRef.current.getElements().forEach(element => { + const entityData = element.get('data'); + if (entityData?.entity) { + const elementId = element.id.toString(); + const isSelected = selectedElements.includes(elementId); + const foreignObject = element.findView(paper)?.el.querySelector('foreignObject'); + const htmlContent = foreignObject?.querySelector('[data-entity-schema]') as HTMLElement; + if (htmlContent) { + if (isSelected) { + // More prominent selection styling + htmlContent.style.border = '3px solid #3b82f6'; + htmlContent.style.borderRadius = '12px'; + htmlContent.style.boxShadow = '0 0 15px rgba(59, 130, 246, 0.6), 0 0 30px rgba(59, 130, 246, 0.3)'; + htmlContent.style.backgroundColor = 'rgba(59, 130, 246, 0.05)'; + htmlContent.style.transform = 'scale(1.02)'; + htmlContent.style.transition = 'all 0.2s ease-in-out'; + htmlContent.style.zIndex = '10'; + } else { + // Clear selection styling + htmlContent.style.border = 'none'; + htmlContent.style.borderRadius = ''; + htmlContent.style.boxShadow = 'none'; + htmlContent.style.backgroundColor = ''; + htmlContent.style.transform = ''; + htmlContent.style.transition = ''; + htmlContent.style.zIndex = ''; + } + } + } + }); + } + } + }, [selectedElements]); + // Cleanup on unmount useEffect(() => { return () => { @@ -861,6 +1225,8 @@ export const useDiagram = (): DiagramState & DiagramActions => { setZoom, setIsPanning, selectElement, + selectMultipleElements, + toggleElementSelection, clearSelection, initializePaper, destroyPaper, From a54e6c41818e705432b291c2383a154146a30df1 Mon Sep 17 00:00:00 2001 From: Lucki2g Date: Wed, 20 Aug 2025 17:34:16 +0200 Subject: [PATCH 2/7] chore: removed some console.logs fixed some styling issues with selection vs. hover --- Website/components/diagram/DiagramView.tsx | 54 +---- Website/hooks/useDiagram.ts | 101 ++------- Website/lib/entity-styling.ts | 241 +++++++++++++++++++++ 3 files changed, 263 insertions(+), 133 deletions(-) create mode 100644 Website/lib/entity-styling.ts diff --git a/Website/components/diagram/DiagramView.tsx b/Website/components/diagram/DiagramView.tsx index c592c86..17696fe 100644 --- a/Website/components/diagram/DiagramView.tsx +++ b/Website/components/diagram/DiagramView.tsx @@ -8,6 +8,7 @@ import { TextElement } from '@/components/diagram/elements/TextElement'; import { DiagramCanvas } from '@/components/diagram/DiagramCanvas'; import { ZoomCoordinateIndicator } from '@/components/diagram/ZoomCoordinateIndicator'; import { EntityActionsPane, LinkPropertiesPane, LinkProperties } from '@/components/diagram/panes'; +import { entityStyleManager } from '@/lib/entity-styling'; import { SquarePropertiesPane } from '@/components/diagram/panes/SquarePropertiesPane'; import { TextPropertiesPane } from '@/components/diagram/panes/TextPropertiesPane'; import { calculateGridLayout, getDefaultLayoutOptions, calculateEntityHeight, estimateEntityDimensions } from '@/components/diagram/GridLayoutManager'; @@ -130,7 +131,6 @@ const DiagramContent = () => { // Check if diagram type has changed and clear all positions if so let diagramTypeChanged = false; if (previousDiagramTypeRef.current !== diagramType) { - console.log(`🔄 Diagram type changed from ${previousDiagramTypeRef.current} to ${diagramType}, clearing all entity positions`); entityPositionsRef.current.clear(); previousDiagramTypeRef.current = diagramType; diagramTypeChanged = true; @@ -169,29 +169,23 @@ const DiagramContent = () => { // Update persistent position tracking with current positions // Skip this if diagram type changed to ensure all entities are treated as new if (!diagramTypeChanged) { - console.log('🔍 Before update - entityPositionsRef has:', Array.from(entityPositionsRef.current.keys())); existingEntities.forEach(element => { const entityData = element.get('data'); if (entityData?.entity?.SchemaName) { const position = element.position(); entityPositionsRef.current.set(entityData.entity.SchemaName, position); - console.log(`📍 Updated position for ${entityData.entity.SchemaName}:`, position); } }); } else { - console.log('🔄 Skipping position update due to diagram type change'); } // Clean up position tracking for entities that are no longer in currentEntities const currentEntityNames = new Set(currentEntities.map(e => e.SchemaName)); - console.log('📋 Current entities:', Array.from(currentEntityNames)); for (const [schemaName] of entityPositionsRef.current) { if (!currentEntityNames.has(schemaName)) { - console.log(`🗑️ Removing position tracking for deleted entity: ${schemaName}`); entityPositionsRef.current.delete(schemaName); } } - console.log('🔍 After cleanup - entityPositionsRef has:', Array.from(entityPositionsRef.current.keys())); // Clear existing elements graph.clear(); @@ -238,20 +232,16 @@ const DiagramContent = () => { entityPositionsRef.current.has(entity.SchemaName) ); - console.log('🆕 New entities (no tracked position):', newEntities.map(e => e.SchemaName)); - console.log('📌 Existing entities (have tracked position):', existingEntitiesWithPositions.map(e => e.SchemaName)); // Store entity elements and port maps by SchemaName for easy lookup const entityMap = new Map(); const placedEntityPositions: { x: number; y: number; width: number; height: number }[] = []; // First, create existing entities with their preserved positions - console.log('🔧 Creating existing entities with preserved positions...'); existingEntitiesWithPositions.forEach((entity) => { const position = entityPositionsRef.current.get(entity.SchemaName); if (!position) return; // Skip if position is undefined - console.log(`📍 Placing existing entity ${entity.SchemaName} at:`, position); const { element, portMap } = renderer.createEntity(entity, position); entityMap.set(entity.SchemaName, { element, portMap }); @@ -265,11 +255,9 @@ const DiagramContent = () => { }); }); - console.log('🚧 Collision avoidance positions:', placedEntityPositions); // Then, create new entities with grid layout that avoids already placed entities if (newEntities.length > 0) { - console.log('🆕 Creating new entities with grid layout...'); // Calculate actual heights for new entities based on diagram type const entityHeights = newEntities.map(entity => calculateEntityHeight(entity, diagramType)); const maxEntityHeight = Math.max(...entityHeights, layoutOptions.entityHeight); @@ -280,25 +268,19 @@ const DiagramContent = () => { diagramType: diagramType }; - console.log('📊 Grid layout options:', adjustedLayoutOptions); - console.log('🚧 Avoiding existing positions:', placedEntityPositions); const layout = calculateGridLayout(newEntities, adjustedLayoutOptions, placedEntityPositions); - console.log('📐 Calculated grid positions:', layout.positions); // Create new entities with grid layout positions newEntities.forEach((entity, index) => { const position = layout.positions[index] || { x: 50, y: 50 }; - console.log(`🆕 Placing new entity ${entity.SchemaName} at:`, position); const { element, portMap } = renderer.createEntity(entity, position); entityMap.set(entity.SchemaName, { element, portMap }); // Update persistent position tracking for newly placed entities entityPositionsRef.current.set(entity.SchemaName, position); - console.log(`💾 Saved position for ${entity.SchemaName}:`, position); }); } else { - console.log('✅ No new entities to place with grid layout'); } util.nextFrame(() => { @@ -418,22 +400,11 @@ const DiagramContent = () => { return; } - // Handle entity hover + // Handle entity hover using centralized style manager const entityData = element.get('data'); - if (entityData?.entity) { - // Change cursor on the SVG element - elementView.el.style.cursor = 'pointer'; - - // Find the foreignObject and its HTML content for the border effect - const foreignObject = elementView.el.querySelector('foreignObject'); - const htmlContent = foreignObject?.querySelector('[data-entity-schema]') as HTMLElement; - - if (htmlContent && !htmlContent.hasAttribute('data-hover-active')) { - htmlContent.setAttribute('data-hover-active', 'true'); - htmlContent.style.border = '1px solid #3b82f6'; - htmlContent.style.borderRadius = '10px'; - } + if (entityData?.entity && paper) { + entityStyleManager.handleEntityMouseEnter(element, paper); } }; @@ -463,21 +434,11 @@ const DiagramContent = () => { return; } - // Handle entity hover leave + // Handle entity hover leave using centralized style manager const entityData = element.get('data'); - if (entityData?.entity) { - // Remove hover styling - elementView.el.style.cursor = 'default'; - - // Remove border from HTML content - const foreignObject = elementView.el.querySelector('foreignObject'); - const htmlContent = foreignObject?.querySelector('[data-entity-schema]') as HTMLElement; - - if (htmlContent) { - htmlContent.removeAttribute('data-hover-active'); - htmlContent.style.border = 'none'; - } + if (entityData?.entity && paper) { + entityStyleManager.handleEntityMouseLeave(element, paper); } }; @@ -691,7 +652,6 @@ const DiagramContent = () => { }; const handleBlankPointerUp = (evt: dia.Event, x: number, y: number) => { - console.log('Blank pointer up at:', x, y); if (!isPanning && Math.abs(selectedArea.start.x - x) > 10 && Math.abs(selectedArea.start.y - y) > 10) { // TODO } diff --git a/Website/hooks/useDiagram.ts b/Website/hooks/useDiagram.ts index 9d56300..0267b37 100644 --- a/Website/hooks/useDiagram.ts +++ b/Website/hooks/useDiagram.ts @@ -9,6 +9,7 @@ import { DiagramRenderer } from '@/components/diagram/DiagramRenderer'; import { SimpleDiagramRenderer } from '@/components/diagram/renderers/SimpleDiagramRender'; import { DetailedDiagramRender } from '@/components/diagram/renderers/DetailedDiagramRender'; import { PRESET_COLORS } from '@/components/diagram/shared/DiagramConstants'; +import { entityStyleManager } from '@/lib/entity-styling'; export type DiagramType = 'simple' | 'detailed'; @@ -759,45 +760,13 @@ export const useDiagram = (): DiagramState & DiagramActions => { paperRef.current = paper; setPaperInitialized(true); - // Centralized function to apply selection styling - const applySelectionStyling = (element: dia.Element, isSelected: boolean) => { - const foreignObject = element.findView(paper)?.el.querySelector('foreignObject'); - const htmlContent = foreignObject?.querySelector('[data-entity-schema]') as HTMLElement; - if (htmlContent) { - if (isSelected) { - // More prominent selection styling - htmlContent.style.border = '3px solid #3b82f6'; - htmlContent.style.borderRadius = '12px'; - htmlContent.style.boxShadow = '0 0 15px rgba(59, 130, 246, 0.6), 0 0 30px rgba(59, 130, 246, 0.3)'; - htmlContent.style.backgroundColor = 'rgba(59, 130, 246, 0.05)'; - htmlContent.style.transform = 'scale(1.02)'; - htmlContent.style.transition = 'all 0.2s ease-in-out'; - htmlContent.style.zIndex = '10'; - } else { - // Clear selection styling - htmlContent.style.border = 'none'; - htmlContent.style.borderRadius = ''; - htmlContent.style.boxShadow = 'none'; - htmlContent.style.backgroundColor = ''; - htmlContent.style.transform = ''; - htmlContent.style.transition = ''; - htmlContent.style.zIndex = ''; - } - } - }; - - // Function to update all entity selection styling - const updateAllSelectionStyling = () => { - if (graphRef.current) { - graphRef.current.getElements().forEach(element => { - const entityData = element.get('data'); - if (entityData?.entity) { - const elementId = element.id.toString(); - const isSelected = selectedElementsRef.current.includes(elementId); - applySelectionStyling(element, isSelected); - } - }); - } + // Update entity style manager when selected elements change + const updateEntityStyleManager = () => { + entityStyleManager.handleSelectionChange( + selectedElementsRef.current, + graphRef.current!, + paper + ); }; // Area selection state tracking @@ -860,7 +829,7 @@ export const useDiagram = (): DiagramState & DiagramActions => { if (isSelecting) { // Finalize selection and apply permanent visual feedback - updateAllSelectionStyling(); + updateEntityStyleManager(); isSelecting = false; currentAreaSelection = []; // Clear the area selection tracking @@ -940,15 +909,8 @@ export const useDiagram = (): DiagramState & DiagramActions => { selectedElementsRef.current = selectedIds; setSelectedElements(selectedIds); - // Apply visual feedback to all entities - graphRef.current.getElements().forEach(element => { - const entityData = element.get('data'); - if (entityData?.entity) { - const elementId = element.id.toString(); - const isSelected = selectedIds.includes(elementId); - applySelectionStyling(element, isSelected); - } - }); + // Apply visual feedback using entity style manager + entityStyleManager.handleSelectionChange(selectedIds, graphRef.current, paper); } } }); @@ -978,8 +940,7 @@ export const useDiagram = (): DiagramState & DiagramActions => { // Update visual feedback after a short delay to let state update setTimeout(() => { - const isNowSelected = selectedElementsRef.current.includes(elementId); - applySelectionStyling(element, isNowSelected); + updateEntityStyleManager(); }, 0); } else if (currentSelection.includes(elementId) && currentSelection.length > 1) { // Start group dragging if clicking on already selected element (and there are multiple selected) @@ -1016,7 +977,7 @@ export const useDiagram = (): DiagramState & DiagramActions => { selectElement(elementId); // Update visual feedback - updateAllSelectionStyling(); + updateEntityStyleManager(); } }); @@ -1065,7 +1026,7 @@ export const useDiagram = (): DiagramState & DiagramActions => { if (!isSelecting) { clearSelection(); // Clear visual feedback - updateAllSelectionStyling(); + updateEntityStyleManager(); } // Regular drag = area selection @@ -1161,39 +1122,7 @@ export const useDiagram = (): DiagramState & DiagramActions => { // Update selection styling whenever selectedElements changes useEffect(() => { if (paperRef.current && graphRef.current) { - const paper = paperRef.current; - if (graphRef.current) { - graphRef.current.getElements().forEach(element => { - const entityData = element.get('data'); - if (entityData?.entity) { - const elementId = element.id.toString(); - const isSelected = selectedElements.includes(elementId); - const foreignObject = element.findView(paper)?.el.querySelector('foreignObject'); - const htmlContent = foreignObject?.querySelector('[data-entity-schema]') as HTMLElement; - if (htmlContent) { - if (isSelected) { - // More prominent selection styling - htmlContent.style.border = '3px solid #3b82f6'; - htmlContent.style.borderRadius = '12px'; - htmlContent.style.boxShadow = '0 0 15px rgba(59, 130, 246, 0.6), 0 0 30px rgba(59, 130, 246, 0.3)'; - htmlContent.style.backgroundColor = 'rgba(59, 130, 246, 0.05)'; - htmlContent.style.transform = 'scale(1.02)'; - htmlContent.style.transition = 'all 0.2s ease-in-out'; - htmlContent.style.zIndex = '10'; - } else { - // Clear selection styling - htmlContent.style.border = 'none'; - htmlContent.style.borderRadius = ''; - htmlContent.style.boxShadow = 'none'; - htmlContent.style.backgroundColor = ''; - htmlContent.style.transform = ''; - htmlContent.style.transition = ''; - htmlContent.style.zIndex = ''; - } - } - } - }); - } + entityStyleManager.handleSelectionChange(selectedElements, graphRef.current, paperRef.current); } }, [selectedElements]); diff --git a/Website/lib/entity-styling.ts b/Website/lib/entity-styling.ts new file mode 100644 index 0000000..9c49b5b --- /dev/null +++ b/Website/lib/entity-styling.ts @@ -0,0 +1,241 @@ +import { dia } from '@joint/core'; + +export enum EntityStyleState { + NORMAL = 'normal', + HOVER = 'hover', + SELECTED = 'selected', + SELECTED_HOVER = 'selected-hover' +} + +export interface EntityStylingConfig { + normal: { + border: string; + borderRadius: string; + cursor: string; + filter: string; + }; + hover: { + border: string; + borderRadius: string; + cursor: string; + filter: string; + }; + selected: { + border: string; + borderRadius: string; + cursor: string; + filter: string; + }; + selectedHover: { + border: string; + borderRadius: string; + cursor: string; + filter: string; + }; +} + +// Default styling configuration for entities +const DEFAULT_ENTITY_STYLING: EntityStylingConfig = { + normal: { + border: 'none', + borderRadius: '', + cursor: 'default', + filter: 'none' + }, + hover: { + border: '1px solid #3b82f6', + borderRadius: '10px', + cursor: 'pointer', + filter: 'none' + }, + selected: { + border: '3px solid #3b82f6', + borderRadius: '12px', + cursor: 'pointer', + filter: 'drop-shadow(0 0 4px rgba(59, 130, 246, 0.3))' + }, + selectedHover: { + border: '3px solid #2563eb', // Slightly darker blue for selected + hover + borderRadius: '12px', + cursor: 'pointer', + filter: 'drop-shadow(0 0 6px rgba(59, 130, 246, 0.4))' + } +}; + +/** + * Centralized entity styling manager + * Handles all entity visual states including hover, selection, and combined states + */ +export class EntityStyleManager { + private selectedElements: Set = new Set(); + private hoveredElements: Set = new Set(); + private config: EntityStylingConfig; + + constructor(config: EntityStylingConfig = DEFAULT_ENTITY_STYLING) { + this.config = config; + } + + /** + * Update the list of selected elements + */ + setSelectedElements(elementIds: string[]): void { + this.selectedElements.clear(); + elementIds.forEach(id => this.selectedElements.add(id)); + } + + /** + * Add an element to the selected set + */ + addSelectedElement(elementId: string): void { + this.selectedElements.add(elementId); + } + + /** + * Remove an element from the selected set + */ + removeSelectedElement(elementId: string): void { + this.selectedElements.delete(elementId); + } + + /** + * Clear all selected elements + */ + clearSelectedElements(): void { + this.selectedElements.clear(); + } + + /** + * Set hover state for an element + */ + setElementHover(elementId: string, isHovered: boolean): void { + if (isHovered) { + this.hoveredElements.add(elementId); + } else { + this.hoveredElements.delete(elementId); + } + } + + /** + * Get the current style state for an element + */ + getElementState(elementId: string): EntityStyleState { + const isSelected = this.selectedElements.has(elementId); + const isHovered = this.hoveredElements.has(elementId); + + if (isSelected && isHovered) { + return EntityStyleState.SELECTED_HOVER; + } else if (isSelected) { + return EntityStyleState.SELECTED; + } else if (isHovered) { + return EntityStyleState.HOVER; + } else { + return EntityStyleState.NORMAL; + } + } + + /** + * Apply styling to an entity element based on its current state + */ + applyEntityStyling(element: dia.Element, paper: dia.Paper): void { + const elementId = element.id.toString(); + const state = this.getElementState(elementId); + + console.log('🎨 Applying styling to entity:', elementId, 'State:', state); + + // Get the element view and HTML content + const elementView = element.findView(paper); + if (!elementView) return; + + const foreignObject = elementView.el.querySelector('foreignObject'); + const htmlContent = foreignObject?.querySelector('[data-entity-schema]') as HTMLElement; + + if (!htmlContent) return; + + // Get styling for current state + const styling = this.getStyleForState(state); + + console.log('🎨 Styling to apply:', styling); + + // Always apply styling based on current state (selection takes precedence over hover) + htmlContent.style.border = styling.border; + htmlContent.style.borderRadius = styling.borderRadius; + elementView.el.style.cursor = styling.cursor; + + // Apply filter to the element's SVG for glow effects + // For entity elements, we need to apply the filter to the root element + if (styling.filter === 'none') { + elementView.el.style.filter = ''; + } else { + elementView.el.style.filter = styling.filter; + } + + // Manage data-hover-active attribute properly + const isHovered = this.hoveredElements.has(elementId); + if (isHovered) { + htmlContent.setAttribute('data-hover-active', 'true'); + } else { + htmlContent.removeAttribute('data-hover-active'); + } + } + + /** + * Apply styling to all entities in a graph + */ + applyAllEntityStyling(graph: dia.Graph, paper: dia.Paper): void { + graph.getElements().forEach((element: dia.Element) => { + const entityData = element.get('data'); + if (entityData?.entity) { + this.applyEntityStyling(element, paper); + } + }); + } + + /** + * Get styling configuration for a specific state + */ + private getStyleForState(state: EntityStyleState) { + switch (state) { + case EntityStyleState.NORMAL: + return this.config.normal; + case EntityStyleState.HOVER: + return this.config.hover; + case EntityStyleState.SELECTED: + return this.config.selected; + case EntityStyleState.SELECTED_HOVER: + return this.config.selectedHover; + default: + return this.config.normal; + } + } + + /** + * Handle mouse enter on an entity + */ + handleEntityMouseEnter(element: dia.Element, paper: dia.Paper): void { + const elementId = element.id.toString(); + this.setElementHover(elementId, true); + this.applyEntityStyling(element, paper); + } + + /** + * Handle mouse leave on an entity + */ + handleEntityMouseLeave(element: dia.Element, paper: dia.Paper): void { + const elementId = element.id.toString(); + console.log('🖱️ Mouse leave entity:', elementId, 'Selected:', this.selectedElements.has(elementId)); + this.setElementHover(elementId, false); + this.applyEntityStyling(element, paper); + } + + /** + * Handle selection change + */ + handleSelectionChange(selectedElementIds: string[], graph: dia.Graph, paper: dia.Paper): void { + console.log('🔄 Selection change:', selectedElementIds); + this.setSelectedElements(selectedElementIds); + this.applyAllEntityStyling(graph, paper); + } +} + +// Export a default instance +export const entityStyleManager = new EntityStyleManager(); From 9b815de801b36beb73625b26ec64dbcc0f5e0d4b Mon Sep 17 00:00:00 2001 From: Lucki2g Date: Sun, 24 Aug 2025 12:20:17 +0200 Subject: [PATCH 3/7] chore: removed help banner and action banner --- Website/components/diagram/DiagramCanvas.tsx | 24 +------------------- Website/components/diagram/DiagramView.tsx | 18 --------------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/Website/components/diagram/DiagramCanvas.tsx b/Website/components/diagram/DiagramCanvas.tsx index bdb9247..8a77a2b 100644 --- a/Website/components/diagram/DiagramCanvas.tsx +++ b/Website/components/diagram/DiagramCanvas.tsx @@ -70,29 +70,7 @@ export const DiagramCanvas: React.FC = ({ children }) => { ref={canvasRef} className={`w-full h-full ${getCursor()}`} /> - - {/* Interaction mode indicators */} - {isPanning && ( -
-
- Panning... -
- )} - - {isCtrlPressed && !isPanning && ( -
- Ctrl - Hold + Drag to Pan -
- )} - - {!isCtrlPressed && !isPanning && ( -
-
- Drag to Select Area -
- )} - + {children}
); diff --git a/Website/components/diagram/DiagramView.tsx b/Website/components/diagram/DiagramView.tsx index 17696fe..fda1179 100644 --- a/Website/components/diagram/DiagramView.tsx +++ b/Website/components/diagram/DiagramView.tsx @@ -788,24 +788,6 @@ const DiagramContent = () => {

- - {/* Interaction Help Banner */} -
-
-
-
- Drag: Select area -
-
- Ctrl - + Drag: Pan diagram -
-
- 🖱️ - Scroll: Zoom -
-
-
{/* Diagram Area */}
Date: Sun, 24 Aug 2025 12:22:38 +0200 Subject: [PATCH 4/7] chore: warning regarding limitations --- Website/components/diagram/DiagramView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Website/components/diagram/DiagramView.tsx b/Website/components/diagram/DiagramView.tsx index fda1179..e4a3e2e 100644 --- a/Website/components/diagram/DiagramView.tsx +++ b/Website/components/diagram/DiagramView.tsx @@ -784,7 +784,7 @@ const DiagramContent = () => { β

- Open Beta Feature: This ER Diagram feature is currently in beta. Some functionality may not work fully. + Open Beta Feature: This ER Diagram feature is currently in beta. Some functionality may not work fully. we do not recommend more than 20 entities

From aad29d0af8a7aa23cd2bd413495c8fa1d1fb009e Mon Sep 17 00:00:00 2001 From: Lucki2g Date: Sun, 24 Aug 2025 12:29:42 +0200 Subject: [PATCH 5/7] fix: correct scale delta for quare resize --- Website/components/diagram/DiagramView.tsx | 42 ++++++++++++---------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/Website/components/diagram/DiagramView.tsx b/Website/components/diagram/DiagramView.tsx index e4a3e2e..225914a 100644 --- a/Website/components/diagram/DiagramView.tsx +++ b/Website/components/diagram/DiagramView.tsx @@ -559,44 +559,50 @@ const DiagramContent = () => { const deltaX = evt.clientX - startPointer.x; const deltaY = evt.clientY - startPointer.y; + // Adjust deltas based on paper scaling and translation + const scale = paper.scale(); + const translate = paper.translate(); + const adjustedDeltaX = deltaX / scale.sx; + const adjustedDeltaY = deltaY / scale.sy; + const newSize = { width: startSize.width, height: startSize.height }; const newPosition = { x: startPosition.x, y: startPosition.y }; // Calculate new size and position based on resize handle switch (handle) { case 'resize-se': // Southeast - newSize.width = Math.max(50, startSize.width + deltaX); - newSize.height = Math.max(30, startSize.height + deltaY); + newSize.width = Math.max(50, startSize.width + adjustedDeltaX); + newSize.height = Math.max(30, startSize.height + adjustedDeltaY); break; case 'resize-sw': // Southwest - newSize.width = Math.max(50, startSize.width - deltaX); - newSize.height = Math.max(30, startSize.height + deltaY); - newPosition.x = startPosition.x + deltaX; + newSize.width = Math.max(50, startSize.width - adjustedDeltaX); + newSize.height = Math.max(30, startSize.height + adjustedDeltaY); + newPosition.x = startPosition.x + adjustedDeltaX; break; case 'resize-ne': // Northeast - newSize.width = Math.max(50, startSize.width + deltaX); - newSize.height = Math.max(30, startSize.height - deltaY); - newPosition.y = startPosition.y + deltaY; + newSize.width = Math.max(50, startSize.width + adjustedDeltaX); + newSize.height = Math.max(30, startSize.height - adjustedDeltaY); + newPosition.y = startPosition.y + adjustedDeltaY; break; case 'resize-nw': // Northwest - newSize.width = Math.max(50, startSize.width - deltaX); - newSize.height = Math.max(30, startSize.height - deltaY); - newPosition.x = startPosition.x + deltaX; - newPosition.y = startPosition.y + deltaY; + newSize.width = Math.max(50, startSize.width - adjustedDeltaX); + newSize.height = Math.max(30, startSize.height - adjustedDeltaY); + newPosition.x = startPosition.x + adjustedDeltaX; + newPosition.y = startPosition.y + adjustedDeltaY; break; case 'resize-e': // East - newSize.width = Math.max(50, startSize.width + deltaX); + newSize.width = Math.max(50, startSize.width + adjustedDeltaX); break; case 'resize-w': // West - newSize.width = Math.max(50, startSize.width - deltaX); - newPosition.x = startPosition.x + deltaX; + newSize.width = Math.max(50, startSize.width - adjustedDeltaX); + newPosition.x = startPosition.x + adjustedDeltaX; break; case 'resize-s': // South - newSize.height = Math.max(30, startSize.height + deltaY); + newSize.height = Math.max(30, startSize.height + adjustedDeltaY); break; case 'resize-n': // North - newSize.height = Math.max(30, startSize.height - deltaY); - newPosition.y = startPosition.y + deltaY; + newSize.height = Math.max(30, startSize.height - adjustedDeltaY); + newPosition.y = startPosition.y + adjustedDeltaY; break; } From 413fb54eeb5d1b5e02304692bd757d67bf788849 Mon Sep 17 00:00:00 2001 From: Lucki2g Date: Sun, 24 Aug 2025 12:41:19 +0200 Subject: [PATCH 6/7] fix: allow multi selection of squares and texts as well --- Website/hooks/useDiagram.ts | 10 ++-------- Website/lib/entity-styling.ts | 4 ---- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/Website/hooks/useDiagram.ts b/Website/hooks/useDiagram.ts index 0267b37..4388dab 100644 --- a/Website/hooks/useDiagram.ts +++ b/Website/hooks/useDiagram.ts @@ -70,7 +70,7 @@ export const useDiagram = (): DiagramState & DiagramActions => { const selectedElementsRef = useRef([]); const cleanupRef = useRef<(() => void) | null>(null); const isAddingAttributeRef = useRef(false); - + const [zoom, setZoomState] = useState(1); const [isPanning, setIsPanningState] = useState(false); const [selectedElements, setSelectedElements] = useState([]); @@ -892,9 +892,6 @@ export const useDiagram = (): DiagramState & DiagramActions => { const bbox = element.getBBox(); const elementType = element.get('type'); - // Only select entity elements, not squares or text - if (elementType !== 'delegate.entity') return false; - // Check if element center is within selection bounds const elementCenterX = bbox.x + bbox.width / 2; const elementCenterY = bbox.y + bbox.height / 2; @@ -925,9 +922,6 @@ export const useDiagram = (): DiagramState & DiagramActions => { const element = elementView.model; const elementType = element.get('type'); - // Only handle entity elements for group dragging and selection - if (elementType !== 'delegate.entity') return; - const elementId = element.id.toString(); const isCtrlPressed = evt.originalEvent?.ctrlKey || evt.originalEvent?.metaKey; const currentSelection = selectedElementsRef.current; @@ -950,7 +944,7 @@ export const useDiagram = (): DiagramState & DiagramActions => { // Store initial positions for all selected elements currentSelection.forEach(id => { const elem = graphRef.current?.getCell(id); - if (elem && elem.get('type') === 'delegate.entity') { + if (elem) { const pos = elem.position(); groupDragStartPositions[id] = { x: pos.x, y: pos.y }; } diff --git a/Website/lib/entity-styling.ts b/Website/lib/entity-styling.ts index 9c49b5b..3053f86 100644 --- a/Website/lib/entity-styling.ts +++ b/Website/lib/entity-styling.ts @@ -140,7 +140,6 @@ export class EntityStyleManager { const elementId = element.id.toString(); const state = this.getElementState(elementId); - console.log('🎨 Applying styling to entity:', elementId, 'State:', state); // Get the element view and HTML content const elementView = element.findView(paper); @@ -154,7 +153,6 @@ export class EntityStyleManager { // Get styling for current state const styling = this.getStyleForState(state); - console.log('🎨 Styling to apply:', styling); // Always apply styling based on current state (selection takes precedence over hover) htmlContent.style.border = styling.border; @@ -222,7 +220,6 @@ export class EntityStyleManager { */ handleEntityMouseLeave(element: dia.Element, paper: dia.Paper): void { const elementId = element.id.toString(); - console.log('🖱️ Mouse leave entity:', elementId, 'Selected:', this.selectedElements.has(elementId)); this.setElementHover(elementId, false); this.applyEntityStyling(element, paper); } @@ -231,7 +228,6 @@ export class EntityStyleManager { * Handle selection change */ handleSelectionChange(selectedElementIds: string[], graph: dia.Graph, paper: dia.Paper): void { - console.log('🔄 Selection change:', selectedElementIds); this.setSelectedElements(selectedElementIds); this.applyAllEntityStyling(graph, paper); } From 0b9097679deb7faa316380810d9eab4e6144447d Mon Sep 17 00:00:00 2001 From: Lucki2g Date: Sun, 24 Aug 2025 12:45:47 +0200 Subject: [PATCH 7/7] fix: ESLint errors --- Website/components/diagram/DiagramView.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Website/components/diagram/DiagramView.tsx b/Website/components/diagram/DiagramView.tsx index 225914a..8db9229 100644 --- a/Website/components/diagram/DiagramView.tsx +++ b/Website/components/diagram/DiagramView.tsx @@ -561,7 +561,6 @@ const DiagramContent = () => { // Adjust deltas based on paper scaling and translation const scale = paper.scale(); - const translate = paper.translate(); const adjustedDeltaX = deltaX / scale.sx; const adjustedDeltaY = deltaY / scale.sy; @@ -638,7 +637,7 @@ const DiagramContent = () => { useEffect(() => { if (!paper) return; - const handleBlankClick = (evt: dia.Event, x: number, y: number) => { + const handleBlankClick = () => { if (selectedSquare) { selectedSquare.hideResizeHandles(); setSelectedSquare(null); @@ -646,7 +645,7 @@ const DiagramContent = () => { } } - const handleBlankPointerDown = (evt: dia.Event, x: number, y: number) => { + const handleBlankPointerDown = (_: dia.Event, x: number, y: number) => { // Don't set selected area if we were panning if (!isPanning) {