Major change to use automerge patches for all updates, code still needs cleanup

This commit is contained in:
Stephan Egli 2025-01-10 18:01:45 +01:00
parent dcd471e2be
commit 442c4f09ac
3 changed files with 166 additions and 82 deletions

View File

@ -9,42 +9,139 @@ const SVG_NS = "http://www.w3.org/2000/svg";
* Renders all shapes into an SVG container * Renders all shapes into an SVG container
* @param {SVGElement} container * @param {SVGElement} container
* @param {Array} shapesArray * @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)) { if (!container || !(container instanceof SVGElement)) {
return; return;
} }
// Create or update shapes console.log("Received patches:", patches);
shapesArray.forEach(shape => {
let element = document.getElementById(shape.id);
if (!element) { // 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);
}
// Draw all shapes
shapesArray.forEach(shape => {
let element;
if (shape.type === "circle") { if (shape.type === "circle") {
element = document.createElementNS(SVG_NS, "circle"); element = document.createElementNS(SVG_NS, "circle");
element.id = shape.id; element.id = shape.id;
container.appendChild(element); updateCircleAttributes(element, shape);
} else if (shape.type === "triangle") { } else if (shape.type === "triangle") {
element = document.createElementNS(SVG_NS, "polygon"); element = document.createElementNS(SVG_NS, "polygon");
element.id = shape.id; element.id = shape.id;
updateTriangleAttributes(element, shape);
}
if (element) {
container.appendChild(element); container.appendChild(element);
} }
} });
if (!element) {
return; return;
} }
// Update element attributes using setAttributeNS for SVG // 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;
let element = document.getElementById(shape.id);
if (!element) {
console.log("create new element, shape:",shape)
// Create new element
if (shape.type === "circle") { 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");
}
}
}
function updateCircleAttributes(element, shape) {
element.setAttributeNS(null, "cx", shape.x); element.setAttributeNS(null, "cx", shape.x);
element.setAttributeNS(null, "cy", shape.y); element.setAttributeNS(null, "cy", shape.y);
element.setAttributeNS(null, "r", shape.radius); element.setAttributeNS(null, "r", shape.radius);
element.setAttributeNS(null, "fill", shape.color || "black"); element.setAttributeNS(null, "fill", shape.color || "black");
element.setAttributeNS(null, "stroke", "black"); element.setAttributeNS(null, "stroke", "black");
element.setAttributeNS(null, "stroke-width", "1"); element.setAttributeNS(null, "stroke-width", "1");
} else if (shape.type === "triangle") { }
function updateTriangleAttributes(element, shape) {
const points = shape.coordinates const points = shape.coordinates
.map(p => `${p.x},${p.y}`) .map(p => `${p.x},${p.y}`)
.join(" "); .join(" ");
@ -52,17 +149,5 @@ export function drawAllShapesOnCanvas(container, shapesArray, movingShapeId = nu
element.setAttributeNS(null, "fill", shape.color || "black"); element.setAttributeNS(null, "fill", shape.color || "black");
element.setAttributeNS(null, "stroke", "black"); element.setAttributeNS(null, "stroke", "black");
element.setAttributeNS(null, "stroke-width", "1"); 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);
}
});
}
} }

View File

@ -73,13 +73,22 @@ export function addTriangle(x1, y1, x2, y2, x3, y3) {
/** /**
* Move a shape in the document * 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) { export function moveShape(shapeId, newX, newY) {
handle.change(doc => { handle.change(doc => {
const shape = doc.shapes.find(s => s.id === shapeId) // Find the index of the shape instead of using find()
if (shape) { // TODO faster implementation than for loop
shape.x = newX for (let i = 0; i < doc.shapes.length; i++) {
shape.y = newY 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;
}
} }
}) })
} }

View File

@ -29,14 +29,15 @@ await handle.whenReady()
drawAllShapesOnCanvas(container, handle.docSync().shapes) drawAllShapesOnCanvas(container, handle.docSync().shapes)
// 2. Subscribe so we can re-render whenever doc changes // 2. Subscribe so we can re-render whenever doc changes
// Listen for 'change' events handle.on("change", (change) => {
handle.on("change", ({doc}) => { console.log("Document changed! Received patches:", change);
console.log("Document changed! Drawallshapes", doc); console.log("Current shapes:", change.doc.shapes);
// Only redraw all if we're not currently dragging // Only redraw if we're not currently dragging
if (!isDragging) { // TODO if (!isDragging) {
drawAllShapesOnCanvas(container, doc.shapes); // TODO modify name of the subsequent function:
} drawAllShapesOnCanvas(container, change.doc.shapes, change.patches);
})
});
// 3. Buttons holen // 3. Buttons holen
const addCircleButton = document.getElementById("addCircleButton"); const addCircleButton = document.getElementById("addCircleButton");
@ -55,27 +56,35 @@ addTriangleButton.addEventListener("click", () => {
// Mousedown: Prüfen, ob wir auf ein Shape klicken // Mousedown: Prüfen, ob wir auf ein Shape klicken
container.addEventListener("mousedown", (e) => { container.addEventListener("mousedown", (e) => {
const clickedElement = e.target;
if (clickedElement.tagName === 'circle' || clickedElement.tagName === 'polygon') {
isDragging = true;
selectedShapeId = clickedElement.id;
console.log("selectedShapeId:",selectedShapeId)
// Get SVG coordinates // Get SVG coordinates
const pt = container.createSVGPoint(); // TODO reenable ?
/* const pt = container.createSVGPoint();
pt.x = e.clientX; pt.x = e.clientX;
pt.y = e.clientY; pt.y = e.clientY;
const svgP = pt.matrixTransform(container.getScreenCTM().inverse()); const svgP = pt.matrixTransform(container.getScreenCTM().inverse());
console.log("svgP:",svgP)
const clickedShape = getShapeAtCoordinates(svgP.x, svgP.y); // Get the shape from the document
const shape = handle.docSync().shapes.find(s => s.id === selectedShapeId);
if (clickedShape) { if (shape) {
isDragging = true; offsetX = svgP.x - shape.x;
selectedShapeId = clickedShape.id; offsetY = svgP.y - shape.y;
offsetX = svgP.x - clickedShape.x; }
offsetY = svgP.y - clickedShape.y; console.log("offsetX:",offsetX)
console.log("offsetY:",offsetY) */
} }
}); });
// Throttled version of moveShape // Throttled version of moveShape
const throttledMoveShape = throttle((shapeId, x, y) => { const throttledMoveShape = throttle((shapeId, x, y) => {
console.log("throttledMoveShape shapeId:",shapeId)
moveShape(shapeId, x, y); moveShape(shapeId, x, y);
// Pass the moving shape ID to drawAllShapesOnCanvas // 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) }, 16); // 50ms throttle time (20 updates per second)
// Mousemove: Shape verschieben, sofern dragging aktiv ist // Mousemove: Shape verschieben, sofern dragging aktiv ist
@ -89,7 +98,9 @@ container.addEventListener("mousemove", (e) => {
const newX = svgP.x - offsetX; const newX = svgP.x - offsetX;
const newY = svgP.y - offsetY; const newY = svgP.y - offsetY;
console.log("newX:",newX)
console.log("newY:",newY)
console.log("selectedShapeId:",selectedShapeId)
throttledMoveShape(selectedShapeId, newX, newY); throttledMoveShape(selectedShapeId, newX, newY);
}); });
@ -99,24 +110,3 @@ container.addEventListener("mouseup", () => {
selectedShapeId = null; 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;
}