import { memo, useMemo } from 'react'; import { Layer, Group, Rect, Line } from 'react-konva'; import type { Point, FurnitureItem } from '@house-plan-maker/shared'; import { BedSilhouette } from '../symbols/furniture/BedSilhouette'; import { DeskSilhouette } from '../symbols/furniture/DeskSilhouette'; import { WardrobeSilhouette } from '../symbols/furniture/WardrobeSilhouette'; import { SofaSilhouette } from '../symbols/furniture/SofaSilhouette'; import { TableSilhouette } from '../symbols/furniture/TableSilhouette'; import { ChairSilhouette } from '../symbols/furniture/ChairSilhouette'; import { ShelfSilhouette } from '../symbols/furniture/ShelfSilhouette'; import { TvSilhouette } from '../symbols/furniture/TvSilhouette'; import { findCollidingFurniture } from '../utils/collisionDetection'; interface FurnitureLayerProps { readonly items: readonly FurnitureItem[]; readonly zoom: number; readonly panOffset: Point; readonly selectedIds: ReadonlySet; readonly visible?: boolean; } const FURNITURE_COLOR = '#495057'; const FURNITURE_FILL = 'rgba(222, 226, 230, 0.5)'; const SELECTED_COLOR = '#4c6ef5'; const SELECTED_FILL = 'rgba(76, 110, 245, 0.1)'; const COLLISION_COLOR = '#e03131'; const COLLISION_FILL = 'rgba(224, 49, 49, 0.1)'; function toScreen(point: Point, zoom: number, panOffset: Point): { x: number; y: number } { return { x: point.x * zoom + panOffset.x, y: point.y * zoom + panOffset.y, }; } export const FurnitureLayer = memo(function FurnitureLayer({ items, zoom, panOffset, selectedIds, visible = true, }: FurnitureLayerProps) { const collidingIds = useMemo(() => findCollidingFurniture(items), [items]); return ( {items.map((item) => { // x,y is the top-left corner; compute center for silhouette rendering const centerX = item.x + item.width / 2; const centerY = item.y + item.depth / 2; const screenCenter = toScreen({ x: centerX, y: centerY }, zoom, panOffset); const isSelected = selectedIds.has(item.id); const isColliding = collidingIds.has(item.id); const widthPx = item.width * zoom; const depthPx = item.depth * zoom; const color = isColliding ? COLLISION_COLOR : isSelected ? SELECTED_COLOR : FURNITURE_COLOR; const fillColor = isColliding ? COLLISION_FILL : isSelected ? SELECTED_FILL : FURNITURE_FILL; return ( {renderFurnitureSilhouette( item.type, screenCenter.x, screenCenter.y, widthPx, depthPx, item.rotation, color, fillColor, )} {/* Rotation handle indicator for selected furniture */} {isSelected && ( )} ); })} ); }); interface RotationHandleProps { readonly x: number; readonly y: number; readonly depthPx: number; readonly rotation: number; } function RotationHandle({ x, y, depthPx, rotation }: RotationHandleProps) { const handleOffset = depthPx / 2 + 12; const rad = (rotation * Math.PI) / 180; const hx = x - Math.sin(rad) * handleOffset; const hy = y - Math.cos(rad) * handleOffset; return ( {/* Line from center to handle */} {/* Handle circle */} ); } function renderFurnitureSilhouette( type: string, x: number, y: number, width: number, depth: number, rotation: number, color: string, fillColor: string, ): React.ReactNode { const props = { x, y, width, depth, rotation, color, fillColor }; switch (type) { case 'BED': return ; case 'DESK': return ; case 'WARDROBE': return ; case 'SOFA': return ; case 'TABLE': return ; case 'CHAIR': return ; case 'SHELF': case 'BOOKCASE': return ; case 'NIGHTSTAND': return ; case 'DRESSER': return ; case 'TV': return ; default: // Generic rectangle for OTHER / unknown return ( ); } }