521ea5e85b
- Add configurable outlet direction (horizontal/vertical) stored in metadata - Add wall light style variants (classic, pendant-globe, sconce-up, sconce-down) - Add PBR floor textures including natural oak - Add stretch ceiling offset support with DB migration - Add furniture surface texture selection - Add canvas theme colors utility for dark mode support - Update projection views with improved rendering - Add EN and RU translations for all new properties
124 lines
3.6 KiB
TypeScript
124 lines
3.6 KiB
TypeScript
import { Group, Rect, Line } from 'react-konva';
|
||
import type { ProjectedOpening } from '../utils/projectionMapping';
|
||
import { projectionToPixel } from '../utils/projectionMapping';
|
||
import { useCanvasColors } from '../utils/canvasThemeColors';
|
||
|
||
interface ProjectionWindowProps {
|
||
readonly projected: ProjectedOpening;
|
||
readonly wallHeight: number;
|
||
readonly scale: number;
|
||
readonly padding: number;
|
||
readonly isSelected: boolean;
|
||
readonly isDragging?: boolean;
|
||
readonly dragAlongWall?: number;
|
||
readonly onClick: () => void;
|
||
readonly onDragStart?: (openingId: string, evt: MouseEvent) => void;
|
||
}
|
||
|
||
/** Render a window in wall elevation view. */
|
||
export function ProjectionWindow({
|
||
projected,
|
||
wallHeight,
|
||
scale,
|
||
padding,
|
||
isSelected,
|
||
isDragging = false,
|
||
dragAlongWall,
|
||
onClick,
|
||
onDragStart,
|
||
}: ProjectionWindowProps) {
|
||
const colors = useCanvasColors();
|
||
const { rect, opening } = projected;
|
||
|
||
const displayX = isDragging && dragAlongWall != null
|
||
? dragAlongWall - opening.width / 2
|
||
: rect.x;
|
||
|
||
const topLeft = projectionToPixel(displayX, rect.y + rect.height, wallHeight, scale, padding);
|
||
const pxWidth = rect.width * scale;
|
||
const pxHeight = rect.height * scale;
|
||
|
||
const frameInset = 3;
|
||
|
||
return (
|
||
<Group
|
||
onClick={onClick}
|
||
onMouseDown={(e) => {
|
||
if (onDragStart && e.evt.button === 0) {
|
||
onDragStart(opening.id, e.evt);
|
||
}
|
||
}}
|
||
>
|
||
{/* Drag ghost outline */}
|
||
{isDragging && (
|
||
<Rect
|
||
x={topLeft.x - 2}
|
||
y={topLeft.y - 2}
|
||
width={pxWidth + 4}
|
||
height={pxHeight + 4}
|
||
stroke={colors.selectedStroke}
|
||
strokeWidth={1}
|
||
dash={[3, 3]}
|
||
fill="transparent"
|
||
/>
|
||
)}
|
||
{/* Window frame (outer) */}
|
||
<Rect
|
||
x={topLeft.x}
|
||
y={topLeft.y}
|
||
width={pxWidth}
|
||
height={pxHeight}
|
||
fill="#dbeafe"
|
||
stroke={isSelected ? colors.selectedStroke : '#3b82f6'}
|
||
strokeWidth={isSelected ? 2.5 : 1.5}
|
||
/>
|
||
{/* Glass pane (inner rectangle) */}
|
||
<Rect
|
||
x={topLeft.x + frameInset}
|
||
y={topLeft.y + frameInset}
|
||
width={pxWidth - frameInset * 2}
|
||
height={pxHeight - frameInset * 2}
|
||
fill="#bfdbfe"
|
||
stroke="#93c5fd"
|
||
strokeWidth={0.5}
|
||
/>
|
||
{/* Internal mullions — N×M grid. Rendered as lines spanning the
|
||
glass area; `gridCols - 1` verticals + `gridRows - 1`
|
||
horizontals. Defaults to 2×2 for legacy windows without an
|
||
explicit grid set. */}
|
||
{(() => {
|
||
const cols = Math.max(1, Math.min(10, Math.round(opening.gridCols ?? 2)));
|
||
const rows = Math.max(1, Math.min(10, Math.round(opening.gridRows ?? 2)));
|
||
const innerLeft = topLeft.x + frameInset;
|
||
const innerTop = topLeft.y + frameInset;
|
||
const innerWidth = pxWidth - frameInset * 2;
|
||
const innerHeight = pxHeight - frameInset * 2;
|
||
const lines: React.ReactNode[] = [];
|
||
for (let i = 1; i < cols; i++) {
|
||
const x = innerLeft + (innerWidth * i) / cols;
|
||
lines.push(
|
||
<Line
|
||
key={`vmul-${i}`}
|
||
points={[x, innerTop, x, innerTop + innerHeight]}
|
||
stroke="#3b82f6"
|
||
strokeWidth={1}
|
||
/>,
|
||
);
|
||
}
|
||
for (let i = 1; i < rows; i++) {
|
||
const y = innerTop + (innerHeight * i) / rows;
|
||
lines.push(
|
||
<Line
|
||
key={`hmul-${i}`}
|
||
points={[innerLeft, y, innerLeft + innerWidth, y]}
|
||
stroke="#3b82f6"
|
||
strokeWidth={1}
|
||
/>,
|
||
);
|
||
}
|
||
return lines;
|
||
})()}
|
||
</Group>
|
||
);
|
||
}
|