diff --git a/ajax/room.php b/ajax/room.php index 518d77c..beb5f95 100644 --- a/ajax/room.php +++ b/ajax/room.php @@ -133,19 +133,7 @@ if (!$gruppen && !$hatThermostat) { . "Das geht über das Haus-Symbol in der Karte „Automatismen“.
"; } -foreach (raumGruppenTitel() as $form => $titel) { - if (empty($gruppen[$form])) { - continue; - } - // Eine einzelne Lampe braucht keine Ueberschrift "Licht" - der Name der - // Lampe steht direkt darunter. Ueberschriften ordnen erst ab zwei. - if (count($gruppen[$form]) > 1) { - echo gruppenTitel($titel[0], $titel[1]); - } - foreach ($gruppen[$form] as $d) { - echo zeichneGeraet($d, $form); - } -} +echo zeichneGruppen($gruppen); echo ""; echo ""; diff --git a/css/solar.css b/css/solar.css index 1bbac26..577b901 100644 --- a/css/solar.css +++ b/css/solar.css @@ -1,15 +1,15 @@ -.winddir { - position:absolute; - left:60%; - width:25%; - height:40%; - padding: 0; - background-image: url('../assets/img/arrow.svg'); - background-size:contain; - background-repeat: no-repeat; - background-position: center; - } - +.winddir { + position:absolute; + left:60%; + width:25%; + height:40%; + padding: 0; + background-image: url('../assets/img/arrow.svg'); + background-size:contain; + background-repeat: no-repeat; + background-position: center; + } + /* Schieberegler in den Modals (Autoladung, Heizstab, Thermostat). Der gefuellte Anteil kommt aus --background-size, gesetzt von @@ -111,91 +111,176 @@ input[type='range']::-webkit-slider-runnable-track { .input-group.raum-cmd > .form-select { min-width: 5rem; } - - -/* -------------------------------------------------------------------------- - Raum-Modal: Bedienelemente - Erzeugt von restricted/roomControls.php, verdrahtet in js/solar/homeMQTT.js. - -------------------------------------------------------------------------- */ - -/* Ueberschrift einer Geraetegruppe (Beschattung, Licht, ...). Klein und - ruhig - sie ordnet, sie soll nicht mit den Geraeten um Aufmerksamkeit - ringen. */ -.raum-gruppe { - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.05em; - margin: 0.75rem 0 0.35rem; -} -.raum-gruppe:first-child { - margin-top: 0; -} - -/* Waehrend ein Kommando unterwegs ist. Ein "Zu" an einer Jalousie dauert - ueber eine Minute; ohne sichtbare Sperre tippt man in der Zeit nach. */ -.raum-geraet.raum-warte { - opacity: 0.55; - pointer-events: none; -} -.raum-geraet.raum-warte::after { - content: ""; - position: absolute; - top: 0.5rem; - right: 0.5rem; - width: 0.9rem; - height: 0.9rem; - border: 2px solid currentColor; - border-right-color: transparent; - border-radius: 50%; - animation: raum-dreht 0.8s linear infinite; -} -.raum-geraet { - position: relative; -} -@keyframes raum-dreht { - to { transform: rotate(360deg); } -} - -/* Schieberegler stehen dicht untereinander (Position und Neigung). Ohne den - Abstand kleben Beschriftung und Bahn aneinander. */ -.raum-regler + .raum-regler { - margin-top: 0.35rem; -} -.raum-regler .form-range { - margin-bottom: 0; -} - -/* Messwerte als Gitter statt als Textwurst: ein Stromzaehler hat sechzehn - Werte, die in einer Zeile niemand findet. auto-fill statt fester Spalten, - damit auf dem Handy zwei und auf dem Schirm vier nebeneinander passen. */ -.raum-werte { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr)); - gap: 0.4rem; -} -.raum-wert { - background: rgba(128, 128, 128, 0.08); - border-radius: 0.35rem; - padding: 0.3rem 0.45rem; - overflow: hidden; -} -.raum-wert-zahl { - font-size: 0.95rem; - font-weight: 600; - line-height: 1.2; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.raum-wert-einheit { - font-size: 0.7rem; - font-weight: 400; - opacity: 0.7; -} -.raum-wert-name { - font-size: 0.68rem; - opacity: 0.7; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} + + +/* -------------------------------------------------------------------------- + Raum-Modal: Bedienelemente + Erzeugt von restricted/roomControls.php, verdrahtet in js/solar/homeMQTT.js. + -------------------------------------------------------------------------- */ + +/* Die Geraetegruppen als Tabs. Auf einem Handy passen fuenf Beschriftungen + nicht nebeneinander - dann scrollt die Leiste seitwaerts, statt die Namen + umzubrechen. */ +.raum-tabs { + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; +} +.raum-tabs::-webkit-scrollbar { + display: none; +} +.raum-tabs .nav-link { + padding: 0.25rem 0.6rem; + font-size: 0.8rem; + white-space: nowrap; +} + +/* Waehrend ein Kommando unterwegs ist. Ein "Zu" an einer Jalousie dauert + ueber eine Minute; ohne sichtbare Sperre tippt man in der Zeit nach. */ +.raum-geraet.raum-warte { + opacity: 0.55; + pointer-events: none; +} +.raum-geraet.raum-warte::after { + content: ""; + position: absolute; + top: 0.5rem; + right: 0.5rem; + width: 0.9rem; + height: 0.9rem; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: raum-dreht 0.8s linear infinite; +} +.raum-geraet { + position: relative; +} +@keyframes raum-dreht { + to { transform: rotate(360deg); } +} + +/* Schieberegler stehen dicht untereinander (Position und Neigung). Ohne den + Abstand kleben Beschriftung und Bahn aneinander. */ +.raum-regler + .raum-regler { + margin-top: 0.35rem; +} +.raum-regler .form-range { + margin-bottom: 0; +} + +/* Messwerte als Gitter statt als Textwurst: ein Stromzaehler hat sechzehn + Werte, die in einer Zeile niemand findet. auto-fill statt fester Spalten, + damit auf dem Handy zwei und auf dem Schirm vier nebeneinander passen. */ +.raum-werte { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr)); + gap: 0.4rem; +} +.raum-wert { + background: rgba(128, 128, 128, 0.08); + border-radius: 0.35rem; + padding: 0.3rem 0.45rem; + overflow: hidden; +} +.raum-wert-zahl { + font-size: 0.95rem; + font-weight: 600; + line-height: 1.2; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.raum-wert-einheit { + font-size: 0.7rem; + font-weight: 400; + opacity: 0.6; +} +.raum-wert-name { + font-size: 0.68rem; + opacity: 0.6; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + + +/* -------------------------------------------------------------------------- + Raum-Modal: Jalousien + -------------------------------------------------------------------------- */ + +/* Fahrknoepfe, Hoehe und Neigung nebeneinander. Die Neigung nimmt den Rest - + sie ist die einzige der drei Spalten, die von Breite profitiert. */ +.raum-jalousie { + display: flex; + align-items: stretch; + gap: 0.6rem; +} +.raum-jalousie .raum-neigung-block { + flex: 1 1 auto; + min-width: 0; + align-self: center; +} +.raum-jalousie .dropdown { + align-self: flex-start; +} + +/* Auf, Stop, Zu untereinander - die Knoepfe zeigen in die Richtung, in die + sie fahren. Schmal, weil nur Symbole darin stehen. */ +.raum-fahrt { + flex: 0 0 auto; +} +.raum-fahrt .btn { + padding: 0.2rem 0.55rem; + line-height: 1.4; +} + +/* Die Hoehe als senkrechter Regler: der Griff steht dort, wo der Rollladen + steht. writing-mode ist der Weg, den Chrome und Firefox heute gehen - + slider-vertical war nie standardisiert und ist inzwischen entfernt. */ +.raum-position { + flex: 0 0 auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.1rem; +} +.raum-position input[type='range'] { + writing-mode: vertical-rl; + width: 1.2rem; + height: 4.5rem; + padding: 0; + margin: 0; +} +/* Die gefuellte Bahn laeuft hier von oben nach unten mit - "voll" heisst + heruntergefahren. */ +.raum-position input[type='range']::-webkit-slider-runnable-track { + background: linear-gradient(to bottom, #00788F, #00788F), #D7D7D7; + background-size: 100% var(--background-size, 0%); +} + +/* Die Endstellungen in Worten statt in Prozent. */ +.raum-ende { + font-size: 0.68rem; + opacity: 0.65; + line-height: 1.1; +} + +/* Die Lamellen zur Neigung. Ihre Hoehe setzt lamellenZeichnen() in + js/solar/homeMQTT.js - schmal mit Luft dazwischen heisst offen, + aneinanderliegend heisst geschlossen. */ +.raum-lamellen { + display: block; + width: 100%; + height: 1.8rem; + margin: 0.1rem 0; + /* Der Rahmen macht aus dem Streifen ein Fenster - ohne ihn liest sich das + geschlossene Bild wie eine zweite Reglerbahn. */ + background: rgba(0, 120, 143, 0.14); + border: 1px solid rgba(128, 128, 128, 0.35); + border-radius: 0.2rem; +} +.raum-lamelle { + fill: currentColor; + opacity: 0.6; +} diff --git a/js/solar/homeMQTT.js b/js/solar/homeMQTT.js index 915dd79..e95c463 100644 --- a/js/solar/homeMQTT.js +++ b/js/solar/homeMQTT.js @@ -226,14 +226,13 @@ function switchFloor(floor){ k.addEventListener("click", () => schalte(k)); }); - // Schieberegler: waehrend des Ziehens nur die Beschriftung, geschickt - // wird erst beim Loslassen. Sonst bekaeme eine Jalousie bei jedem Pixel - // ein neues Ziel und kaeme aus dem Anfahren nicht mehr heraus. + // Schieberegler: waehrend des Ziehens nur die Anzeige, geschickt wird + // erst beim Loslassen. Sonst bekaeme eine Jalousie bei jedem Pixel ein + // neues Ziel und kaeme aus dem Anfahren nicht mehr heraus. koerper.querySelectorAll(".raum-regler").forEach(regler => { const schieber = regler.querySelector("input[type=range]"); - const anzeige = regler.querySelector(".raum-regler-wert"); - const einheit = schieber.dataset.einheit || ""; - schieber.addEventListener("input", () => { anzeige.textContent = schieber.value + einheit; }); + reglerAnzeigen(regler); + schieber.addEventListener("input", () => reglerAnzeigen(regler)); schieber.addEventListener("change", () => { const params = {}; params[regler.dataset.param] = schieber.value; @@ -280,6 +279,54 @@ function switchFloor(floor){ }); } + /** + * Anzeige, gefuellte Bahn und Lamellenbild eines Reglers nachziehen. + * + * An den Endstellungen steht ein Wort statt der Zahl: "unten" liest sich + * ohne Umweg, "100 %" muss man erst uebersetzen. Dazwischen bleibt es bei + * der Prozentzahl - fuer halb heruntergefahren gibt es kein Wort. + */ + function reglerAnzeigen(regler) { + const schieber = regler.querySelector("input[type=range]"); + const anzeige = regler.querySelector(".raum-regler-wert"); + const wert = Number(schieber.value); + + if (anzeige) { + const worte = regler.dataset.worte ? JSON.parse(regler.dataset.worte) : null; + anzeige.textContent = (worte && worte[wert] !== undefined) + ? worte[wert] + : wert + (schieber.dataset.einheit || ""); + } + + // Der gefuellte Anteil der Bahn - dieselbe Eigenschaft, aus der auch + // bindModalSlider() in common.js den Temperaturregler faerbt. + const min = Number(schieber.min); + const max = Number(schieber.max); + schieber.style.setProperty("--background-size", + ((wert - min) * 100 / (max - min)) + "%"); + + lamellenZeichnen(regler.closest(".raum-neigung-block"), wert, min, max); + } + + /** + * Die Lamellen zur Neigung zeichnen. + * + * Gezeichnet wird die Hoehe, die eine gekippte Lamelle von vorn zeigt: + * w * sin(Winkel). Bei 0 % steht sie waagerecht und man sieht nur ihre + * Kante, bei 100 % steht sie quer und deckt den Abstand zur naechsten + * vollstaendig ab - genau das, was das Licht abhaelt. + */ + function lamellenZeichnen(block, wert, min, max) { + if (!block) return; + const anteil = (wert - min) / (max - min); + const hoehe = Math.max(1.6, 11 * Math.sin(anteil * Math.PI / 2)); + block.querySelectorAll(".raum-lamelle").forEach(lamelle => { + const mitte = Number(lamelle.dataset.mitte); + lamelle.setAttribute("height", hoehe); + lamelle.setAttribute("y", mitte - hoehe / 2); + }); + } + /** * Die Ist-Werte im Heizungsteil aus dem laufenden MQTT-Strom fuellen. * diff --git a/restricted/roomControls.php b/restricted/roomControls.php index 10f0907..cdd4b64 100644 --- a/restricted/roomControls.php +++ b/restricted/roomControls.php @@ -137,18 +137,72 @@ function bedienform($d) return "sonstiges"; } -/** Ueberschriften der Gruppen, in der Reihenfolge, in der sie erscheinen. */ +/** + * Die Gruppen in der Reihenfolge, in der ihre Tabs erscheinen. + * + * Beschattung zuerst: das ist es, was man in einem Raum am haeufigsten + * anfasst. Messwerte zuletzt - die schaut man an, man bedient sie nicht. + */ function raumGruppenTitel() { return [ "beschattung" => ["Beschattung", "bi-blinds"], "licht" => ["Licht", "bi-lightbulb"], "schalter" => ["Schalten", "bi-toggle-on"], - "sonstiges" => ["Weitere Geräte", "bi-sliders"], + "sonstiges" => ["Geräte", "bi-sliders"], "messwerte" => ["Messwerte", "bi-speedometer2"], ]; } +/** + * Die Geraetegruppen als Tabs. + * + * Vorher standen sie untereinander, und man scrollte an drei Rollladen + * vorbei, um zum Licht zu kommen. Bei nur einer Gruppe bleibt die Leiste + * weg - ein einzelner Tab beschriftet nur sich selbst. + */ +function zeichneGruppen($gruppen) +{ + $vorhanden = []; + foreach (raumGruppenTitel() as $form => $titel) { + if (!empty($gruppen[$form])) { + $vorhanden[$form] = $titel; + } + } + if (!$vorhanden) { + return ""; + } + + $html = ""; + if (count($vorhanden) > 1) { + $html .= ""; + } + + $html .= "