feat: add screenshot functionality to export canvas layout as a PNG image

This commit is contained in:
sebseb7
2026-06-08 00:05:48 +02:00
parent 53d9561de1
commit 14c2b9b08c
2 changed files with 69 additions and 7 deletions

View File

@@ -3,7 +3,8 @@ import { Stage, Layer, Group } from 'react-konva';
import { RoomPolygon } from './RoomPolygon';
import { Tile } from './Tile';
import { computeRoomVertices } from './utils/geometry';
import { Settings2, Plus, Trash2, Download, Upload } from 'lucide-react';
import { Settings2, Plus, Trash2, Download, Upload, Camera } from 'lucide-react';
// Fixed real-world side lengths in meters (+ 180° CW: A←C, B←D, C←A, D←B)
const SCALE = 70; // 70 pixels per meter
@@ -16,7 +17,7 @@ function App() {
const SIDE_C = isReducedSize ? 4.50 : 10.40;
const SIDE_D = 4.93;
const [alphaDeg, setAlphaDeg] = useState(90);
const alphaDeg = 90;
const [tiles, setTiles] = useState([]);
const [selectedId, setSelectedId] = useState(null);
const [dimensions, setDimensions] = useState({ width: 800, height: 600 });
@@ -28,6 +29,63 @@ function App() {
const count240x120 = tiles.filter(t => t.width === 2.4 && t.height === 1.2).length;
const countLights = tiles.filter(t => t.type === 'light').length;
const containerRef = useRef(null);
const stageRef = useRef(null);
const downloadScreenshot = () => {
if (stageRef.current) {
const currentSelectedId = selectedId;
setSelectedId(null);
// Force a slight delay to allow React to render without selection border
setTimeout(() => {
const stage = stageRef.current;
const stageCanvas = stage.toCanvas({ pixelRatio: 2 });
const exportCanvas = document.createElement('canvas');
exportCanvas.width = stageCanvas.width;
exportCanvas.height = stageCanvas.height;
const ctx = exportCanvas.getContext('2d');
// Background color matching the dark theme
ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, exportCanvas.width, exportCanvas.height);
// Draw the matching grid dots
const dotSpacing = 24 * 2;
const dotRadius = 1 * 2;
ctx.fillStyle = 'rgba(51, 65, 85, 0.3)';
const stageX = stage.x() * 2;
const stageY = stage.y() * 2;
const startX = stageX % dotSpacing;
const startY = stageY % dotSpacing;
for (let x = startX - dotSpacing; x < exportCanvas.width + dotSpacing; x += dotSpacing) {
for (let y = startY - dotSpacing; y < exportCanvas.height + dotSpacing; y += dotSpacing) {
ctx.beginPath();
ctx.arc(x, y, dotRadius, 0, Math.PI * 2);
ctx.fill();
}
}
// Draw the stage on top
ctx.drawImage(stageCanvas, 0, 0);
// Create download link
const dataUrl = exportCanvas.toDataURL('image/png');
const downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataUrl);
downloadAnchorNode.setAttribute("download", "room-layout.png");
document.body.appendChild(downloadAnchorNode);
downloadAnchorNode.click();
downloadAnchorNode.remove();
// Restore selection
setSelectedId(currentSelectedId);
}, 50);
}
};
// Responsive canvas size
useEffect(() => {
@@ -77,8 +135,6 @@ function App() {
};
const addTile = (width, height, label) => {
const isSquare = width === height;
// Assign colors based on dimensions
let color = "rgba(16, 185, 129, 0.6)"; // Default Green
let stroke = "#10b981";
@@ -264,6 +320,10 @@ function App() {
<Download size={14} /> Exportieren...
</button>
<button onClick={downloadScreenshot} className="btn btn-primary" style={{ backgroundColor: '#4b5563', boxShadow: '0 4px 6px -1px rgba(75, 85, 99, 0.4)' }}>
<Camera size={14} /> Screenshot...
</button>
<label className="btn btn-primary" style={{ backgroundColor: '#4b5563', boxShadow: '0 4px 6px -1px rgba(75, 85, 99, 0.4)', cursor: 'pointer', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Upload size={14} /> Importieren...
<input type="file" accept=".json" onChange={importTiles} style={{ display: 'none' }} />
@@ -282,6 +342,7 @@ function App() {
{/* Main Canvas Area */}
<main className="canvas-container" ref={containerRef}>
<Stage
ref={stageRef}
width={dimensions.width}
height={dimensions.height}
draggable

View File

@@ -1,7 +1,8 @@
import React, { useRef, useState } from 'react';
import React, { useRef } from 'react';
import { Rect, Group, Transformer } from 'react-konva';
export function Tile({ id, initialX, initialY, scale, meterWidth = 1.6, meterHeight = 1.6, color = "rgba(16, 185, 129, 0.6)", stroke = "#10b981", isSelected, onSelect, type, rotation = 0, onChange }) {
export function Tile({ initialX, initialY, scale, meterWidth = 1.6, meterHeight = 1.6, color = "rgba(16, 185, 129, 0.6)", stroke = "#10b981", isSelected, onSelect, type, rotation = 0, onChange }) {
const shapeRef = useRef();
const trRef = useRef();
const pixelWidth = meterWidth * scale;
@@ -54,7 +55,7 @@ export function Tile({ id, initialX, initialY, scale, meterWidth = 1.6, meterHei
y: e.target.y()
});
}}
onTransformEnd={(e) => {
onTransformEnd={() => {
const node = shapeRef.current;
node.scaleX(1);
node.scaleY(1);