Used classes for DOM handling of different shapes

This commit is contained in:
Stephan Egli 2025-01-16 17:30:02 +01:00
parent 57e3c9989e
commit 39a6efc076
5 changed files with 119 additions and 93 deletions

View File

@ -1,16 +1,8 @@
// js/canvasManager.js
let backgroundCanvas;
let lastShapesArray = [];
import { Circle } from './shapes/Circle';
import { Triangle } from './shapes/Triangle';
const SVG_NS = "http://www.w3.org/2000/svg";
/**
* Renders all shapes into an SVG container
* @param {SVGElement} container
* @param {Array} shapesArray
* @param {Array} patches
*/
export function createOrUpdateShapes(container, shapesArray, patches = []) {
if (!container || !(container instanceof SVGElement)) {
return;
@ -20,7 +12,6 @@ export function createOrUpdateShapes(container, shapesArray, patches = []) {
const patchArray = patches?.patches || patches || [];
if (!Array.isArray(patchArray)) {
console.warn("Expected patches to be an array, got:", typeof patchArray, patchArray);
// Fall back to full redraw
patches = [];
}
@ -32,18 +23,10 @@ export function createOrUpdateShapes(container, shapesArray, patches = []) {
}
// Draw all shapes
shapesArray.forEach(shape => {
let 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) {
shapesArray.forEach(shapeData => {
const shape = createShapeInstance(shapeData);
if (shape) {
const element = shape.createSVGElement();
container.appendChild(element);
}
});
@ -52,38 +35,25 @@ export function createOrUpdateShapes(container, shapesArray, patches = []) {
// Process patches
patchArray.forEach(patch => {
// Check if this patch affects the shapes array
if (patch.path[0] === 'shapes') {
if (patch.action === 'put') {
const shapeIndex = patch.path[1];
const shape = shapesArray[shapeIndex];
const shapeData = shapesArray[shapeIndex];
if (!shapeData) return;
const shape = createShapeInstance(shapeData);
if (!shape) return;
let element = document.getElementById(shape.id);
let element = document.getElementById(shapeData.id);
if (!element) {
// 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);
}
element = shape.createSVGElement();
container.appendChild(element);
} else {
// Update existing element
if (shape.type === "circle") {
updateCircleAttributes(element, shape);
} else if (shape.type === "triangle") {
updateTriangleAttributes(element, shape);
}
shape.updateAttributes(element);
}
} else if (patch.action === 'del') {
// Shape removed
const element = document.getElementById(patch.path[1]);
if (element) {
container.removeChild(element);
@ -93,50 +63,15 @@ export function createOrUpdateShapes(container, shapesArray, patches = []) {
});
}
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 createShapeInstance(shapeData) {
switch (shapeData.type) {
case "circle":
return new Circle(shapeData.id, shapeData.x, shapeData.y, shapeData.radius, shapeData.color);
case "triangle":
return new Triangle(shapeData.id, shapeData.coordinates, shapeData.color);
default:
console.warn(`Unknown shape type: ${shapeData.type}`);
return null;
}
}
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");
}

View File

@ -21,12 +21,12 @@ export async function initRepo() {
// Check for existing docId in URL hash
const rootDocUrl = document.location.hash.substring(1)
if (rootDocUrl && isValidAutomergeUrl(rootDocUrl)) {
handle = repo.find(rootDocUrl)
// Wait for handle to be ready
await handle.whenReady()
// Check and upgrade schema if needed
handle.change(doc => {
if (!doc.schemaVersion) {
@ -83,7 +83,7 @@ export function addTriangle(x1, y1, x2, y2, x3, y3) {
coordinates: [
{ x: x1, y: y1 },
{ x: x2, y: y2 },
{ x: x3, y: y3 },
{ x: x3, y: y3 }
],
color: "blue"
});
@ -102,8 +102,26 @@ export function moveShape(shapeId, newX, newY) {
// TODO faster implementation than for loop
for (let i = 0; i < doc.shapes.length; i++) {
if (doc.shapes[i].id === Number(shapeId)) {
doc.shapes[i].x = newX;
doc.shapes[i].y = newY;
if (doc.shapes[i].type === "circle") {
doc.shapes[i].x = newX;
doc.shapes[i].y = newY;
} else if (doc.shapes[i].type === "triangle") {
// Calculate the current center
const center = {
x: doc.shapes[i].coordinates.reduce((sum, p) => sum + p.x, 0) / 3,
y: doc.shapes[i].coordinates.reduce((sum, p) => sum + p.y, 0) / 3
};
// Calculate the movement delta
const dx = newX - center.x;
const dy = newY - center.y;
// Move all points
doc.shapes[i].coordinates = doc.shapes[i].coordinates.map(p => ({
x: p.x + dx,
y: p.y + dy
}));
}
break;
}
}

22
src/shapes/Circle.js Normal file
View File

@ -0,0 +1,22 @@
import { Shape } from './Shape';
export class Circle extends Shape {
constructor(id, x, y, radius, color = "red") {
super(id, "circle", color);
this.x = x;
this.y = y;
this.radius = radius;
}
getSVGElementType() {
return "circle";
}
updateAttributes(element) {
element.setAttributeNS(null, "cx", this.x);
element.setAttributeNS(null, "cy", this.y);
element.setAttributeNS(null, "r", this.radius);
element.setAttributeNS(null, "fill", this.color);
}
}

29
src/shapes/Shape.js Normal file
View File

@ -0,0 +1,29 @@
const SVG_NS = "http://www.w3.org/2000/svg";
export class Shape {
constructor(id, type, color = "black") {
this.id = id;
this.type = type;
this.color = color;
}
createSVGElement() {
const element = document.createElementNS(SVG_NS, this.getSVGElementType());
element.id = this.id;
this.updateAttributes(element);
return element;
}
// Abstract methods
getSVGElementType() {
throw new Error("Must be implemented by derived class");
}
// this update also takes care of movements. The actual movement is done in documentManager.js
updateAttributes(element) {
throw new Error("Must be implemented by derived class");
}
move(newX, newY) {
throw new Error("Must be implemented by derived class");
}
}

22
src/shapes/Triangle.js Normal file
View File

@ -0,0 +1,22 @@
import { Shape } from './Shape';
export class Triangle extends Shape {
constructor(id, coordinates, color = "blue") {
super(id, "triangle", color);
this.coordinates = coordinates;
}
getSVGElementType() {
return "polygon";
}
updateAttributes(element) {
const points = this.coordinates
.map(p => `${p.x},${p.y}`)
.join(" ");
element.setAttributeNS(null, "points", points);
element.setAttributeNS(null, "fill", this.color);
}
}