From 442c4f09ac2eacf75bee93ebce90b9d9217e211d Mon Sep 17 00:00:00 2001 From: Stephan Egli Date: Fri, 10 Jan 2025 18:01:45 +0100 Subject: [PATCH] Major change to use automerge patches for all updates, code still needs cleanup --- src/canvasManager.js | 151 +++++++++++++++++++++++++++++++++---------- src/docManager.js | 17 +++-- src/main.js | 80 ++++++++++------------- 3 files changed, 166 insertions(+), 82 deletions(-) diff --git a/src/canvasManager.js b/src/canvasManager.js index 30fdaee..d99e4da 100644 --- a/src/canvasManager.js +++ b/src/canvasManager.js @@ -9,60 +9,145 @@ const SVG_NS = "http://www.w3.org/2000/svg"; * Renders all shapes into an SVG container * @param {SVGElement} container * @param {Array} shapesArray - * @param {string|null} movingShapeId + * @param {Array} patches */ -export function drawAllShapesOnCanvas(container, shapesArray, movingShapeId = null) { +export function drawAllShapesOnCanvas(container, shapesArray, patches = []) { + console.log("drawAllShapesOnCanvas container:",container) if (!container || !(container instanceof SVGElement)) { return; } - - // Create or update shapes - shapesArray.forEach(shape => { - let element = document.getElementById(shape.id); + + console.log("Received patches:", patches); + + // Ensure patches is an array + const patchArray = patches?.patches || patches || []; + console.log("patchArray:",patchArray) + if (!Array.isArray(patchArray)) { + console.warn("Expected patches to be an array, got:", typeof patchArray, patchArray); + // Fall back to full redraw + patches = []; + } + + // If no patches, do a full redraw + if (!patchArray || patchArray.length === 0) { + // Clear container + while (container.firstChild) { + container.removeChild(container.firstChild); + } - if (!element) { + // Draw all shapes + shapesArray.forEach(shape => { + let element; if (shape.type === "circle") { element = document.createElementNS(SVG_NS, "circle"); element.id = shape.id; - container.appendChild(element); + updateCircleAttributes(element, shape); } else if (shape.type === "triangle") { element = document.createElementNS(SVG_NS, "polygon"); element.id = shape.id; + updateTriangleAttributes(element, shape); + } + if (element) { container.appendChild(element); } - } + }); + return; + } - if (!element) { - return; - } + // Process patches + patchArray.forEach(patch => { + console.log("patch:",patch) + // Check if this patch affects the shapes array + if (patch.path[0] === 'shapes') { + console.log("patch.path[0] shapes:",patch.path[0]) + if (patch.action === 'put') { + // New shape added or shape updated + console.log("patch.path[1] shapeindex:",patch.path[1]) + const shapeIndex = patch.path[1]; + const shape = shapesArray[shapeIndex]; + console.log("shape:",shape) + if (!shape) 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") { + let element = document.getElementById(shape.id); + if (!element) { + console.log("create new element, shape:",shape) + // Create new element + if (shape.type === "circle") { + element = document.createElementNS(SVG_NS, "circle"); + element.id = shape.id; + updateCircleAttributes(element, shape); + } else if (shape.type === "triangle") { + element = document.createElementNS(SVG_NS, "polygon"); + element.id = shape.id; + updateTriangleAttributes(element, shape); + } + if (element) { + container.appendChild(element); + } + } else { + // Update existing element + console.log("update existing element, shape:",shape) + if (shape.type === "circle") { + updateCircleAttributes(element, shape); + } else if (shape.type === "triangle") { + updateTriangleAttributes(element, shape); + } + } + } else if (patch.action === 'del') { + // Shape removed + const element = document.getElementById(patch.path[1]); + if (element) { + container.removeChild(element); + } + } + } + }); +} + +function updateSpecificAttribute(element, shape, path) { + if (shape.type === "circle") { + switch (path[path.length - 1]) { + case 'x': + element.setAttributeNS(null, "cx", shape.x); + break; + case 'y': + element.setAttributeNS(null, "cy", shape.y); + break; + case 'radius': + element.setAttributeNS(null, "r", shape.radius); + break; + case 'color': + element.setAttributeNS(null, "fill", shape.color || "black"); + break; + } + } else if (shape.type === "triangle") { + if (path.includes('coordinates')) { const points = shape.coordinates .map(p => `${p.x},${p.y}`) .join(" "); element.setAttributeNS(null, "points", points); + } else if (path.includes('color')) { element.setAttributeNS(null, "fill", shape.color || "black"); - element.setAttributeNS(null, "stroke", "black"); - element.setAttributeNS(null, "stroke-width", "1"); } - }); - - // 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); - } - }); } } +function updateCircleAttributes(element, shape) { + 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"); +} + +function updateTriangleAttributes(element, shape) { + 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"); +} + diff --git a/src/docManager.js b/src/docManager.js index d16fa70..9be62a1 100644 --- a/src/docManager.js +++ b/src/docManager.js @@ -73,13 +73,22 @@ export function addTriangle(x1, y1, x2, y2, x3, y3) { /** * Move a shape in the document + * + * Watch out: do not use the Array.find method, because these are Proxies and the find method is not supported on Proxies. + * Also make sure that you compare numbers with numbers, not strings. */ export function moveShape(shapeId, newX, newY) { handle.change(doc => { - const shape = doc.shapes.find(s => s.id === shapeId) - if (shape) { - shape.x = newX - shape.y = newY + // Find the index of the shape instead of using find() + // TODO faster implementation than for loop + for (let i = 0; i < doc.shapes.length; i++) { + console.log("moveShape doc.shapes[i]:",i,doc.shapes[i].id,Number(shapeId)) + if (doc.shapes[i].id === Number(shapeId)) { + console.log("moveShape doc.shapes[i]:",i,doc.shapes[i].x,newX,doc.shapes[i].y,newY) + doc.shapes[i].x = newX; + doc.shapes[i].y = newY; + break; + } } }) } diff --git a/src/main.js b/src/main.js index be29100..3cb7a6c 100644 --- a/src/main.js +++ b/src/main.js @@ -29,14 +29,15 @@ await handle.whenReady() 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); - // Only redraw all if we're not currently dragging - if (!isDragging) { - drawAllShapesOnCanvas(container, doc.shapes); - } -}) +handle.on("change", (change) => { + console.log("Document changed! Received patches:", change); + console.log("Current shapes:", change.doc.shapes); + // Only redraw if we're not currently dragging + // TODO if (!isDragging) { + // TODO modify name of the subsequent function: + drawAllShapesOnCanvas(container, change.doc.shapes, change.patches); + +}); // 3. Buttons holen const addCircleButton = document.getElementById("addCircleButton"); @@ -55,27 +56,35 @@ addTriangleButton.addEventListener("click", () => { // Mousedown: Prüfen, ob wir auf ein Shape klicken 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; - offsetX = svgP.x - clickedShape.x; - offsetY = svgP.y - clickedShape.y; - } + const clickedElement = e.target; + if (clickedElement.tagName === 'circle' || clickedElement.tagName === 'polygon') { + isDragging = true; + selectedShapeId = clickedElement.id; + console.log("selectedShapeId:",selectedShapeId) + // Get SVG coordinates + // TODO reenable ? +/* const pt = container.createSVGPoint(); + pt.x = e.clientX; + pt.y = e.clientY; + const svgP = pt.matrixTransform(container.getScreenCTM().inverse()); + console.log("svgP:",svgP) + // Get the shape from the document + const shape = handle.docSync().shapes.find(s => s.id === selectedShapeId); + if (shape) { + offsetX = svgP.x - shape.x; + offsetY = svgP.y - shape.y; + } + console.log("offsetX:",offsetX) + console.log("offsetY:",offsetY) */ + } }); // Throttled version of moveShape const throttledMoveShape = throttle((shapeId, x, y) => { + console.log("throttledMoveShape shapeId:",shapeId) moveShape(shapeId, x, y); // Pass the moving shape ID to drawAllShapesOnCanvas - drawAllShapesOnCanvas(container, handle.docSync().shapes, shapeId); + // TODO no longer needed ?drawAllShapesOnCanvas(container, handle.docSync().shapes) }, 16); // 50ms throttle time (20 updates per second) // Mousemove: Shape verschieben, sofern dragging aktiv ist @@ -89,7 +98,9 @@ container.addEventListener("mousemove", (e) => { const newX = svgP.x - offsetX; const newY = svgP.y - offsetY; - +console.log("newX:",newX) +console.log("newY:",newY) +console.log("selectedShapeId:",selectedShapeId) throttledMoveShape(selectedShapeId, newX, newY); }); @@ -99,24 +110,3 @@ container.addEventListener("mouseup", () => { selectedShapeId = null; }); -/** - * Gibt das Shape zurück, das die Koordinaten (mx, my) enthält/berührt - * (Vereinfacht: nur für Kreise) - */ -function getShapeAtCoordinates(mx, my) { - const doc = handle.docSync() - console.log("DOC IN GETSHAPEATCOORDIATES:",doc) - // Hier nur ein einfacher Loop durch alle Kreise - for (let shape of doc.shapes) { - if (shape.type === "circle") { - const dist = Math.sqrt((mx - shape.x) ** 2 + (my - shape.y) ** 2); - console.log("calculated distance and radius:",dist,shape.radius) - if (dist <= shape.radius) { - console.log("Found shape:",shape) - return shape; - } - } - // ggf. erweitern für Dreiecke oder andere Formen - } - return null; -} \ No newline at end of file