Bild einstellen gehoert in die Einstellungen, nicht ins Widget
This commit is contained in:
1 parent
e2988f7ee9
commit
75df4e0833
5 files changed
+151
-65
No files matched your search
@@ -326,6 +326,12 @@ Zu klären, wenn es soweit ist:
|
|||||||
- Passt zum Aufnehmen eigener App-Logos (1.3): dieselbe Ablage, dasselbe
|
- Passt zum Aufnehmen eigener App-Logos (1.3): dieselbe Ablage, dasselbe
|
||||||
Ausliefern. Zusammen bauen spart die Hälfte.
|
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)
|
### Notiz-Widget — ✅ gebaut (30.08.2026), Sofort-Ansicht (31.08.2026)
|
||||||
|
|
||||||
Ein Zettel auf dem Schreibtisch: hineintippen, fertig. Serverseitig je Nutzer,
|
Ein Zettel auf dem Schreibtisch: hineintippen, fertig. Serverseitig je Nutzer,
|
||||||
|
|||||||
@@ -279,7 +279,10 @@ gewinnt.
|
|||||||
### Das Bild
|
### Das Bild
|
||||||
|
|
||||||
Genau eines je Nutzer, hochgeladen oder verlinkt; ein neues ersetzt das alte
|
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
|
**Bytes** bestimmt, nicht aus dem, was der Browser behauptet. SVG ist bewusst
|
||||||
nicht dabei: Es kann Skripte enthalten. Ausgeliefert wird mit einem
|
nicht dabei: Es kann Skripte enthalten. Ausgeliefert wird mit einem
|
||||||
Versionsanteil in der Adresse, sonst bliebe beim Austausch das alte Bild im
|
Versionsanteil in der Adresse, sonst bliebe beim Austausch das alte Bild im
|
||||||
|
|||||||
+20
-1
@@ -1307,6 +1307,26 @@ body {
|
|||||||
.bildw-bild { width: 100%; height: 100%; object-fit: cover; display: block; }
|
.bildw-bild { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||||
.bildw .w-zustand { padding: 0 12px; text-align: center; }
|
.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 { display: flex; gap: 6px; margin-top: 8px; }
|
||||||
.bildw-linkzeile input {
|
.bildw-linkzeile input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -1319,7 +1339,6 @@ body {
|
|||||||
padding: 4px 6px;
|
padding: 4px 6px;
|
||||||
}
|
}
|
||||||
.bildw-linkzeile input:focus { outline: none; border-color: var(--p); }
|
.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
|
/* Einspaltig (Handy, schmales Fenster) sollen die Widgets so breit sein wie das
|
||||||
Systemfenster. Drei verschieden breite Kästen untereinander sehen aus wie ein
|
Systemfenster. Drei verschieden breite Kästen untereinander sehen aus wie ein
|
||||||
|
|||||||
+8
-1
@@ -11,7 +11,7 @@ import "./widgets/user.js";
|
|||||||
import { statsHtml } from "./widgets/system.js";
|
import { statsHtml } from "./widgets/system.js";
|
||||||
import { loadWeather, renderWeatherBar } from "./widgets/weather.js";
|
import { loadWeather, renderWeatherBar } from "./widgets/weather.js";
|
||||||
import "./widgets/notiz.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
|
// Die Reihenfolge steht hier und nicht in den Importzeilen darüber: Module
|
||||||
// werden nicht in deren Reihenfolge ausgewertet, sobald eines auf oberster
|
// werden nicht in deren Reihenfolge ausgewertet, sobald eines auf oberster
|
||||||
@@ -590,6 +590,12 @@ function openSettings() {
|
|||||||
`<ul class="wl" id="wlListe"></ul>
|
`<ul class="wl" id="wlListe"></ul>
|
||||||
<p class="sek-hinweis">Reihenfolge und An/Aus liegen bei deinem Konto, nicht am Gerät.</p>`
|
<p class="sek-hinweis">Reihenfolge und An/Aus liegen bei deinem Konto, nicht am Gerät.</p>`
|
||||||
) +
|
) +
|
||||||
|
sektion(
|
||||||
|
"Bild",
|
||||||
|
bildFormularHtml() +
|
||||||
|
`<p class="sek-hinweis">Genau ein Bild je Konto; ein neues ersetzt das alte.
|
||||||
|
Zu sehen ist es im Widget „bild".</p>`
|
||||||
|
) +
|
||||||
sektion(
|
sektion(
|
||||||
"Wetter",
|
"Wetter",
|
||||||
`<div class="wx-city-row">
|
`<div class="wx-city-row">
|
||||||
@@ -638,6 +644,7 @@ function openSettings() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
widgetListeZeichnen(root.querySelector("#wlListe"));
|
widgetListeZeichnen(root.querySelector("#wlListe"));
|
||||||
|
bildFormularMontieren(root);
|
||||||
|
|
||||||
const zeigeOrt = () => {
|
const zeigeOrt = () => {
|
||||||
root.querySelector("#wxCurrent").textContent = state.settings.weatherCity
|
root.querySelector("#wxCurrent").textContent = state.settings.weatherCity
|
||||||
|
|||||||
+113
-62
@@ -10,92 +10,142 @@ import { widgetAnmelden, widgetsLaden, widgetsZeichnen } from "./rahmen.js";
|
|||||||
* Hochgeladen wird zum eigenen Server; verlinkt wird auf eine fremde Adresse.
|
* Hochgeladen wird zum eigenen Server; verlinkt wird auf eine fremde Adresse.
|
||||||
* Der Unterschied ist keiner der Bequemlichkeit: Ein Link holt bei jedem
|
* Der Unterschied ist keiner der Bequemlichkeit: Ein Link holt bei jedem
|
||||||
* Aufbau des Schreibtischs etwas von woanders, ein Upload nie.
|
* 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 art = ""; // "datei" | "link" | ""
|
||||||
let quelle = "";
|
let quelle = "";
|
||||||
let stand = ""; // Meldung unter dem Bild
|
let stand = ""; // Meldung im Einstellungsfeld
|
||||||
let linkAuf = false;
|
let linkAuf = false;
|
||||||
|
let kaputt = false; // die Adresse liefert kein Bild
|
||||||
|
|
||||||
widgetAnmelden({
|
widgetAnmelden({
|
||||||
id: "bild",
|
id: "bild",
|
||||||
titel: "bild",
|
titel: "bild",
|
||||||
// Hält ein Dateifeld und ein Eingabefeld: Der Rahmen darf den Inhalt nicht
|
// Das Bild soll beim Neuzeichnen nicht neu geladen werden — ein Bild, das
|
||||||
// unter den Fingern austauschen.
|
// alle zwei Minuten kurz weiß aufblitzt, wäre schlechter als keines.
|
||||||
stabil: true,
|
stabil: true,
|
||||||
|
|
||||||
async laden() {
|
async laden() {
|
||||||
const d = await api("/api/image/info");
|
const d = await api("/api/image/info");
|
||||||
art = (d && d.art) || "";
|
art = (d && d.art) || "";
|
||||||
quelle = (d && d.url) || "";
|
quelle = (d && d.url) || "";
|
||||||
|
kaputt = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
inhalt() {
|
inhalt() {
|
||||||
const bild = quelle
|
if (kaputt) {
|
||||||
? `<img class="bildw-bild" src="${escapeAttr(quelle)}" alt="" />`
|
return `<div class="bildw"><p class="w-zustand">${
|
||||||
: `<p class="w-zustand">${LEER}</p>`;
|
art === "link" ? "Adresse liefert kein Bild" : "Bild nicht lesbar"
|
||||||
|
}</p></div>`;
|
||||||
return `
|
}
|
||||||
<div class="bildw">${bild}</div>
|
return `<div class="bildw">${
|
||||||
${
|
quelle
|
||||||
linkAuf
|
? `<img class="bildw-bild" src="${escapeAttr(quelle)}" alt="" />`
|
||||||
? `<div class="bildw-linkzeile">
|
: `<p class="w-zustand">${LEER}</p>`
|
||||||
<input class="bildw-url" type="url" spellcheck="false"
|
}</div>`;
|
||||||
placeholder="https://…" value="${escapeAttr(art === "link" ? quelle : "")}" />
|
|
||||||
<button class="btn bildw-uebernehmen">setzen</button>
|
|
||||||
</div>`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
<div class="w-fuss">
|
|
||||||
<span class="w-stand">${escapeHtml(stand)}</span>
|
|
||||||
<span class="bildw-knoepfe">
|
|
||||||
<input class="bildw-datei" type="file" accept="image/png,image/jpeg,image/webp,image/gif" hidden />
|
|
||||||
<button class="btn bildw-waehlen">${quelle ? "ersetzen" : "hochladen"}</button>
|
|
||||||
<button class="btn bildw-link">link</button>
|
|
||||||
${quelle ? `<button class="btn bildw-weg">entfernen</button>` : ""}
|
|
||||||
</span>
|
|
||||||
</div>`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
montiert(el) {
|
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
|
// Ein Bild, das nicht kommt, soll es sagen — bei einem Link ist das der
|
||||||
// Normalfall des Tages, an dem die fremde Seite umzieht.
|
// Normalfall des Tages, an dem die fremde Seite umzieht.
|
||||||
const img = el.querySelector(".bildw-bild");
|
el.querySelector(".bildw-bild")?.addEventListener("error", () => {
|
||||||
if (img) {
|
kaputt = true;
|
||||||
img.addEventListener("error", () => {
|
melden(art === "link" ? "Adresse liefert kein Bild" : "Bild nicht lesbar");
|
||||||
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 `<div id="${FORM_ID}">${formInhalt()}</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nach dem Einhängen verdrahten. */
|
||||||
|
export function bildFormularMontieren(root) {
|
||||||
|
const el = root.querySelector(`#${FORM_ID}`);
|
||||||
|
if (el) verdrahte(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formInhalt() {
|
||||||
|
const vorschau = quelle
|
||||||
|
? `<img class="bildset-bild" src="${escapeAttr(quelle)}" alt="" />`
|
||||||
|
: `<span class="bildset-leer">kein bild</span>`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="bildset">
|
||||||
|
<span class="bildset-vorschau">${vorschau}</span>
|
||||||
|
<div class="bildset-rechts">
|
||||||
|
<div class="bildset-knoepfe">
|
||||||
|
<input class="bildw-datei" type="file" accept="image/png,image/jpeg,image/webp,image/gif" hidden />
|
||||||
|
<button type="button" class="btn bildw-waehlen">${quelle ? "ersetzen" : "hochladen"}</button>
|
||||||
|
<button type="button" class="btn bildw-link">link</button>
|
||||||
|
${quelle ? `<button type="button" class="btn bildw-weg">entfernen</button>` : ""}
|
||||||
|
</div>
|
||||||
|
<span class="w-stand">${escapeHtml(stand)}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${
|
||||||
|
linkAuf
|
||||||
|
? `<div class="bildw-linkzeile">
|
||||||
|
<input class="bildw-url" type="url" spellcheck="false"
|
||||||
|
placeholder="https://…" value="${escapeAttr(art === "link" ? quelle : "")}" />
|
||||||
|
<button type="button" class="btn bildw-uebernehmen">setzen</button>
|
||||||
|
</div>`
|
||||||
|
: ""
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
async function hochladen(file) {
|
||||||
melden("lädt hoch …");
|
melden("lädt hoch …");
|
||||||
try {
|
try {
|
||||||
@@ -106,6 +156,7 @@ async function hochladen(file) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
melden(e.message);
|
melden(e.message);
|
||||||
}
|
}
|
||||||
|
formNeu();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function linkSetzen(adresse) {
|
async function linkSetzen(adresse) {
|
||||||
@@ -118,13 +169,13 @@ async function linkSetzen(adresse) {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
melden(e.message);
|
melden(e.message);
|
||||||
}
|
}
|
||||||
|
formNeu();
|
||||||
}
|
}
|
||||||
|
|
||||||
function melden(s) {
|
function melden(s) {
|
||||||
stand = s;
|
stand = s;
|
||||||
document.querySelectorAll('[data-widget="bild"] .w-stand').forEach((el) => {
|
const el = document.querySelector(`#${FORM_ID} .w-stand`);
|
||||||
el.textContent = s;
|
if (el) el.textContent = s;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Neu zeichnen — stabile Widgets lässt der Rahmen sonst in Ruhe. */
|
/** Neu zeichnen — stabile Widgets lässt der Rahmen sonst in Ruhe. */
|
||||||
|
|||||||
Reference in new issue
Block a user