Files
HoneyOS/web/js/widgets/rahmen.js
T
marc b7cc06b7c1 Widget-Rahmen: Widgets sind Beschreibungen, keine Kaesten in der Seite
widgetAnmelden({ id, titel, wo, sichtbar, laden, leer, inhalt, takt }) —
der Rahmen macht daraus ein Fenster in der Spalte (Terminal) oder ein
Schild (Desktop), kennt lädt/fertig/nichts da/Fehler und haelt den
Nachladetakt. Benutzer, Wetter und Systemzustand sind umgestellt; aus
index.html sind vier feste Kaesten verschwunden und aus dem Raster die
Einzelplaetze.

Zwei Dinge, die erst dabei sichtbar wurden:

- Der Server meldete 'kein Ort eingestellt' und 'Dienst nicht
  erreichbar' beide als 204. Ein Ausfall sah damit aus wie ein
  abgeschaltetes Widget. Ausfall ist jetzt 502, mit Test.
- Die Reihenfolge haengt nicht mehr an der Modulauswertung: Seit
  glyphs.js seine Raster mit await auf oberster Ebene laedt, laufen
  Module ohne Symbole vorher durch — der Systemzustand stand deshalb
  ueber dem Benutzer. widgetsOrdnen() macht die Reihenfolge zur Angabe,
  und genau dort haengt spaeter die im Server gespeicherte ein.
2026-08-30 03:12:00 +02:00

152 lines
5.3 KiB
JavaScript

/* ── Der Widget-Rahmen ─────────────────────────────────────────────────────
*
* Ein Widget ist hier eine **Beschreibung**, kein Stück HTML im Dokument.
* Vorher standen Benutzer und Wetter fest in index.html, jedes mit eigener
* Zeigen-und-Verstecken-Logik in seiner Datei — beim dritten Widget hätte man
* dieselben zwanzig Zeilen ein drittes Mal geschrieben.
*
* Der Rahmen kümmert sich um das, was jedes Widget braucht und keines für sich
* lösen sollte:
*
* · beide Betriebsarten — Fenster im Terminal, Schild auf dem Schreibtisch
* · die vier Zustände: lädt · fertig · nichts da · Fehler
* · Reihenfolge und Nachladen im Takt
*
* Ein Widget beschreibt sich so:
*
* {
* id, titel,
* wo: ["terminal", "desktop"] // wo es erscheinen darf
* sichtbar: () => true // Schalter aus → gar nicht erst da
* laden: async () => false|void // false = es gibt nichts zu zeigen
* leer: "verbergen" | "Text" // was dann passiert
* inhalt: () => "<html>"
* takt: 600000 // ms, optional
* }
*
* Absichtlich offen gelassen: eine dritte Form. Die Widgets sollen später auch
* als Aufklappfeld in der HoneyBar leben können — deshalb erzeugt `inhalt()`
* nur den Kern, und die Verpackung entsteht hier.
*/
import { $, state } from "../core.js";
const REGISTER = [];
/** id → "laedt" | "fertig" | "leer" | "fehler" */
const zustand = {};
const uhren = {};
export function widgetAnmelden(def) {
REGISTER.push(def);
zustand[def.id] = def.laden ? "laedt" : "fertig";
}
/** Nur zum Nachsehen von außen — die Liste selbst gehört dem Rahmen. */
export function widgetListe() {
return REGISTER.map((w) => w.id);
}
/**
* Legt die Reihenfolge fest. Was nicht genannt ist, hängt sich hinten an.
*
* Warum überhaupt? Weil die Reihenfolge sonst davon abhinge, in welcher
* Reihenfolge die Module ausgewertet werden — und das ist **nicht** die
* Reihenfolge der Importzeilen: `glyphs.js` lädt seine Raster mit `await` auf
* oberster Ebene, also warten alle Module, die es brauchen, während die
* anderen schon durchlaufen. Der Systemzustand braucht keine Symbole und stand
* deshalb vor dem Benutzer, obwohl er darunter importiert wird.
*
* Hier ist die Reihenfolge damit eine Angabe statt einer Nebenwirkung — und
* genau die Stelle, an der später die im Server gespeicherte Reihenfolge
* einhaken kann.
*/
export function widgetsOrdnen(ids) {
const platz = (w) => {
const i = ids.indexOf(w.id);
return i < 0 ? ids.length : i;
};
REGISTER.sort((a, b) => platz(a) - platz(b));
}
/**
* Lädt ein Widget (oder alle) und zeichnet neu.
*
* Fehler bleiben hier: Ein Widget, das seinen Server nicht erreicht, sagt das
* in seinem eigenen Rahmen — es darf weder den Schreibtisch aufhalten noch die
* anderen Widgets mitreißen.
*/
export async function widgetsLaden(nurId) {
const dran = REGISTER.filter((w) => w.laden && (!nurId || w.id === nurId));
await Promise.all(
dran.map(async (w) => {
zustand[w.id] = "laedt";
widgetsZeichnen();
try {
zustand[w.id] = (await w.laden()) === false ? "leer" : "fertig";
} catch {
zustand[w.id] = "fehler";
}
})
);
widgetsZeichnen();
}
/** Startet die Nachlade-Takte. Einmal beim Aufbau des Schreibtischs. */
export function widgetsStarten() {
REGISTER.forEach((w) => {
if (!w.takt || uhren[w.id]) return;
uhren[w.id] = setInterval(() => widgetsLaden(w.id), w.takt);
});
}
function zustandHtml(w) {
switch (zustand[w.id]) {
case "laedt":
return `<p class="w-zustand">lädt …</p>`;
case "fehler":
return `<p class="w-zustand w-fehler">nicht erreichbar</p>`;
case "leer":
return `<p class="w-zustand">${w.leer}</p>`;
default:
return w.inhalt();
}
}
/**
* Zeichnet alle Widgets in die Ansicht, die gerade gilt.
*
* Beide Container werden bei jedem Durchgang neu gefüllt statt einzeln
* nachgeführt: Bei einer Handvoll Widgets ist das billiger als Buchführung
* darüber, was sich geändert hat — und es kann nicht auseinanderlaufen.
*/
export function widgetsZeichnen() {
const desktop = state.settings.mode === "desktop";
const modus = desktop ? "desktop" : "terminal";
const spalte = $("widgetSpalte");
const schilder = $("deskwidgets");
if (!spalte || !schilder) return;
const dran = REGISTER.filter((w) => {
if (w.wo && !w.wo.includes(modus)) return false;
if (w.sichtbar && !w.sichtbar()) return false;
// „nichts da" heißt bei manchen Widgets: gar nicht erst hinstellen. Eine
// leere Wetterkachel wäre schlechter als keine.
return !(zustand[w.id] === "leer" && w.leer === "verbergen");
});
spalte.innerHTML = desktop
? ""
: dran
.map(
(w) => `
<section class="win win-widget" data-widget="${w.id}">
<div class="win-bar"><span class="win-title">${w.titel}</span><span class="win-dots">&#9679;&#9679;&#9679;</span></div>
<div class="win-body">${zustandHtml(w)}</div>
</section>`
)
.join("");
schilder.innerHTML = desktop
? dran.map((w) => `<div class="deskwidget" data-widget="${w.id}">${zustandHtml(w)}</div>`).join("")
: "";
}