Files
draw-tools/static/components/canvas-display/canvas-display.js
Thomas Nilles 77ba31ce8f Inline component templates into JS
- Replace HTML template lookups with programmatically created
  `<template>` elements.
- Add component‑specific styles and markup for CanvasDisplay, including
  a grid overlay SVG.
- Add SideMenu markup with toggle button, menu styling, and slot
  content.
2026-01-15 13:02:50 -05:00

247 lines
6.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// CanvasDisplay component logic
// This file defines the custom element <canvas-display> which renders a canvas
// and an SVG grid overlay. It reacts to backgroundcolor and gridsettings
// events from the toolbar or directly via attributes.
class CanvasDisplay extends HTMLElement {
static get observedAttributes() {
return ["bg-color", "grid-color", "grid-size", "grid-numbering"];
}
constructor() {
super();
// Attach shadow root
this.attachShadow({ mode: "open" });
// Create a template element with the canvas-display markup directly in JS
const tmpl = document.createElement("template");
tmpl.innerHTML = `
<style>
:host {
display: block;
position: relative;
font-family: sans-serif;
}
.canvas-wrapper {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
display: block;
width: 100%;
height: 100%;
background: var(--bg-color, #ffffff);
}
/* grid overlay */
.grid-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
}
</style>
<div class="canvas-wrapper">
<canvas id="draw-canvas"></canvas>
<svg class="grid-overlay" id="grid-overlay"></svg>
</div>
`;
this.shadowRoot.appendChild(tmpl.content.cloneNode(true));
// Elements inside the shadow DOM
this._canvas = this.shadowRoot.querySelector("#draw-canvas");
this._gridOverlay = this.shadowRoot.querySelector("#grid-overlay");
// Canvas drawing context
this._ctx = this._canvas.getContext("2d");
// Bind handlers
this._onBgColorChange = this._onBgColorChange.bind(this);
this._onGridSettingsChange = this._onGridSettingsChange.bind(this);
this._onGridToggle = this._onGridToggle.bind(this);
}
connectedCallback() {
// Initialise size a default that can be overridden by CSS
this._canvas.width = this.clientWidth || 300;
this._canvas.height = this.clientHeight || 300;
// Apply default attribute values if none are set
if (!this.hasAttribute("bg-color"))
this.setAttribute("bg-color", "#ffffff");
if (!this.hasAttribute("grid-color"))
this.setAttribute("grid-color", "#000000");
if (!this.hasAttribute("grid-size")) this.setAttribute("grid-size", "25");
if (!this.hasAttribute("grid-numbering"))
this.setAttribute("grid-numbering", "true");
// Initial draw
this._updateCanvasBackground();
this._drawGrid();
// Listen for events bubbled up from slotted child components
this.addEventListener("bgcolorchange", this._onBgColorChange);
this.addEventListener("gridsettingschange", this._onGridSettingsChange);
this.addEventListener("gridtoggle", this._onGridToggle);
}
disconnectedCallback() {
this.removeEventListener("bgcolorchange", this._onBgColorChange);
this.removeEventListener("gridsettingschange", this._onGridSettingsChange);
this.removeEventListener("gridtoggle", this._onGridToggle);
}
attributeChangedCallback(name, oldVal, newVal) {
if (oldVal === newVal) return;
switch (name) {
case "bg-color":
this._updateCanvasBackground();
break;
case "grid-color":
case "grid-size":
case "grid-numbering":
this._drawGrid();
break;
}
}
// ----- Property getters/setters -----
get bgColor() {
return this.getAttribute("bg-color");
}
set bgColor(v) {
this.setAttribute("bg-color", v);
}
get gridColor() {
return this.getAttribute("grid-color");
}
set gridColor(v) {
this.setAttribute("grid-color", v);
}
get gridSize() {
return Number(this.getAttribute("grid-size"));
}
set gridSize(v) {
this.setAttribute("grid-size", v);
}
get gridNumbering() {
return this.getAttribute("grid-numbering") === "true";
}
set gridNumbering(v) {
this.setAttribute("grid-numbering", v);
}
// ----- Event handlers -----
_onBgColorChange(e) {
const { color } = e.detail;
this.bgColor = color;
}
_onGridSettingsChange(e) {
const { color, size, numbering } = e.detail;
this.gridColor = color;
this.gridSize = size;
this.gridNumbering = numbering;
}
_onGridToggle(e) {
const { enabled } = e.detail;
this.gridNumbering = enabled;
// Reflect the change to the attribute so CSS/DOM stays in sync
this.setAttribute("grid-numbering", enabled);
}
// ----- Rendering helpers -----
_updateCanvasBackground() {
const { _ctx, _canvas, bgColor } = this;
_ctx.save();
_ctx.fillStyle = bgColor;
_ctx.fillRect(0, 0, _canvas.width, _canvas.height);
_ctx.restore();
}
_drawGrid() {
const { _gridOverlay, _canvas, gridColor, gridSize, gridNumbering } = this;
// Clear any previous grid
while (_gridOverlay.firstChild)
_gridOverlay.removeChild(_gridOverlay.firstChild);
const width = _canvas.width;
const height = _canvas.height;
const spacing = gridSize;
// Set SVG size and viewBox to match canvas dimensions
_gridOverlay.setAttribute("width", width);
_gridOverlay.setAttribute("height", height);
_gridOverlay.setAttribute("viewBox", `0 0 ${width} ${height}`);
// Helper to create a line
const makeLine = (x1, y1, x2, y2, thick) => {
const line = document.createElementNS(
"http://www.w3.org/2000/svg",
"line",
);
line.setAttribute("x1", x1);
line.setAttribute("y1", y1);
line.setAttribute("x2", x2);
line.setAttribute("y2", y2);
line.setAttribute("stroke", gridColor);
line.setAttribute("stroke-width", thick ? 2 : 1);
return line;
};
// Draw vertical lines
for (let x = 0; x <= width; x += spacing) {
const thick = (x / spacing) % 5 === 0;
_gridOverlay.appendChild(makeLine(x, 0, x, height, thick));
}
// Draw horizontal lines
for (let y = 0; y <= height; y += spacing) {
const thick = (y / spacing) % 5 === 0;
_gridOverlay.appendChild(makeLine(0, y, width, y, thick));
}
// Optional numbering
if (gridNumbering) {
const makeText = (x, y, txt) => {
const t = document.createElementNS(
"http://www.w3.org/2000/svg",
"text",
);
t.setAttribute("x", x + 2);
t.setAttribute("y", y + 10);
t.setAttribute("fill", gridColor);
t.setAttribute("font-size", "8");
t.textContent = txt;
return t;
};
// Number columns
for (let x = spacing; x <= width; x += spacing) {
const col = x / spacing;
if (col % 5 === 0) _gridOverlay.appendChild(makeText(x, 12, col));
}
// Number rows
for (let y = spacing; y <= height; y += spacing) {
const row = y / spacing;
if (row % 5 === 0) _gridOverlay.appendChild(makeText(2, y, row));
}
}
}
}
// Register the custom element
customElements.define("canvas-display", CanvasDisplay);