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>
170 lines
4.3 KiB
JavaScript
170 lines
4.3 KiB
JavaScript
import {
|
|
SCHEMATIC_FLOOR_LINE,
|
|
SCHEMATIC_HEX_PATH,
|
|
SCHEMATIC_PANEL_LAYOUT,
|
|
} from "../portal-layout.js";
|
|
import "./portal-panel.js";
|
|
|
|
const SCHEMATIC_STYLES = `
|
|
:host {
|
|
display: none;
|
|
width: 100%;
|
|
height: 100%;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 1rem;
|
|
box-sizing: border-box;
|
|
background: radial-gradient(ellipse at 50% 45%, #141820 0%, #0a0c12 70%);
|
|
}
|
|
:host([visible]) {
|
|
display: flex;
|
|
}
|
|
.hex-stage {
|
|
position: relative;
|
|
width: min(88vw, 420px);
|
|
aspect-ratio: 100 / 118;
|
|
}
|
|
.hex-svg {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
}
|
|
.hex-fill {
|
|
fill: rgba(0, 0, 0, 0.35);
|
|
}
|
|
.hex-stroke {
|
|
fill: none;
|
|
stroke: #3a4458;
|
|
stroke-width: 1.2;
|
|
}
|
|
.hex-floor {
|
|
fill: none;
|
|
stroke: #2a3044;
|
|
stroke-width: 0.8;
|
|
stroke-dasharray: 3 3;
|
|
}
|
|
.floor-label {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 91%;
|
|
transform: translate(-50%, -50%);
|
|
font-size: 0.65rem;
|
|
letter-spacing: 0.14em;
|
|
text-transform: uppercase;
|
|
color: #5a6278;
|
|
font-family: system-ui, sans-serif;
|
|
pointer-events: none;
|
|
}
|
|
.ground {
|
|
position: absolute;
|
|
left: 5%;
|
|
right: 5%;
|
|
bottom: -2%;
|
|
height: 2px;
|
|
background: #3a4050;
|
|
}
|
|
.slot {
|
|
position: absolute;
|
|
transform: translate(-50%, -50%);
|
|
z-index: 1;
|
|
}
|
|
.slot portal-panel {
|
|
display: block;
|
|
transform-origin: center center;
|
|
box-shadow: 0 0 12px rgba(255, 60, 40, 0.15);
|
|
}
|
|
`;
|
|
|
|
export class PortalSchematic extends HTMLElement {
|
|
constructor() {
|
|
super();
|
|
/** @type {Map<number, import('./portal-panel.js').PortalPanel>} */
|
|
this._panels = new Map();
|
|
}
|
|
|
|
connectedCallback() {
|
|
if (this.shadowRoot) return;
|
|
const root = this.attachShadow({ mode: "open" });
|
|
root.innerHTML = `
|
|
<style>${SCHEMATIC_STYLES}</style>
|
|
<div class="hex-stage">
|
|
<svg class="hex-svg" viewBox="0 0 100 108" preserveAspectRatio="xMidYMid meet">
|
|
<path class="hex-fill" d="${SCHEMATIC_HEX_PATH}" />
|
|
<path class="hex-stroke" d="${SCHEMATIC_HEX_PATH}" />
|
|
<line class="hex-floor"
|
|
x1="${SCHEMATIC_FLOOR_LINE.x1}" y1="${SCHEMATIC_FLOOR_LINE.y1}"
|
|
x2="${SCHEMATIC_FLOOR_LINE.x2}" y2="${SCHEMATIC_FLOOR_LINE.y2}" />
|
|
</svg>
|
|
<div class="floor-label">floor · walk through</div>
|
|
<div class="ground"></div>
|
|
<div class="slots"></div>
|
|
</div>
|
|
`;
|
|
this._slotsEl = root.querySelector(".slots");
|
|
}
|
|
|
|
_ensureSlot(position) {
|
|
let slot = this._slotsEl.querySelector(`[data-position="${position}"]`);
|
|
if (slot) return slot;
|
|
|
|
const layout = SCHEMATIC_PANEL_LAYOUT[position];
|
|
if (!layout) return null;
|
|
|
|
slot = document.createElement("div");
|
|
slot.className = "slot";
|
|
slot.dataset.position = position;
|
|
slot.style.left = `${layout.x}%`;
|
|
slot.style.top = `${layout.y}%`;
|
|
slot.style.maxWidth = layout.maxW;
|
|
this._slotsEl.appendChild(slot);
|
|
return slot;
|
|
}
|
|
|
|
/** @param {import('../api.js').PanelConfig[]} panels */
|
|
setPanels(panels) {
|
|
if (!this.shadowRoot) this.connectedCallback();
|
|
this._panels.clear();
|
|
this._slotsEl.querySelectorAll(".slot").forEach((s) => {
|
|
s.innerHTML = "";
|
|
});
|
|
|
|
for (const panel of panels) {
|
|
const slot = this._ensureSlot(panel.position);
|
|
if (!slot) continue;
|
|
|
|
const layout = SCHEMATIC_PANEL_LAYOUT[panel.position];
|
|
const el = document.createElement("portal-panel");
|
|
el.setAttribute("index", String(panel.index));
|
|
el.setAttribute("position", panel.position);
|
|
el.setAttribute("width", String(panel.width));
|
|
el.setAttribute("height", String(panel.height));
|
|
el.style.transform = `rotate(${layout.rot}deg)`;
|
|
slot.appendChild(el);
|
|
this._panels.set(panel.index, el);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} index
|
|
* @param {number[] | Uint8Array} rgb
|
|
* @param {number} width
|
|
* @param {number} height
|
|
* @param {boolean} identify
|
|
*/
|
|
updatePanel(index, rgb, width, height, identify) {
|
|
const panel = this._panels.get(index);
|
|
if (!panel) return;
|
|
if (identify) panel.showIdentify();
|
|
else panel.setFrame(rgb, width, height);
|
|
}
|
|
|
|
set visible(on) {
|
|
if (on) this.setAttribute("visible", "");
|
|
else this.removeAttribute("visible");
|
|
}
|
|
}
|
|
|
|
customElements.define("portal-schematic", PortalSchematic);
|