Add portal web simulator, SPI bridges, and Pico firmware updates.
Bring the five-panel hex portal online with a browser 3D/schematic preview, Pi SPI backends, and renamed multi-panel Pico UDP firmware. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
90
web/js/components/portal-panel.js
Normal file
90
web/js/components/portal-panel.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import { paintIdentify, paintRgb } from "../panel-paint.js";
|
||||
|
||||
const PANEL_STYLES = `
|
||||
:host {
|
||||
display: block;
|
||||
background: #000;
|
||||
border: 1px solid var(--border, #2a3044);
|
||||
border-radius: 4px;
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.label {
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--muted, #8b93a8);
|
||||
margin: 0.35rem 0;
|
||||
font-family: system-ui, sans-serif;
|
||||
}
|
||||
`;
|
||||
|
||||
export class PortalPanel extends HTMLElement {
|
||||
static observedAttributes = ["index", "position", "width", "height"];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._index = 0;
|
||||
this._pixelWidth = 45;
|
||||
this._pixelHeight = 9;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
if (this.shadowRoot) return;
|
||||
const root = this.attachShadow({ mode: "open" });
|
||||
root.innerHTML = `
|
||||
<style>${PANEL_STYLES}</style>
|
||||
<canvas part="canvas"></canvas>
|
||||
<div class="label" part="label"></div>
|
||||
`;
|
||||
this._canvas = /** @type {HTMLCanvasElement} */ (root.querySelector("canvas"));
|
||||
this._ctx = /** @type {CanvasRenderingContext2D} */ (this._canvas.getContext("2d"));
|
||||
this._label = root.querySelector(".label");
|
||||
this._syncFromAttributes();
|
||||
}
|
||||
|
||||
attributeChangedCallback(name) {
|
||||
if (!this.shadowRoot) return;
|
||||
this._syncFromAttributes();
|
||||
}
|
||||
|
||||
_syncFromAttributes() {
|
||||
this._index = Number(this.getAttribute("index") ?? 0);
|
||||
this._pixelWidth = Number(this.getAttribute("width") ?? 45);
|
||||
this._pixelHeight = Number(this.getAttribute("height") ?? 9);
|
||||
const position = this.getAttribute("position") ?? "";
|
||||
if (this._label) {
|
||||
this._label.textContent = `${this._index} · ${position}`;
|
||||
}
|
||||
if (this._canvas) {
|
||||
this._canvas.width = this._pixelWidth;
|
||||
this._canvas.height = this._pixelHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {HTMLCanvasElement} */
|
||||
get canvas() {
|
||||
if (!this._canvas) this.connectedCallback();
|
||||
return this._canvas;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[] | Uint8Array} rgb
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
*/
|
||||
setFrame(rgb, width, height) {
|
||||
if (!this._ctx) this.connectedCallback();
|
||||
paintRgb(this._ctx, this._canvas, rgb, width, height);
|
||||
}
|
||||
|
||||
showIdentify() {
|
||||
if (!this._ctx) this.connectedCallback();
|
||||
paintIdentify(this._ctx, this._canvas, this._index, this._pixelWidth, this._pixelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("portal-panel", PortalPanel);
|
||||
Reference in New Issue
Block a user