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>
57 lines
1.6 KiB
JavaScript
57 lines
1.6 KiB
JavaScript
/** @typedef {{ index: number, width: number, height: number, position: string, label?: string }} PanelConfig */
|
|
|
|
/** @typedef {{ frame: number, animation: string, panels: Array<{ index: number, width: number, height: number, rgb: number[] }> }} FramePayload */
|
|
|
|
/** @typedef {{ panels: PanelConfig[], animations: string[], defaultFps: Record<string, number>, defaultBrightness: number }} PortalConfig */
|
|
|
|
/**
|
|
* @param {string} path
|
|
* @returns {Promise<PortalConfig>}
|
|
*/
|
|
export async function fetchConfig(path = "/api/config") {
|
|
const res = await fetch(path);
|
|
if (!res.ok) throw new Error(`config ${res.status}`);
|
|
return res.json();
|
|
}
|
|
|
|
export class FrameStream {
|
|
/** @param {() => void} onFrame */
|
|
constructor(onFrame) {
|
|
this._onFrame = onFrame;
|
|
this._source = null;
|
|
this._retry = null;
|
|
/** @type {(() => void) | null} */
|
|
this.onDisconnect = null;
|
|
}
|
|
|
|
/** @param {{ animation: string, brightness: number, fps: number }} opts */
|
|
start(opts) {
|
|
this.stop();
|
|
const params = new URLSearchParams({
|
|
animation: opts.animation,
|
|
brightness: String(opts.brightness),
|
|
fps: String(opts.fps),
|
|
});
|
|
this._source = new EventSource(`/api/stream?${params}`);
|
|
this._source.onmessage = (event) => {
|
|
this._onFrame(JSON.parse(event.data));
|
|
};
|
|
this._source.onerror = () => {
|
|
this.onDisconnect?.();
|
|
this.stop();
|
|
this._retry = setTimeout(() => this.start(opts), 1500);
|
|
};
|
|
}
|
|
|
|
stop() {
|
|
if (this._retry) {
|
|
clearTimeout(this._retry);
|
|
this._retry = null;
|
|
}
|
|
if (this._source) {
|
|
this._source.close();
|
|
this._source = null;
|
|
}
|
|
}
|
|
}
|