/** * Component Property Dialog * * Displays component properties and actions when a component is selected. * Shows pin roles, rotation, and delete options. */ import React, { useEffect, useState, useRef } from 'react'; import type { ComponentMetadata } from '../../types/component-metadata'; import './ComponentPropertyDialog.css'; interface ComponentPropertyDialogProps { componentId: string; componentMetadata: ComponentMetadata; componentProperties: Record; position: { x: number; y: number }; pinInfo: Array<{ name: string; x: number; y: number; signals?: any[]; description?: string }>; onClose: () => void; onRotate: (componentId: string) => void; onDelete: (componentId: string) => void; lockComponents?: boolean; } export const ComponentPropertyDialog: React.FC = ({ componentId, componentMetadata, componentProperties, position, pinInfo, onClose, onRotate, onDelete, lockComponents = false, }) => { const dialogRef = useRef(null); const [dialogPosition, setDialogPosition] = useState({ x: 0, y: 0 }); // Calculate dialog position on mount useEffect(() => { if (!dialogRef.current) return; const dialogWidth = 220; const dialogHeight = dialogRef.current.offsetHeight || 200; const canvasElement = document.querySelector('.canvas-content'); if (!canvasElement) return; const canvasWidth = canvasElement.clientWidth; const canvasHeight = canvasElement.clientHeight; // Try positioning to the right of component let x = position.x + 150; // Approximate component width + gap let y = position.y; // If off-screen right, position to left if (x + dialogWidth > canvasWidth) { x = Math.max(10, position.x - dialogWidth - 10); } // Keep within vertical bounds if (y + dialogHeight > canvasHeight) { y = Math.max(10, canvasHeight - dialogHeight - 10); } setDialogPosition({ x, y }); }, [position]); // Close on Escape key useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { onClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [onClose]); // Close when clicking outside useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (dialogRef.current && !dialogRef.current.contains(e.target as Node)) { onClose(); } }; // Delay to avoid immediate close from the click that opened the dialog const timer = setTimeout(() => { document.addEventListener('mousedown', handleClickOutside); }, 100); return () => { clearTimeout(timer); document.removeEventListener('mousedown', handleClickOutside); }; }, [onClose]); return (
e.stopPropagation()} > {/* Header */}
{componentMetadata.name}
{/* Pin Roles Section */} {pinInfo.length > 0 && (
Pin Roles:
{pinInfo.map((pin) => (
• {pin.name} {pin.description && ( ({pin.description}) )}
))}
)} {/* Current Arduino Pin Assignment */} {componentProperties.pin !== undefined && (
Arduino Pin:
{componentProperties.pin >= 14 ? `A${componentProperties.pin - 14}` : `D${componentProperties.pin}`}
)} {/* Action Buttons */}
{!lockComponents && ( )}
); };