Raum-Modal Licht: Preset als Chips, Effekt als durchsuchbarer Picker, WLED-Zustand live
Die native Auswahlliste zeichnete Chrome hell auf hell. Welcher Effekt und welches Preset gerade laufen, liest room.php?action=wled direkt von der Lampe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -115,7 +115,7 @@ einen Einmal-Link (`addUser.php`, Tabelle `addUser`, eine Minute gültig).
|
||||
welche Seite es gibt und was sie braucht:
|
||||
|
||||
```php
|
||||
"home" => ["template" => "home.php", "mqtt" => true, "charts" => false,
|
||||
"home" => ["`POST ?action=command|temp|jalousie|my|wled` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand und WLED-Zustand (Effekt, Preset) live lesen;late" => "home.php", "mqtt" => true, "charts" => false,
|
||||
"meteogram" => false, "leaflet" => false,
|
||||
"scripts" => ["js/solar/autoActionFuncs.js", "js/solar/homeMQTT.js"]],
|
||||
```
|
||||
@@ -184,7 +184,7 @@ genau die Topics, die auf den Kacheln stehen (`tileTopics()` in `rooms.php`).
|
||||
| Seite (`?action=`) | Vorlage | Seitenskripte | holt per fetch | hört auf MQTT |
|
||||
|---|---|---|---|---|
|
||||
| `solar` (Vorgabe) | `solar.php` | `jahresstatistik.js`, `anlageKatalog.js`, `anlage.js`, `speicher.js`, `energieflussKatalog.js`, `energiefluss.js`, `bewaesserungAnzeige.js`, `ladefenster.js`, `bedienfenster.js`, `solarMQTT.js` | `speicher`, `getProdData`, `getConsData`, `getForecastData`, `getSunrise`, `getStats`, `carEG`, `carOG`, `heater`, `watering` | `solarManager/#`, `weatherStation/#`, `wattpilot/#`, `go-eCharger/#`, `Gartenwasser/#` |
|
||||
| `home` | `home.php` | `autoActionFuncs.js`, `zeitleiste.js`, `raumfenster.js`, `homeMQTT.js` | `room.php`, `AutoAction.php` | die Topics der Kacheln, `Raumtemp/#` |
|
||||
| `home` | `home.php` | `autoActionFuncs.js`, `zeitleiste.js`, `raumfenster.js`, `homeMQTT.js` | `room.php`, `AutoAction.php` | die Topics der Kacheln, `Raum`POST ?action=command|temp|jalousie|my|wled` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand und WLED-Zustand (Effekt, Preset) live lesen;/#` |
|
||||
| `heat` | `heat.php` | `heatMQTT.js` | `getHeaterData`, `getWaterData`, `getSunrise` | `solarManager/#`, `weatherStation/#`, Wallbox-Topics |
|
||||
| `history` | `history.php` | `jahresstatistik.js`, `historyMQTT.js` | `energyHistory` (6 ×), `getStats` | — |
|
||||
| `skoda` | `skoda.php` | `skodaMQTT.js` | `skoda.php?was=…`, `skodaCmd.php`, `tile.php` | `solarManager/#` |
|
||||
@@ -429,7 +429,7 @@ klein: Diagramme füllen bzw. Logzeilen nachladen und einfärben.
|
||||
|
||||
| Endpunkt | Aufruf | liefert / tut |
|
||||
|---|---|---|
|
||||
| `room.php` | `?room=<rooms.id>`, `POST ?action=command\|temp\|jalousie\|my` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand lesen; aktuellen Stand als my-Position in den Motor schreiben (`setMemorized1Position`/`-Orientation`) |
|
||||
| `room.php` | `?room=<rooms.id>`, `POST ?action=command|temp|jalousie|my|wled` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand und WLED-Zustand (Effekt, Preset) live lesen;\|temp\|jalousie\|my` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand lesen; aktuellen Stand als my-Position in den Motor schreiben (`setMemorized1Position`/`-Orientation`) |
|
||||
| `AutoAction.php` | `?action=editor\|list\|zeitleiste\|followers\|werte`, `POST save\|delete\|toggle` | Automatik-Editor, Übersicht als Liste und als Zeitleiste (`&datum=YYYY-MM-DD`) |
|
||||
| `settings.php` | `?action=list\|kacheln\|geraete\|grundriss\|skoda-keys`, `POST save\|kacheln-save\|geraete-raeume\|geraet-loeschen\|etage-speichern\|etagen-reihenfolge\|etage-loeschen\|etage-bild\|raum-speichern\|raum-position\|raum-loeschen\|skoda-keys-save` | Einstellungsseite; die Grundriss-Aktionen antworten mit dem ganzen neuen Stand, `skoda-keys` meldet nur, ob ein Schlüssel hinterlegt ist und worauf er endet |
|
||||
| `getStats.php` | `GET` | Jahresstatistik, alle Jahre, mit Metadaten je Kennzahl |
|
||||
@@ -532,7 +532,7 @@ erDiagram
|
||||
|---|---|---|
|
||||
| `EnergyFlow`, `EnergyFlow_hourly` | Leistungen im Rohtakt und als Stundenarchiv | `solarManager.py` |
|
||||
| `stats_daily` | Tageswerte für die Jahresstatistik | `solarManager.py` |
|
||||
| `Heater`, `puffertemp`, `wasser`, `zisterne`, `windrad`, `kiga_windrad` | Heizung, Speicher, Wasser, Windrad | `solarManager.py` |
|
||||
| `Heater`, `puffer`POST ?action=command|temp|jalousie|my|wled` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand und WLED-Zustand (Effekt, Preset) live lesen;`, `wasser`, `zisterne`, `windrad`, `kiga_windrad` | Heizung, Speicher, Wasser, Windrad | `solarManager.py` |
|
||||
| `skoda`, `skoda_raw` | Fahrzeugzustand im Verlauf | `gatherSkodaData.py` |
|
||||
| `skoda_ladepunkte` | Wallbox-Verlauf während einer Ladung, im Minutentakt, wird nicht ausgedünnt (`solarLog_skoda_ladepunkte.sql`) | `gatherSkodaData.py`; ältere Ladungen `skoda_ladepunkte_nachtragen.py` |
|
||||
| `byd`, `byd_zellen` | BYD-Speicher direkt aus der BMU: Ladestand, SOH, Temperaturen, Spreizung, Zähler alle 5 Minuten; alle 128 Zellspannungen und 64 Temperaturen alle 15 Minuten (`solarLog_byd.sql`) | `gatherBYDData.py` |
|
||||
@@ -568,7 +568,7 @@ Automatiken lässt sich nicht löschen.
|
||||
Ist und Feuchte aus diesem Zweig, sonst nur der Name. Derselbe Zweig trägt
|
||||
Heizsymbol und Temperaturregler (`mqttZweig()` in `homeMQTT.js`,
|
||||
`<zweig>/changeSetTemp` in `room.php`) — nichts setzt mehr voraus, dass ein
|
||||
Thermostat unter `Raumtemp/<Etage>/<Raum>` meldet.
|
||||
Thermostat unter `Raum`POST ?action=command|temp|jalousie|my|wled` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand und WLED-Zustand (Effekt, Preset) live lesen;/<Etage>/<Raum>` meldet.
|
||||
|
||||
**Grundrissbilder** kommen per Upload nach `tiles/grundriss/` — das einzige
|
||||
Verzeichnis, in das der Webserver schreiben darf, und nicht im Git. PNG, JPEG
|
||||
@@ -814,8 +814,8 @@ Drei Eigenschaften, die man kennen muss:
|
||||
|
||||
Die **Wetterstation** hängt am Websocket von `192.168.179.42`;
|
||||
`wsMQTTbridge.py` im SolarManager legt ihre Felder unter `weatherStation/#`
|
||||
ab. Von dort kommen Außentemperatur, Wind und Böe — die Außentemperatur ist
|
||||
für den Hitzeschutz die bessere Bedingung als die Raumtemperatur (ist es
|
||||
ab. Von dort kommen Außen`POST ?action=command|temp|jalousie|my|wled` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand und WLED-Zustand (Effekt, Preset) live lesen;eratur, Wind und Böe — die Außentemperatur ist
|
||||
für den Hitzeschutz die bessere Bedingung als die Raum`POST ?action=command|temp|jalousie|my|wled` | Raum-Modal bauen; ein Kommando oder eine Solltemperatur senden; Jalousie-Stand und WLED-Zustand (Effekt, Preset) live lesen;eratur (ist es
|
||||
drinnen schon warm, ist es zum Verschatten zu spät), und ohne den Wind gibt
|
||||
es für Markise und Sonnensegel keinen Sturmschutz. Die trägen Werte
|
||||
(Temperatur, Feuchte, Druck, Taupunkt) gehen nur bei jeder einundzwanzigsten
|
||||
|
||||
@@ -168,6 +168,39 @@ if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
"myNeigung" => $zahl("core:Memorized1OrientationState"),
|
||||
]);
|
||||
}
|
||||
// Den Zustand einer WLED-Lampe live lesen: an/aus, Helligkeit, Farbe,
|
||||
// Effekt und Preset. In actor_states stehen nur die ersten drei, und
|
||||
// ohne Effekt und Preset koennte das Raum-Modal nicht zeigen, was
|
||||
// gerade laeuft.
|
||||
if (($_GET["action"] ?? "") === "wled") {
|
||||
$db = commandDb();
|
||||
$stmt = $db->prepare("SELECT url FROM actors WHERE id = ?");
|
||||
$id = intval($body["actor"] ?? 0);
|
||||
$stmt->bind_param("i", $id);
|
||||
$stmt->execute();
|
||||
$zeile = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
if (!$zeile || strpos(strval($zeile["url"]), "wled://") !== 0) {
|
||||
raumAntwort(["error" => "Keine WLED-Lampe mit dieser Nummer."], 404);
|
||||
}
|
||||
$ch = curl_init("http://" . rtrim(substr($zeile["url"], strlen("wled://")), "/") . "/json/state");
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 3);
|
||||
$stand = json_decode(strval(curl_exec($ch)), true);
|
||||
curl_close($ch);
|
||||
if (!is_array($stand)) {
|
||||
raumAntwort(["error" => "Die Lampe antwortet nicht."], 503);
|
||||
}
|
||||
$segment = $stand["seg"][0] ?? [];
|
||||
$farbe = $segment["col"][0] ?? null;
|
||||
raumAntwort([
|
||||
"an" => !empty($stand["on"]),
|
||||
"bri" => isset($stand["bri"]) ? intval($stand["bri"]) : null,
|
||||
"farbe" => is_array($farbe) && count($farbe) >= 3 ? array_map("intval", array_slice($farbe, 0, 3)) : null,
|
||||
"effect" => isset($segment["fx"]) ? intval($segment["fx"]) : null,
|
||||
"preset" => isset($stand["ps"]) ? intval($stand["ps"]) : null,
|
||||
]);
|
||||
}
|
||||
// Den Stand, auf dem die Jalousie gerade steht, als my-Position in den
|
||||
// Motor schreiben. Die Box kennt dafuer setMemorized1Position und bei
|
||||
// Raffstores zusaetzlich setMemorized1Orientation; die Geraetesuche
|
||||
|
||||
+25
-1
@@ -1111,7 +1111,31 @@ input[type='range']::-webkit-slider-runnable-track {
|
||||
.rf .rf-farbe:focus-visible, .rf .rf-farbe-eigen:focus-within { outline: 2px solid #f7c000; outline-offset: 3px; }
|
||||
.rf .rf-farbe-eigen { background: conic-gradient(#f55, #fd5, #5f7, #5df, #75f, #f5d, #f55); }
|
||||
.rf .rf-farbe-eigen input { position: absolute; inset: 0; opacity: 0; cursor: pointer; width: 100%; height: 100%; }
|
||||
.rf .rf-auswahl { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: .5rem; align-items: center; font-size: .78rem; color: var(--ton-schrift-2, #9aa6ad); margin: 0; }
|
||||
/* Preset, Effekt: Titel links, Chips oder Picker rechts. Lange Listen
|
||||
klappen als durchsuchbarer Kasten unter der Zeile auf. */
|
||||
.rf .rf-liste { display: grid; grid-template-columns: 4.3rem minmax(0, 1fr); align-items: start; gap: .4rem .5rem; padding-top: .55rem; border-top: 1px solid rgba(255, 255, 255, .06); }
|
||||
.rf .rf-liste + .rf-liste { border-top: 0; padding-top: 0; }
|
||||
.rf .rf-liste > .rf-reihe-titel { padding-top: .35rem; }
|
||||
.rf .rf-liste .rf-chip { padding: .28rem .65rem; font-size: .78rem; }
|
||||
.rf .rf-liste .rf-chip[aria-pressed="true"] { color: #1a1406; }
|
||||
.rf .rf-picker-knopf { all: unset; box-sizing: border-box; cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: .5rem; min-width: 0; padding: .38rem .7rem; border-radius: .6rem; background: #22292d; font-size: .82rem; font-weight: 500; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06); }
|
||||
.rf .rf-picker-knopf span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.rf .rf-picker-knopf .bi { font-size: .75rem; color: var(--ton-schrift-2, #9aa6ad); transition: transform .2s; }
|
||||
.rf .rf-picker-knopf:hover { background: #283035; }
|
||||
.rf .rf-picker-knopf[aria-expanded="true"] { box-shadow: inset 0 0 0 1.5px var(--a); }
|
||||
.rf .rf-picker-knopf[aria-expanded="true"] .bi { transform: rotate(180deg); }
|
||||
.rf .rf-picker-knopf:focus-visible { outline: 2px solid var(--a); outline-offset: 1px; }
|
||||
.rf .rf-picker { grid-column: 1 / -1; display: grid; gap: .45rem; padding: .5rem; border-radius: .65rem; background: #22292d; animation: rf-auf .2s cubic-bezier(.2, .7, .2, 1); }
|
||||
.rf .rf-picker[hidden] { display: none; }
|
||||
.rf .rf-suche { all: unset; box-sizing: border-box; width: 100%; padding: .4rem .65rem; border-radius: .5rem; background: var(--ton-flaeche-2, #2b3237); color: var(--ton-schrift, #e8ede9); font-size: .82rem; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .08); }
|
||||
.rf .rf-suche::placeholder { color: var(--ton-schrift-3, #6f7a81); }
|
||||
.rf .rf-suche:focus { box-shadow: inset 0 0 0 1.5px var(--a); }
|
||||
.rf .rf-picker-liste { display: flex; flex-wrap: wrap; align-content: flex-start; gap: .3rem; max-height: 11rem; overflow-y: auto; padding-right: .25rem; scrollbar-width: thin; scrollbar-color: #3f474e transparent; }
|
||||
.rf .rf-picker-liste .rf-chip[hidden] { display: none; }
|
||||
@media (max-width: 575.98px) {
|
||||
.rf .rf-liste { grid-template-columns: 1fr; gap: .3rem; }
|
||||
.rf .rf-liste > .rf-reihe-titel { padding-top: 0; }
|
||||
}
|
||||
|
||||
/* --- Schalter --- */
|
||||
.rf .rf-schalter-liste { display: grid; grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); gap: .5rem; }
|
||||
|
||||
+79
-11
@@ -620,18 +620,86 @@ function rfLampeStarten(karte) {
|
||||
eigen.addEventListener("change", () => setzen(eigen.value));
|
||||
}
|
||||
|
||||
// Effekt und Preset: Chips oder - bei langen Listen - eine Auswahl.
|
||||
karte.querySelectorAll(".rf-effekte .rf-chips[data-cmd]").forEach(gruppe => {
|
||||
gruppe.querySelectorAll("[data-wert]").forEach(k => k.addEventListener("click", () => {
|
||||
gruppe.querySelectorAll("[data-wert]").forEach(x => x.setAttribute("aria-pressed", String(x === k)));
|
||||
// Preset, Effekt und andere Listen: Chips, lange Listen im aufklappbaren
|
||||
// Picker mit Suche.
|
||||
karte.querySelectorAll(".rf-liste").forEach(liste => {
|
||||
const knopf = liste.querySelector(".rf-picker-knopf");
|
||||
const picker = liste.querySelector(".rf-picker");
|
||||
const suche = liste.querySelector(".rf-suche");
|
||||
const auf = offen => {
|
||||
if (!picker) return;
|
||||
picker.hidden = !offen;
|
||||
knopf.setAttribute("aria-expanded", String(offen));
|
||||
if (offen) {
|
||||
suche.value = "";
|
||||
liste.querySelectorAll("[data-wert]").forEach(k => { k.hidden = false; });
|
||||
const gewaehlt = liste.querySelector("[data-wert][aria-pressed='true']");
|
||||
if (gewaehlt) gewaehlt.scrollIntoView({ block: "nearest" });
|
||||
suche.focus({ preventScroll: true });
|
||||
}
|
||||
};
|
||||
if (knopf) knopf.addEventListener("click", () => auf(picker.hidden));
|
||||
if (suche) {
|
||||
suche.addEventListener("input", () => {
|
||||
const text = suche.value.trim().toLowerCase();
|
||||
liste.querySelectorAll("[data-wert]").forEach(k => { k.hidden = text !== "" && !k.textContent.toLowerCase().includes(text); });
|
||||
});
|
||||
suche.addEventListener("keydown", e => {
|
||||
if (e.key === "Escape") { e.stopPropagation(); auf(false); knopf.focus(); }
|
||||
if (e.key === "Enter") {
|
||||
const erster = [...liste.querySelectorAll("[data-wert]")].find(k => !k.hidden);
|
||||
if (erster) erster.click();
|
||||
}
|
||||
});
|
||||
}
|
||||
liste.querySelectorAll("[data-wert]").forEach(k => k.addEventListener("click", () => {
|
||||
rfListeSetzen(liste, k.dataset.wert);
|
||||
auf(false);
|
||||
const params = {};
|
||||
params[gruppe.dataset.param] = k.dataset.wert;
|
||||
rfKommando(Number(gruppe.dataset.cmd), params, gruppe);
|
||||
params[liste.dataset.param] = k.dataset.wert;
|
||||
rfKommando(Number(liste.dataset.cmd), params, liste);
|
||||
}));
|
||||
});
|
||||
karte.querySelectorAll(".rf-effekte select[data-cmd]").forEach(auswahl => auswahl.addEventListener("change", () => {
|
||||
const params = {};
|
||||
params[auswahl.dataset.param] = auswahl.value;
|
||||
rfKommando(Number(auswahl.dataset.cmd), params, auswahl);
|
||||
}));
|
||||
|
||||
if (karte.dataset.wled) rfWledLesen(karte);
|
||||
}
|
||||
|
||||
/** Einen Eintrag einer Liste als gewaehlt zeigen; null = keiner. */
|
||||
function rfListeSetzen(liste, wert) {
|
||||
let name = null;
|
||||
liste.querySelectorAll("[data-wert]").forEach(k => {
|
||||
const ja = wert !== null && k.dataset.wert === String(wert);
|
||||
k.setAttribute("aria-pressed", String(ja));
|
||||
if (ja) name = k.textContent;
|
||||
});
|
||||
const anzeige = liste.querySelector("[data-rf-picker-wert]");
|
||||
if (anzeige) anzeige.textContent = name === null ? "–" : name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Den wirklichen Zustand einer WLED-Lampe holen. In der Datenbank stehen nur
|
||||
* an/aus, Helligkeit und Farbe vom letzten Poll; welcher Effekt und welches
|
||||
* Preset laufen, weiss nur die Lampe selbst.
|
||||
*/
|
||||
function rfWledLesen(karte) {
|
||||
rfRequest("wled", { actor: Number(karte.dataset.actor) }).then(stand => {
|
||||
if (!stand || stand.error || !document.body.contains(karte)) return;
|
||||
karte.dataset.an = stand.an ? "1" : "";
|
||||
const hell = karte.querySelector(".rf-hell");
|
||||
if (hell && stand.bri !== null && document.activeElement !== hell) hell.value = stand.bri;
|
||||
if (stand.farbe && (stand.farbe[0] || stand.farbe[1] || stand.farbe[2])) {
|
||||
const hex = "#" + stand.farbe.map(v => Math.max(0, Math.min(255, v)).toString(16).padStart(2, "0")).join("");
|
||||
karte.style.setProperty("--farbe", hex);
|
||||
const eigen = karte.querySelector(".rf-farbe-eigen input");
|
||||
if (eigen) eigen.value = hex;
|
||||
karte.querySelectorAll("[data-farbe]").forEach(k => k.setAttribute("aria-pressed", String(k.dataset.farbe === hex)));
|
||||
}
|
||||
karte.querySelectorAll(".rf-liste").forEach(liste => {
|
||||
const wert = stand[liste.dataset.feld];
|
||||
// WLED meldet ps = -1, wenn kein Preset aktiv ist.
|
||||
if (typeof wert !== "undefined") rfListeSetzen(liste, wert === null || wert < 0 ? null : wert);
|
||||
});
|
||||
rfLampeZeichnen(karte);
|
||||
rfStatus();
|
||||
});
|
||||
}
|
||||
|
||||
+29
-23
@@ -462,7 +462,8 @@ function zeichneLicht($d)
|
||||
. " data-ein='" . (isset($schalter["ein"]) ? intval($schalter["ein"]["id"]) : "") . "'"
|
||||
. " data-aus='" . (isset($schalter["aus"]) ? intval($schalter["aus"]["id"]) : "") . "'"
|
||||
. " data-um='" . (isset($schalter["um"]) ? intval($schalter["um"]["id"]) : "") . "'"
|
||||
. " data-an='" . ($an ? "1" : "") . "' style='--farbe:" . $hex . "'>";
|
||||
. " data-an='" . ($an ? "1" : "") . "'" . (strpos(strval($d["url"]), "wled://") === 0 ? " data-wled='1'" : "")
|
||||
. " style='--farbe:" . $hex . "'>";
|
||||
|
||||
$html .= "<div class='rf-lampe-kopf'>"
|
||||
. "<button type='button' class='rf-birne' data-rf-birne aria-pressed='" . ($an ? "true" : "false") . "'"
|
||||
@@ -506,29 +507,34 @@ function zeichneLicht($d)
|
||||
. "<input type='color' value='" . $hex . "' aria-label='Eigene Farbe'></label></div>";
|
||||
}
|
||||
|
||||
if ($listen) {
|
||||
$html .= "<details class='rf-effekte'><summary>" . htmlspecialchars(implode(" & ", array_map(function ($c) { return $c["name"]; }, $listen))) . "</summary>";
|
||||
foreach ($listen as $c) {
|
||||
$p = $c["params"][0];
|
||||
// Wenige Eintraege als Chips, lange Listen (WLED hat ueber 180
|
||||
// Effekte) als Auswahl - Chips waeren dort eine Wand.
|
||||
if (count($p["options"]) <= 16) {
|
||||
$html .= "<div class='rf-chips' data-cmd='" . intval($c["id"]) . "' data-param='" . intval($p["id"]) . "'>";
|
||||
foreach ($p["options"] as $o) {
|
||||
$html .= "<button type='button' class='rf-chip' data-wert='" . htmlspecialchars($o["value"], ENT_QUOTES) . "'"
|
||||
. " aria-pressed='false'>" . htmlspecialchars($o["label"]) . "</button>";
|
||||
}
|
||||
$html .= "</div>";
|
||||
} else {
|
||||
$html .= "<label class='rf-auswahl'><span>" . htmlspecialchars($c["name"]) . "</span>"
|
||||
. "<select class='form-select form-select-sm' data-cmd='" . intval($c["id"]) . "' data-param='" . intval($p["id"]) . "'>";
|
||||
foreach ($p["options"] as $o) {
|
||||
$html .= "<option value='" . htmlspecialchars($o["value"], ENT_QUOTES) . "'>" . htmlspecialchars($o["label"]) . "</option>";
|
||||
}
|
||||
$html .= "</select></label>";
|
||||
}
|
||||
// Presets, Effekte und andere Listen. Wenige Eintraege stehen als Chips
|
||||
// da; lange Listen (WLED hat ueber 180 Effekte) bekommen einen Knopf mit
|
||||
// dem aktuellen Eintrag, der eine durchsuchbare Liste aufklappt. Die
|
||||
// native Auswahlliste zeichnet der Browser hell auf hell und passt nicht
|
||||
// in das dunkle Fenster. Welcher Eintrag gerade gilt, liest
|
||||
// js/solar/raumfenster.js bei WLED live aus (room.php?action=wled);
|
||||
// data-feld ist der Parametername, "effect" oder "preset".
|
||||
foreach ($listen as $c) {
|
||||
$p = $c["params"][0];
|
||||
$kopf = "<div class='rf-liste' data-cmd='" . intval($c["id"]) . "' data-param='" . intval($p["id"]) . "'"
|
||||
. " data-feld='" . htmlspecialchars(strtolower($p["name"]), ENT_QUOTES) . "'>"
|
||||
. "<span class='rf-reihe-titel'>" . htmlspecialchars($c["name"]) . "</span>";
|
||||
$knoepfe = "";
|
||||
foreach ($p["options"] as $o) {
|
||||
$knoepfe .= "<button type='button' class='rf-chip' data-wert='" . htmlspecialchars($o["value"], ENT_QUOTES) . "'"
|
||||
. " aria-pressed='false'>" . htmlspecialchars($o["label"]) . "</button>";
|
||||
}
|
||||
if (count($p["options"]) <= 16) {
|
||||
$html .= $kopf . "<div class='rf-chips'>" . $knoepfe . "</div></div>";
|
||||
} else {
|
||||
$html .= $kopf
|
||||
. "<button type='button' class='rf-picker-knopf' aria-expanded='false'>"
|
||||
. "<span data-rf-picker-wert>–</span><i class='bi bi-chevron-down'></i></button>"
|
||||
. "<div class='rf-picker' hidden>"
|
||||
. "<input type='search' class='rf-suche' placeholder='" . htmlspecialchars($c["name"], ENT_QUOTES) . " suchen …'"
|
||||
. " aria-label='" . htmlspecialchars($c["name"], ENT_QUOTES) . " suchen'>"
|
||||
. "<div class='rf-picker-liste'>" . $knoepfe . "</div></div></div>";
|
||||
}
|
||||
$html .= "</details>";
|
||||
}
|
||||
return $html . "</div></div>";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user