From 75df4e08336c92e10e33baea434ea150adc6ddfa Mon Sep 17 00:00:00 2001 From: marc Date: Mon, 31 Aug 2026 20:12:21 +0200 Subject: [PATCH] Bild einstellen gehoert in die Einstellungen, nicht ins Widget --- IDEAS.md | 6 ++ README.md | 5 +- web/css/style.css | 21 ++++- web/js/os.js | 9 ++- web/js/widgets/bild.js | 175 ++++++++++++++++++++++++++--------------- 5 files changed, 151 insertions(+), 65 deletions(-) diff --git a/IDEAS.md b/IDEAS.md index 040b198..5089210 100644 --- a/IDEAS.md +++ b/IDEAS.md @@ -326,6 +326,12 @@ Zu klären, wenn es soweit ist: - Passt zum Aufnehmen eigener App-Logos (1.3): dieselbe Ablage, dasselbe Ausliefern. Zusammen bauen spart die Hälfte. +**Nachtrag 31.08.2026.** Hochladen, Verlinken und Entfernen standen erst im +Widget selbst und sind in die Einstellungen gewandert (Marcs Einwand): Das +Widget zeigt, die Einstellungen stellen ein — wie beim Wetter. Damit gilt für +alle Widgets dieselbe Regel, und der Schreibtisch bleibt eine Anzeige statt +einer Bedienoberfläche. + ### Notiz-Widget — ✅ gebaut (30.08.2026), Sofort-Ansicht (31.08.2026) Ein Zettel auf dem Schreibtisch: hineintippen, fertig. Serverseitig je Nutzer, diff --git a/README.md b/README.md index 63696c7..f78bfce 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,10 @@ gewinnt. ### Das Bild Genau eines je Nutzer, hochgeladen oder verlinkt; ein neues ersetzt das alte -samt Datei. Erlaubt sind PNG, JPEG, WebP und GIF — der Typ wird aus den +samt Datei. **Eingestellt wird es unter *Einstellungen → Bild*, nicht im Widget** +— wie beim Wetter, dessen Ort auch nicht in der Wetterkachel gesucht wird. Ein +Schild auf dem Schreibtisch, das zu drei Vierteln aus Knöpfen besteht, zeigt vor +allem sich selbst. Erlaubt sind PNG, JPEG, WebP und GIF — der Typ wird aus den **Bytes** bestimmt, nicht aus dem, was der Browser behauptet. SVG ist bewusst nicht dabei: Es kann Skripte enthalten. Ausgeliefert wird mit einem Versionsanteil in der Adresse, sonst bliebe beim Austausch das alte Bild im diff --git a/web/css/style.css b/web/css/style.css index bfdecb6..d9e5dca 100644 --- a/web/css/style.css +++ b/web/css/style.css @@ -1307,6 +1307,26 @@ body { .bildw-bild { width: 100%; height: 100%; object-fit: cover; display: block; } .bildw .w-zustand { padding: 0 12px; text-align: center; } +/* Die Knöpfe stehen in den Einstellungen, nicht im Widget: links eine kleine + Vorschau, rechts was man damit tun kann. */ +.bildset { display: flex; gap: 10px; align-items: flex-start; } +.bildset-vorschau { + flex: none; + width: 84px; + height: 56px; + border: 1px solid var(--p-faint); + background: var(--bg); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; +} +.bildset-bild { width: 100%; height: 100%; object-fit: cover; display: block; } +.bildset-leer { color: var(--p-dim); font-size: 11px; } +.bildset-rechts { display: flex; flex-direction: column; gap: 6px; min-width: 0; } +.bildset-knoepfe { display: flex; gap: 4px; flex-wrap: wrap; } +.bildset-knoepfe .btn { font-size: 12px; padding: 3px 9px; } + .bildw-linkzeile { display: flex; gap: 6px; margin-top: 8px; } .bildw-linkzeile input { flex: 1; @@ -1319,7 +1339,6 @@ body { padding: 4px 6px; } .bildw-linkzeile input:focus { outline: none; border-color: var(--p); } -.bildw-knoepfe { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; } /* Einspaltig (Handy, schmales Fenster) sollen die Widgets so breit sein wie das Systemfenster. Drei verschieden breite Kästen untereinander sehen aus wie ein diff --git a/web/js/os.js b/web/js/os.js index c68e1ef..67a392c 100644 --- a/web/js/os.js +++ b/web/js/os.js @@ -11,7 +11,7 @@ import "./widgets/user.js"; import { statsHtml } from "./widgets/system.js"; import { loadWeather, renderWeatherBar } from "./widgets/weather.js"; import "./widgets/notiz.js"; -import "./widgets/bild.js"; +import { bildFormularHtml, bildFormularMontieren } from "./widgets/bild.js"; // Die Reihenfolge steht hier und nicht in den Importzeilen darüber: Module // werden nicht in deren Reihenfolge ausgewertet, sobald eines auf oberster @@ -590,6 +590,12 @@ function openSettings() { `

Reihenfolge und An/Aus liegen bei deinem Konto, nicht am Gerät.

` ) + + sektion( + "Bild", + bildFormularHtml() + + `

Genau ein Bild je Konto; ein neues ersetzt das alte. + Zu sehen ist es im Widget „bild".

` + ) + sektion( "Wetter", `
@@ -638,6 +644,7 @@ function openSettings() { }); widgetListeZeichnen(root.querySelector("#wlListe")); + bildFormularMontieren(root); const zeigeOrt = () => { root.querySelector("#wxCurrent").textContent = state.settings.weatherCity diff --git a/web/js/widgets/bild.js b/web/js/widgets/bild.js index 5078694..6f20dfc 100644 --- a/web/js/widgets/bild.js +++ b/web/js/widgets/bild.js @@ -10,92 +10,142 @@ import { widgetAnmelden, widgetsLaden, widgetsZeichnen } from "./rahmen.js"; * Hochgeladen wird zum eigenen Server; verlinkt wird auf eine fremde Adresse. * Der Unterschied ist keiner der Bequemlichkeit: Ein Link holt bei jedem * Aufbau des Schreibtischs etwas von woanders, ein Upload nie. + * + * DAS WIDGET ZEIGT, DIE EINSTELLUNGEN STELLEN EIN. + * + * Hochladen, Verlinken und Entfernen standen erst im Widget selbst. Das ist die + * Ausnahme gewesen, nicht die Regel: Der Ort des Wetters wird auch nicht in der + * Wetterkachel gesucht. Ein Schild auf dem Schreibtisch, das zu drei Vierteln + * aus Knöpfen besteht, zeigt vor allem sich selbst. + * + * Also: Das Widget zeigt nur noch das Bild. Die Knöpfe stehen unter + * *Einstellungen → Bild* — dieselbe Datei, aber ein zweiter Ausgang + * (`bildFormularHtml` / `bildFormularMontieren`), den os.js einhängt. */ -const LEER = "Kein Bild — hochladen oder Adresse einfügen."; +const LEER = "Kein Bild — unter Einstellungen → Bild hochladen oder verlinken."; +const FORM_ID = "bildForm"; let art = ""; // "datei" | "link" | "" let quelle = ""; -let stand = ""; // Meldung unter dem Bild +let stand = ""; // Meldung im Einstellungsfeld let linkAuf = false; +let kaputt = false; // die Adresse liefert kein Bild widgetAnmelden({ id: "bild", titel: "bild", - // Hält ein Dateifeld und ein Eingabefeld: Der Rahmen darf den Inhalt nicht - // unter den Fingern austauschen. + // Das Bild soll beim Neuzeichnen nicht neu geladen werden — ein Bild, das + // alle zwei Minuten kurz weiß aufblitzt, wäre schlechter als keines. stabil: true, async laden() { const d = await api("/api/image/info"); art = (d && d.art) || ""; quelle = (d && d.url) || ""; + kaputt = false; }, inhalt() { - const bild = quelle - ? `` - : `

${LEER}

`; - - return ` -
${bild}
- ${ - linkAuf - ? `
- - -
` - : "" - } -
- ${escapeHtml(stand)} - - - - - ${quelle ? `` : ""} - -
`; + if (kaputt) { + return `

${ + art === "link" ? "Adresse liefert kein Bild" : "Bild nicht lesbar" + }

`; + } + return `
${ + quelle + ? `` + : `

${LEER}

` + }
`; }, montiert(el) { - const datei = el.querySelector(".bildw-datei"); - el.querySelector(".bildw-waehlen").addEventListener("click", () => datei.click()); - datei.addEventListener("change", () => datei.files[0] && hochladen(datei.files[0])); - - el.querySelector(".bildw-link").addEventListener("click", () => { - linkAuf = !linkAuf; - neu(); - }); - - const url = el.querySelector(".bildw-url"); - if (url) { - url.focus(); - const setzen = () => linkSetzen(url.value.trim()); - el.querySelector(".bildw-uebernehmen").addEventListener("click", setzen); - url.addEventListener("keydown", (e) => { - if (e.key === "Enter") { - e.preventDefault(); - setzen(); - } - }); - } - - const weg = el.querySelector(".bildw-weg"); - if (weg) weg.addEventListener("click", () => linkSetzen("")); - // Ein Bild, das nicht kommt, soll es sagen — bei einem Link ist das der // Normalfall des Tages, an dem die fremde Seite umzieht. - const img = el.querySelector(".bildw-bild"); - if (img) { - img.addEventListener("error", () => { - melden(art === "link" ? "Adresse liefert kein Bild" : "Bild nicht lesbar"); - }); - } + el.querySelector(".bildw-bild")?.addEventListener("error", () => { + kaputt = true; + melden(art === "link" ? "Adresse liefert kein Bild" : "Bild nicht lesbar"); + neu(); + }); }, }); +/* ── Der Ausgang in die Einstellungen ──────────────────────────────────── */ + +/** Das Feld für *Einstellungen → Bild*. os.js hängt es ein, kennt es aber nicht. */ +export function bildFormularHtml() { + return `
${formInhalt()}
`; +} + +/** Nach dem Einhängen verdrahten. */ +export function bildFormularMontieren(root) { + const el = root.querySelector(`#${FORM_ID}`); + if (el) verdrahte(el); +} + +function formInhalt() { + const vorschau = quelle + ? `` + : `kein bild`; + + return ` +
+ ${vorschau} +
+
+ + + + ${quelle ? `` : ""} +
+ ${escapeHtml(stand)} +
+
+ ${ + linkAuf + ? `
+ + +
` + : "" + }`; +} + +function verdrahte(el) { + const datei = el.querySelector(".bildw-datei"); + el.querySelector(".bildw-waehlen").addEventListener("click", () => datei.click()); + datei.addEventListener("change", () => datei.files[0] && hochladen(datei.files[0])); + + el.querySelector(".bildw-link").addEventListener("click", () => { + linkAuf = !linkAuf; + formNeu(); + }); + + const url = el.querySelector(".bildw-url"); + if (url) { + url.focus(); + const setzen = () => linkSetzen(url.value.trim()); + el.querySelector(".bildw-uebernehmen").addEventListener("click", setzen); + url.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + e.preventDefault(); + setzen(); + } + }); + } + + el.querySelector(".bildw-weg")?.addEventListener("click", () => linkSetzen("")); +} + +/** Das Einstellungsfeld neu zeichnen — es steht nicht unter dem Widget-Rahmen. */ +function formNeu() { + const el = document.getElementById(FORM_ID); + if (!el) return; + el.innerHTML = formInhalt(); + verdrahte(el); +} + async function hochladen(file) { melden("lädt hoch …"); try { @@ -106,6 +156,7 @@ async function hochladen(file) { } catch (e) { melden(e.message); } + formNeu(); } async function linkSetzen(adresse) { @@ -118,13 +169,13 @@ async function linkSetzen(adresse) { } catch (e) { melden(e.message); } + formNeu(); } function melden(s) { stand = s; - document.querySelectorAll('[data-widget="bild"] .w-stand').forEach((el) => { - el.textContent = s; - }); + const el = document.querySelector(`#${FORM_ID} .w-stand`); + if (el) el.textContent = s; } /** Neu zeichnen — stabile Widgets lässt der Rahmen sonst in Ruhe. */