Files
house-plan-maker/apps/client/src/components/editor/projection/ProjectionWindow.tsx
T
alexei.dolgolyov 521ea5e85b feat: add outlet direction (horizontal/vertical), wall light styles, floor textures, and stretch ceiling
- 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
2026-04-12 20:52:49 +03:00

124 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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>
);
}