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 && (
-
+
)}
+ {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 */}
+
+
+
+
+ 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 && (
-
- )}
-
- {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 */}
-
-
-
-
- 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) {