workshop: страница в общей оболочке сайта + компоненты
Мастерская переехала на eureka2: страница /workshop — часть сайта (lib/workshop.js, общая шапка/навигация/подвал), оформление в tokens сайта (assets/workshop/ workshop.css), компоненты читают данные пода по SSE /workshop/sse. Компоненты перенесены на сторону сайта, геометрия мозга — assets/workshop/. build.js собирает страницу (active: workshop).
This commit is contained in:
parent
e2fe11f723
commit
cc3d6c5ca0
15 changed files with 1627 additions and 2 deletions
109
assets/workshop/workshop-seismogram.js
Normal file
109
assets/workshop/workshop-seismogram.js
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/* workshop-seismogram.js — сырое движение земли бегущей линией.
|
||||
*
|
||||
* Где фингерпринт отвечает «из чего дрожь» (частоты), эта линия — «как двигалась
|
||||
* земля» (время). То же окно Земли, две системы координат: фингерпринт считается
|
||||
* *из* этого сигнала, так что волна первична, а сетка — её разложение.
|
||||
*
|
||||
* Линия — окно с вычтенным средним (датчик несёт DC ~1000 отсчётов, это его
|
||||
* положение, не движение) и авто-масштабом по пику окна. Высота не абсолютна:
|
||||
* важно, как линия меняется, а не насколько она высока.
|
||||
*/
|
||||
class WorkshopSeismogram extends HTMLElement {
|
||||
static get observedAttributes() { return ["height", "line-color"]; }
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: "open" });
|
||||
this._wave = new Array(256).fill(0);
|
||||
this._unsub = null;
|
||||
}
|
||||
|
||||
connectedCallback() { this._render(); this._subscribe(); }
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._unsub) this._unsub();
|
||||
this._unsub = null;
|
||||
if (this._ro) this._ro.disconnect();
|
||||
this._ro = null;
|
||||
}
|
||||
|
||||
attributeChangedCallback() { if (this.isConnected) this._render(); }
|
||||
|
||||
get height() {
|
||||
const v = parseInt(this.getAttribute("height") || "130", 10);
|
||||
return Number.isFinite(v) && v > 0 ? v : 130;
|
||||
}
|
||||
|
||||
_subscribe() {
|
||||
if (!this.isConnected || this._unsub) return;
|
||||
this._unsub = window.WorkshopStream.on("noise", (ev) => this._update(ev));
|
||||
}
|
||||
|
||||
_render() {
|
||||
const h = this.height;
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host { display: block; }
|
||||
canvas {
|
||||
width: 100%; height: ${h}px; display: block;
|
||||
background: var(--workshop-high, #10151a);
|
||||
border: 1px solid var(--workshop-line, #232a33);
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
<canvas></canvas>`;
|
||||
this._canvas = this.shadowRoot.querySelector("canvas");
|
||||
this._ctx = this._canvas.getContext("2d");
|
||||
// При connectedCallback элемент ещё не разложен, rect.width = 0 и canvas был
|
||||
// бы 1px. Размер — на следующем кадре, синхронизация через ResizeObserver.
|
||||
requestAnimationFrame(() => this._resize());
|
||||
if (!this._ro && typeof ResizeObserver !== "undefined") {
|
||||
this._ro = new ResizeObserver(() => this._resize());
|
||||
this._ro.observe(this);
|
||||
}
|
||||
}
|
||||
|
||||
_resize() {
|
||||
if (!this._canvas) return;
|
||||
const w = Math.max(1, Math.round(this.clientWidth || 640));
|
||||
const h = this.height;
|
||||
if (this._canvas.width !== w || this._canvas.height !== h) {
|
||||
this._canvas.width = w;
|
||||
this._canvas.height = h;
|
||||
}
|
||||
this._draw();
|
||||
}
|
||||
|
||||
_update(ev) {
|
||||
if (!ev.wave || !ev.wave.length) return;
|
||||
this._wave = ev.wave;
|
||||
this._draw();
|
||||
}
|
||||
|
||||
_draw() {
|
||||
if (!this._ctx) return;
|
||||
const ctx = this._ctx;
|
||||
const W = this._canvas.width, H = this._canvas.height;
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
let peak = 0;
|
||||
for (const v of this._wave) peak = Math.max(peak, Math.abs(v));
|
||||
const scale = peak > 0 ? (H / 2 - 4) / peak : 1;
|
||||
ctx.strokeStyle = this.getAttribute("line-color") || "#4a9de0";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
const stepX = W / (this._wave.length - 1);
|
||||
for (let i = 0; i < this._wave.length; i++) {
|
||||
const x = i * stepX;
|
||||
const y = H / 2 - this._wave[i] * scale;
|
||||
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.strokeStyle = "#232a33";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, H / 2);
|
||||
ctx.lineTo(W, H / 2);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("workshop-seismogram", WorkshopSeismogram);
|
||||
Loading…
Add table
Add a link
Reference in a new issue