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
71
assets/workshop/workshop-target.js
Normal file
71
assets/workshop/workshop-target.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/* <workshop-target> — where the fly is heading right now.
|
||||
*
|
||||
* The flight reports a `target` event on every step (throttled server-side to a
|
||||
* sane rate), naming the word it currently pulls toward. That word changes
|
||||
* fast — several per second — and this element shows it as a running line, with
|
||||
* a short trail of the words it passed on the way. This is the fly *flying*,
|
||||
* not the fly eating: the meal stream (workshop-words) stays separate and still
|
||||
* arrives ~once every ten seconds.
|
||||
*
|
||||
* A `target` with `none: true` means every word was on cooldown — there is no
|
||||
* destination at that moment, and the element says so instead of freezing on a
|
||||
* stale word.
|
||||
*
|
||||
* Honesty: the fly does not "want" or "aim" — "target" is our layer over the
|
||||
* brain's state. Which word wins is measured (`sim`); the hungriness shown is
|
||||
* the author's frame, labelled on the page.
|
||||
*/
|
||||
class WorkshopTarget extends HTMLElement {
|
||||
connectedCallback() {
|
||||
this.trailLen = parseInt(this.getAttribute("trail") || "8", 10);
|
||||
this.trail = [];
|
||||
this.current = null;
|
||||
this.render();
|
||||
this._off = WorkshopStream.on("target", (ev) => this.push(ev));
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
if (this._off) this._off();
|
||||
}
|
||||
|
||||
push(ev) {
|
||||
this.current = ev;
|
||||
if (!ev.none && ev.term && ev.term !== (this.trail[0] && this.trail[0].term)) {
|
||||
this.trail.unshift(ev);
|
||||
if (this.trail.length > this.trailLen) this.trail.pop();
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const cur = this.current;
|
||||
let head;
|
||||
if (!cur) {
|
||||
head = '<span class="wt-idle">полёт ещё не пошёл — муха ждёт настроения от мозга</span>';
|
||||
} else if (cur.none) {
|
||||
head = '<span class="wt-none" title="все слова на кулдауне — цели нет">нет цели · все слова отрастают</span>';
|
||||
} else {
|
||||
const sim = typeof cur.sim === "number" ? cur.sim.toFixed(3) : "—";
|
||||
const hunger = typeof cur.hunger === "number" ? cur.hunger.toFixed(2) : "—";
|
||||
const entropy = typeof cur.entropy === "number" ? cur.entropy.toFixed(2) : "—";
|
||||
const tip = `lang=${cur.lang} sim=${sim} step=${cur.step} ` +
|
||||
`hunger=${hunger} entropy=${entropy}`;
|
||||
head = `<span class="wt-cur" title="${tip}">` +
|
||||
`${escapeHtml(cur.term)}<i>${sim}</i></span>`;
|
||||
}
|
||||
const trail = this.trail.slice(1).map((t) =>
|
||||
`<span class="wt-past" title="sim=${t.sim}">${escapeHtml(t.term)}</span>`
|
||||
).join("");
|
||||
this.innerHTML = `<div class="wt-line"><span class="wt-label">цель сейчас:</span>` +
|
||||
`${head}</div>` +
|
||||
(trail ? `<div class="wt-trail">${trail}</div>` : "");
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(s) {
|
||||
return String(s).replace(/[&<>"']/g, (c) => ({
|
||||
"&": "&", "<": "<", ">": ">", '"': """, "'": "'",
|
||||
})[c]);
|
||||
}
|
||||
|
||||
customElements.define("workshop-target", WorkshopTarget);
|
||||
Loading…
Add table
Add a link
Reference in a new issue