/* * Gemeinsame Bausteine der Dashboard-Seiten. * Wird vor solarMQTT.js / homeMQTT.js / heatMQTT.js / historyMQTT.js geladen. */ /** Sekunden als "HH:MM". */ String.prototype.toHHMM = function () { var sec_num = parseInt(this, 10); var hours = Math.floor(sec_num / 3600); var minutes = Math.floor((sec_num - (hours * 3600)) / 60); if (hours < 10) { hours = "0" + hours; } if (minutes < 10) { minutes = "0" + minutes; } return hours + ':' + minutes; }; function powerToString(power) { if (Math.abs(power) > 999) { power = power / 1000; return power.toPrecision(3) + " kW"; } else { return Math.round(power) + " W"; } } function loadingHTML(msg) { return "
Loading...
   " + msg + "
"; } /** * Diagrammdaten holen und eintragen. * params Funktion, die {FROM, TO} liefert (Zeitraum aendert sich zur Laufzeit) * sunrise true -> Sonnenstands-Markierungen fuer denselben Zeitraum * Objekt {FROM, TO} -> fester Zeitraum * autoUpdate alle 5 Minuten neu laden * updateMode an chart.update() durchgereicht ("none" = ohne Animation) */ async function getData(chart, url, options = {}) { const { params = null, sunrise = false, autoUpdate = true, updateMode = undefined, refreshMs = 5 * 60 * 1000 } = options; const range = params ? params() : null; const query = range ? "?FROM=" + range.FROM + "&TO=" + range.TO : ""; try { chart.options.events = []; // waehrend des Ladens keine Tooltips const response = await fetch(url + query); if (!response.ok) { throw new Error(`Response status: ${response.status}`); } chart.data = await response.json(); if (sunrise) { const sunRange = sunrise === true ? range : sunrise; const response2 = await fetch("ajax/getSunrise.php?FROM=" + sunRange.FROM + "&TO=" + sunRange.TO); if (!response2.ok) { throw new Error(`Response status: ${response2.status}`); } chart.options.plugins.annotation.annotations = await response2.json(); } chart.options.events = ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove']; chart.update(updateMode); } catch (error) { console.log("getData " + url + ": " + error.message); } if (autoUpdate) { setTimeout(function () { getData(chart, url, options); }, refreshMs); } } /** * Schieberegler im Modal mit seiner Beschriftung verbinden. * label(wert) liefert den anzuzeigenden Text. */ function bindModalSlider(slider, span, label) { slider.oninput = function () { const min = Number(slider.min); const max = Number(slider.max); const percent = (Number(slider.value) - min) * 100 / (max - min); slider.style.setProperty("--background-size", `${percent}%`); // Beschriftung mitlaufen lassen, an den Raendern etwas gebremst let margin = percent; if (margin < 10) { margin = margin - 0.4 * margin; } else if (margin < 85) { margin = margin - 4; } else { margin = margin - 4 - 0.5 * (margin - 85); } span.setAttribute('style', 'margin-left:' + margin + '%;'); span.innerHTML = label(slider.value); }; } /** Meteoblue-Meteogramm einhängen, sofern die Seite einen Platz dafür hat. */ // Mindestbreite, in der das Meteogramm von meteoblue geladen wird. Der // Anbieter legt sein Bild einmal beim Laden fest und zeichnet es bei einer // Breitenaenderung nicht neu; unterhalb von etwa 600 px ueberlagern sich // Wochentage und Stundenachse zu einem unlesbaren Streifen. Auf schmalen // Schirmen wird es deshalb in dieser Breite geladen und als Ganzes // verkleinert - dieselbe Ansicht wie am Rechner, nur kleiner. Ist die Karte // breiter, wird gleich in Kartenbreite geladen und nichts skaliert. const METEOGRAM_BREITE = 640; const METEOGRAM_HOEHE = 500; function mountMeteogram() { const ziel = document.getElementById("meteogram"); if (!ziel) return; // loading="lazy": das Meteogramm ist ein vollstaendiges Fremd-Dokument und // liegt unterhalb der Falz - es wird erst geladen, wenn man hinscrollt. const stil = window.getComputedStyle(ziel); const innenBreite = ziel.clientWidth - parseFloat(stil.paddingLeft) - parseFloat(stil.paddingRight); const ladeBreite = Math.max(METEOGRAM_BREITE, Math.round(innenBreite) || 0); ziel.innerHTML = "
" + "" + "
" + "" + ""; const buehne = ziel.querySelector(".meteogram-buehne"); const rahmen = buehne.querySelector("iframe"); // Zuschnitt und Skalierung stehen hier und nicht im Stylesheet: sonst // haengt die Darstellung an einer womoeglich veralteten solar.css im // Browser-Cache, und der breit geladene Rahmen ragte ungeschnitten aus // der Karte heraus. buehne.style.overflow = "hidden"; rahmen.style.display = "block"; rahmen.style.transformOrigin = "0 0"; let letzteBreite = 0; const anpassen = function () { const breite = buehne.clientWidth; if (breite === 0 || breite === letzteBreite) return; letzteBreite = breite; const faktor = Math.min(1, breite / ladeBreite); rahmen.style.transform = "scale(" + faktor + ")"; buehne.style.height = Math.round(METEOGRAM_HOEHE * faktor) + "px"; }; anpassen(); // Nur auf Breitenaenderungen reagieren - die Hoehe setzt anpassen() selbst, // sonst riefe sich der Beobachter gegenseitig auf. if (window.ResizeObserver) { new ResizeObserver(anpassen).observe(buehne); } else { window.addEventListener("resize", anpassen); } } /** * Wetterkacheln aktualisieren (restricted/weather_cards.html). * value(name) liefert den Wert zu weatherStation/ - die Seiten legen * die MQTT-Werte unterschiedlich ab, deshalb der Zugriff über eine Funktion. */ function updateWeatherCards(value) { const windDir = document.getElementById("windDir"); if (!windDir) return; // Seite ohne Wetterkacheln windDir.style.transform = "rotate(" + (90 + Number(value("windDeg"))) + "deg)"; document.getElementById("windSpd").innerHTML = value("avgWindspeed"); document.getElementById("humidity").innerHTML = value("hum"); document.getElementById("temp").innerHTML = value("tempAmb"); document.getElementById("windDirGust").style.transform = "rotate(" + (90 + Number(value("gustDeg"))) + "deg)"; document.getElementById("windGust").innerHTML = value("maxgust"); document.getElementById("ambPress").innerHTML = Math.round(value("qff") * 10) / 10; } /** * Offenen Reiter in der Adresse merken. * * Gilt fuer jede Leiste, die restricted/reiter.php mit "merken" gebaut hat - * erkennbar an data-reiter-merken. Ohne das landet man nach jedem Neuladen * wieder im ersten Reiter, obwohl man gerade woanders gearbeitet hat. * * In der Adresse steht nur das Kuerzel des Reiters (#kacheln), nicht der Name * der Leiste: es ist eine Adresse, die man auch von Hand tippt. Zwei merkende * Leisten auf einer Seite waeren damit nicht zu unterscheiden - dann muesste * hier der Name davor, und die Adresse waere haesslicher als noetig. */ function reiterMerken() { if (typeof bootstrap === "undefined") return; document.querySelectorAll("[data-reiter-merken]").forEach(function (leiste) { const knoepfe = leiste.querySelectorAll("[data-reiter]"); const gewuenscht = location.hash.replace("#", ""); knoepfe.forEach(function (knopf) { knopf.addEventListener("shown.bs.tab", function () { history.replaceState(null, "", "#" + knopf.dataset.reiter); }); if (gewuenscht && knopf.dataset.reiter === gewuenscht) { new bootstrap.Tab(knopf).show(); } }); }); } document.addEventListener("DOMContentLoaded", reiterMerken);