From dcd471e2be86e8647cf29fd3b69e6f9889feab8a Mon Sep 17 00:00:00 2001 From: Stephan Egli Date: Fri, 10 Jan 2025 14:43:24 +0100 Subject: [PATCH] Replaced canvas by SVG objects allowing direct DOM manipulation --- index.html | 6 ++- src/canvasManager.js | 106 +++++++++++++++++++++++++------------------ src/main.js | 50 +++++++++++--------- 3 files changed, 95 insertions(+), 67 deletions(-) diff --git a/index.html b/index.html index bb9c0ba..d8f2b3f 100644 --- a/index.html +++ b/index.html @@ -17,7 +17,11 @@ - + + diff --git a/src/canvasManager.js b/src/canvasManager.js index 241f4fd..30fdaee 100644 --- a/src/canvasManager.js +++ b/src/canvasManager.js @@ -1,50 +1,68 @@ // js/canvasManager.js +let backgroundCanvas; +let lastShapesArray = []; + +const SVG_NS = "http://www.w3.org/2000/svg"; + /** - * Zeichnet alle Shapes auf dem Canvas neu - * @param {HTMLCanvasElement} canvas - * @param {Array} shapesArray - Array der Shapes aus dem Dokument + * Renders all shapes into an SVG container + * @param {SVGElement} container + * @param {Array} shapesArray + * @param {string|null} movingShapeId */ -export function drawAllShapesOnCanvas(canvas, shapesArray) { - const ctx = canvas.getContext("2d"); - - // Canvas leerräumen - ctx.clearRect(0, 0, canvas.width, canvas.height); - +export function drawAllShapesOnCanvas(container, shapesArray, movingShapeId = null) { + if (!container || !(container instanceof SVGElement)) { + return; + } + + // Create or update shapes shapesArray.forEach(shape => { - if (shape.type === "circle") { - drawCircle(ctx, shape); - } else if (shape.type === "triangle") { - drawTriangle(ctx, shape); - } + let element = document.getElementById(shape.id); + + if (!element) { + if (shape.type === "circle") { + element = document.createElementNS(SVG_NS, "circle"); + element.id = shape.id; + container.appendChild(element); + } else if (shape.type === "triangle") { + element = document.createElementNS(SVG_NS, "polygon"); + element.id = shape.id; + container.appendChild(element); + } + } + + if (!element) { + return; + } + + // Update element attributes using setAttributeNS for SVG + if (shape.type === "circle") { + element.setAttributeNS(null, "cx", shape.x); + element.setAttributeNS(null, "cy", shape.y); + element.setAttributeNS(null, "r", shape.radius); + element.setAttributeNS(null, "fill", shape.color || "black"); + element.setAttributeNS(null, "stroke", "black"); + element.setAttributeNS(null, "stroke-width", "1"); + } else if (shape.type === "triangle") { + const points = shape.coordinates + .map(p => `${p.x},${p.y}`) + .join(" "); + element.setAttributeNS(null, "points", points); + element.setAttributeNS(null, "fill", shape.color || "black"); + element.setAttributeNS(null, "stroke", "black"); + element.setAttributeNS(null, "stroke-width", "1"); + } }); - } - - /** - * Zeichnet einen Kreis - * @param {CanvasRenderingContext2D} ctx - * @param {Object} circle - */ - function drawCircle(ctx, circle) { - ctx.beginPath(); - ctx.arc(circle.x, circle.y, circle.radius, 0, 2 * Math.PI); - ctx.fillStyle = circle.color || "black"; - ctx.fill(); - } - - /** - * Zeichnet ein Dreieck - * @param {CanvasRenderingContext2D} ctx - * @param {Object} triangle - */ - function drawTriangle(ctx, triangle) { - const [p1, p2, p3] = triangle.coordinates; - ctx.beginPath(); - ctx.moveTo(p1.x, p1.y); - ctx.lineTo(p2.x, p2.y); - ctx.lineTo(p3.x, p3.y); - ctx.closePath(); - ctx.fillStyle = triangle.color || "black"; - ctx.fill(); - } - + + // Only clean up if we have shapes to compare against + if (shapesArray && Array.isArray(shapesArray)) { + const currentIds = shapesArray.map(shape => String(shape.id)); + Array.from(container.children).forEach(element => { + if (!currentIds.includes(element.id)) { + container.removeChild(element); + } + }); + } +} + diff --git a/src/main.js b/src/main.js index 0d4b5fb..be29100 100644 --- a/src/main.js +++ b/src/main.js @@ -7,7 +7,7 @@ let selectedShapeId = null; let offsetX = 0; let offsetY = 0; -const canvas = document.getElementById("shapesCanvas") +const container = document.getElementById("shapesCanvas"); // Add throttle function at the top function throttle(func, limit) { @@ -26,13 +26,16 @@ const handle = initRepo() await handle.whenReady() // draw current state -drawAllShapesOnCanvas(canvas, handle.docSync().shapes) +drawAllShapesOnCanvas(container, handle.docSync().shapes) // 2. Subscribe so we can re-render whenever doc changes // Listen for 'change' events handle.on("change", ({doc}) => { - console.log("Document changed! Drawallshapes", doc) - drawAllShapesOnCanvas(canvas, doc.shapes) + console.log("Document changed! Drawallshapes", doc); + // Only redraw all if we're not currently dragging + if (!isDragging) { + drawAllShapesOnCanvas(container, doc.shapes); + } }) // 3. Buttons holen @@ -51,44 +54,47 @@ addTriangleButton.addEventListener("click", () => { }); // Mousedown: Prüfen, ob wir auf ein Shape klicken -canvas.addEventListener("mousedown", (e) => { - const { offsetX: mouseX, offsetY: mouseY } = e; - - // 1. Shape unter Maus suchen - const clickedShape = getShapeAtCoordinates(mouseX, mouseY); - console.log("Clicked shape:",clickedShape) - +container.addEventListener("mousedown", (e) => { + // Get SVG coordinates + const pt = container.createSVGPoint(); + pt.x = e.clientX; + pt.y = e.clientY; + const svgP = pt.matrixTransform(container.getScreenCTM().inverse()); + + const clickedShape = getShapeAtCoordinates(svgP.x, svgP.y); + if (clickedShape) { isDragging = true; selectedShapeId = clickedShape.id; - - // Damit das Shape unter dem Mauszeiger bleibt, merken wir uns den "Offset" - offsetX = mouseX - clickedShape.x; - offsetY = mouseY - clickedShape.y; + offsetX = svgP.x - clickedShape.x; + offsetY = svgP.y - clickedShape.y; } }); // Throttled version of moveShape const throttledMoveShape = throttle((shapeId, x, y) => { moveShape(shapeId, x, y); + // Pass the moving shape ID to drawAllShapesOnCanvas + drawAllShapesOnCanvas(container, handle.docSync().shapes, shapeId); }, 16); // 50ms throttle time (20 updates per second) // Mousemove: Shape verschieben, sofern dragging aktiv ist -canvas.addEventListener("mousemove", (e) => { +container.addEventListener("mousemove", (e) => { if (!isDragging || selectedShapeId === null) return; - const { offsetX: mouseX, offsetY: mouseY } = e; + const pt = container.createSVGPoint(); + pt.x = e.clientX; + pt.y = e.clientY; + const svgP = pt.matrixTransform(container.getScreenCTM().inverse()); - // Neue Koordinaten berechnen - const newX = mouseX - offsetX; - const newY = mouseY - offsetY; + const newX = svgP.x - offsetX; + const newY = svgP.y - offsetY; - // Use throttled version instead throttledMoveShape(selectedShapeId, newX, newY); }); // Mouseup: Dragging beenden -canvas.addEventListener("mouseup", () => { +container.addEventListener("mouseup", () => { isDragging = false; selectedShapeId = null; });