HexaWetter v1.3.0: Add precipitation map feature, update README, and enhance UI elements
This commit is contained in:
327
frontend/app.js
327
frontend/app.js
@@ -14,6 +14,9 @@ let radarTimes = [];
|
||||
let radarFrameIndex = 0;
|
||||
let radarPlayTimer = null;
|
||||
let radarPlaying = false;
|
||||
let mapMode = "radar";
|
||||
let precipData = null;
|
||||
let precipLayerGroup = null;
|
||||
let hourlyChart = null;
|
||||
let settings = loadSettings();
|
||||
let searchTimer = null;
|
||||
@@ -27,8 +30,8 @@ const WEATHER_ICONS = {
|
||||
|
||||
function loadSettings() {
|
||||
try {
|
||||
return { theme: "dark", unitTemp: "celsius", unitWind: "kmh", radarMinutes: 120, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}") };
|
||||
} catch { return { theme: "dark", unitTemp: "celsius", unitWind: "kmh", radarMinutes: 120 }; }
|
||||
return { theme: "dark", unitTemp: "celsius", unitWind: "kmh", radarMinutes: 120, forecastHours: 24, ...JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}") };
|
||||
} catch { return { theme: "dark", unitTemp: "celsius", unitWind: "kmh", radarMinutes: 120, forecastHours: 24 }; }
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
@@ -43,7 +46,11 @@ function saveFavorites(items) {
|
||||
localStorage.setItem(FAV_KEY, JSON.stringify(items));
|
||||
}
|
||||
|
||||
function weatherText(code) {
|
||||
function weatherIcon(code) {
|
||||
return WEATHER_ICONS[code] || "🌡️";
|
||||
}
|
||||
|
||||
function weatherLabel(code) {
|
||||
const map = {
|
||||
0: "Klar", 1: "Überwiegend klar", 2: "Teils bewölkt", 3: "Bewölkt",
|
||||
45: "Nebel", 48: "Reifnebel", 51: "Leichter Niesel", 53: "Niesel",
|
||||
@@ -51,7 +58,11 @@ function weatherText(code) {
|
||||
71: "Leichter Schnee", 73: "Schnee", 75: "Starker Schnee", 80: "Regenschauer",
|
||||
81: "Starke Schauer", 82: "Heftige Schauer", 95: "Gewitter", 96: "Gewitter mit Hagel", 99: "Schweres Gewitter",
|
||||
};
|
||||
return `${WEATHER_ICONS[code] || ""} ${map[code] || `Code ${code}`}`.trim();
|
||||
return map[code] || `Code ${code}`;
|
||||
}
|
||||
|
||||
function weatherText(code) {
|
||||
return `${weatherIcon(code)} ${weatherLabel(code)}`.trim();
|
||||
}
|
||||
|
||||
function convertTemp(c) {
|
||||
@@ -270,6 +281,7 @@ function applyDashboard(dash) {
|
||||
renderDaily(dash.forecast.data.daily);
|
||||
renderHourly(dash.forecast.data.hourly);
|
||||
renderHourStrip(dash.forecast.data.hourly);
|
||||
renderForecastOutlook(dash.forecast.data.hourly);
|
||||
}
|
||||
|
||||
if (dash.observations?.data) {
|
||||
@@ -281,23 +293,43 @@ function applyDashboard(dash) {
|
||||
}
|
||||
}
|
||||
|
||||
if (dash.radar) {
|
||||
el("radarCount").textContent = `${dash.radar.count} online`;
|
||||
el("radarMeta").textContent = `Produkt ${dash.radar.product?.toUpperCase() || "RV"} · DWD OpenData`;
|
||||
}
|
||||
|
||||
if (dash.warnings) {
|
||||
applyWarningsData(dash.warnings);
|
||||
}
|
||||
}
|
||||
|
||||
function renderForecastOutlook(hourly) {
|
||||
if (!hourly?.time?.length) {
|
||||
el("rainOutlookBig").textContent = "–";
|
||||
el("rainOutlookMeta").textContent = "Kein Forecast";
|
||||
return;
|
||||
}
|
||||
|
||||
let nextRainIdx = -1;
|
||||
let maxProb = 0;
|
||||
hourly.time.slice(0, 24).forEach((_, idx) => {
|
||||
const prob = hourly.precipitation_probability[idx] ?? 0;
|
||||
const precip = hourly.precipitation[idx] ?? 0;
|
||||
if (nextRainIdx === -1 && (prob >= 40 || precip >= 0.15)) nextRainIdx = idx;
|
||||
maxProb = Math.max(maxProb, prob);
|
||||
});
|
||||
|
||||
if (nextRainIdx >= 0) {
|
||||
el("rainOutlookBig").textContent = formatDateTime(hourly.time[nextRainIdx]);
|
||||
el("rainOutlookMeta").textContent = `${hourly.precipitation_probability[nextRainIdx] ?? 0}% · ${hourly.precipitation[nextRainIdx] ?? 0} mm/h`;
|
||||
} else {
|
||||
el("rainOutlookBig").textContent = "Trocken";
|
||||
el("rainOutlookMeta").textContent = `Max. ${maxProb}% in 24 h`;
|
||||
}
|
||||
}
|
||||
|
||||
function applyWarningsData(data) {
|
||||
const localOnly = el("warningsLocalOnly")?.checked ?? true;
|
||||
const warnings = data.warnings || [];
|
||||
el("warnCount").textContent = String(data.count_local ?? warnings.length);
|
||||
el("warnMeta").textContent = localOnly
|
||||
? `${warnings.length} Warnung(en) für deinen Standort`
|
||||
: `${data.count_total} Warnungen bundesweit (Top ${warnings.length})`;
|
||||
? `${data.count_local ?? warnings.length} Warnung(en) für deinen Standort`
|
||||
: `${data.count_total} Warnungen bundesweit (${data.count_local ?? warnings.length} eindeutig)`;
|
||||
|
||||
const warncellEl = el("warncellInfo");
|
||||
if (localOnly && data.warncells?.length) {
|
||||
@@ -342,22 +374,75 @@ async function loadForecast() {
|
||||
renderDaily(data.data.daily);
|
||||
renderHourly(data.data.hourly);
|
||||
renderHourStrip(data.data.hourly);
|
||||
renderForecastOutlook(data.data.hourly);
|
||||
}
|
||||
|
||||
function dailyLabel(dateStr) {
|
||||
const d = new Date(`${dateStr}T12:00:00`);
|
||||
const today = new Date();
|
||||
today.setHours(12, 0, 0, 0);
|
||||
const diff = Math.round((d - today) / 86400000);
|
||||
if (diff === 0) return "Heute";
|
||||
if (diff === 1) return "Morgen";
|
||||
return d.toLocaleDateString("de-DE", { weekday: "long" });
|
||||
}
|
||||
|
||||
function renderDaily(daily) {
|
||||
if (!daily?.time?.length) {
|
||||
el("dailyForecast").innerHTML = `<p class="muted">Kein Forecast verfügbar.</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const weekMin = Math.min(...daily.temperature_2m_min);
|
||||
const weekMax = Math.max(...daily.temperature_2m_max);
|
||||
const tempSpan = Math.max(weekMax - weekMin, 1);
|
||||
|
||||
el("dailyForecast").innerHTML = daily.time.map((date, idx) => {
|
||||
const d = new Date(date + "T12:00:00");
|
||||
const day = d.toLocaleDateString("de-DE", { weekday: "short", day: "2-digit", month: "2-digit" });
|
||||
const sunrise = daily.sunrise?.[idx] ? new Date(daily.sunrise[idx]).toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" }) : "–";
|
||||
const sunset = daily.sunset?.[idx] ? new Date(daily.sunset[idx]).toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" }) : "–";
|
||||
return `<div class="day">
|
||||
<strong>${day}</strong>
|
||||
<span class="muted">${weatherText(daily.weather_code[idx])}</span>
|
||||
<p class="temp">${convertTemp(daily.temperature_2m_max[idx])} / ${convertTemp(daily.temperature_2m_min[idx])}</p>
|
||||
<span class="muted">Regen: ${daily.precipitation_sum[idx]} mm · ${daily.precipitation_probability_max[idx] ?? 0}%</span>
|
||||
<span class="muted block">Wind: ${convertWind(daily.wind_speed_10m_max[idx] ?? 0)} · Böen ${convertWind(daily.wind_gusts_10m_max[idx] ?? 0)}</span>
|
||||
<span class="muted block">Sonne: ${sunrise} – ${sunset}</span>
|
||||
</div>`;
|
||||
const d = new Date(`${date}T12:00:00`);
|
||||
const label = dailyLabel(date);
|
||||
const dateLine = d.toLocaleDateString("de-DE", { weekday: "short", day: "2-digit", month: "2-digit" });
|
||||
const code = daily.weather_code[idx];
|
||||
const tMax = daily.temperature_2m_max[idx];
|
||||
const tMin = daily.temperature_2m_min[idx];
|
||||
const rainProb = daily.precipitation_probability_max[idx] ?? 0;
|
||||
const rainMm = daily.precipitation_sum[idx] ?? 0;
|
||||
const wind = daily.wind_speed_10m_max[idx] ?? 0;
|
||||
const gusts = daily.wind_gusts_10m_max[idx] ?? 0;
|
||||
const sunrise = daily.sunrise?.[idx]
|
||||
? new Date(daily.sunrise[idx]).toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" })
|
||||
: "–";
|
||||
const sunset = daily.sunset?.[idx]
|
||||
? new Date(daily.sunset[idx]).toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" })
|
||||
: "–";
|
||||
|
||||
const barLeft = ((tMin - weekMin) / tempSpan) * 100;
|
||||
const barWidth = Math.max(((tMax - tMin) / tempSpan) * 100, 10);
|
||||
const isToday = idx === 0;
|
||||
const rainClass = rainProb >= 60 || rainMm >= 5 ? "rain-high" : rainProb >= 30 || rainMm >= 1 ? "rain-mid" : "";
|
||||
|
||||
return `<article class="day-card${isToday ? " day-card-today" : ""}${rainClass ? ` ${rainClass}` : ""}">
|
||||
<header class="day-card-head">
|
||||
<span class="day-card-label">${label}</span>
|
||||
<span class="day-card-date">${dateLine}</span>
|
||||
</header>
|
||||
<div class="day-card-icon" aria-hidden="true">${weatherIcon(code)}</div>
|
||||
<p class="day-card-condition">${weatherLabel(code)}</p>
|
||||
<div class="day-card-temps">
|
||||
<span class="day-temp-max">${convertTemp(tMax)}</span>
|
||||
<div class="day-temp-track" title="${convertTemp(tMin)} – ${convertTemp(tMax)}">
|
||||
<i class="day-temp-range" style="left:${barLeft}%;width:${barWidth}%"></i>
|
||||
</div>
|
||||
<span class="day-temp-min">${convertTemp(tMin)}</span>
|
||||
</div>
|
||||
<div class="day-card-rain">
|
||||
<div class="bar"><i style="width:${Math.min(rainProb, 100)}%"></i></div>
|
||||
<span>${rainProb}% · ${rainMm} mm</span>
|
||||
</div>
|
||||
<footer class="day-card-meta">
|
||||
<span>💨 ${convertWind(wind)} · Böen ${convertWind(gusts)}</span>
|
||||
<span>☀️ ${sunrise} – ${sunset}</span>
|
||||
</footer>
|
||||
</article>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
@@ -419,28 +504,174 @@ async function loadWarnings() {
|
||||
}
|
||||
}
|
||||
|
||||
function setMapMode(mode) {
|
||||
mapMode = mode;
|
||||
document.querySelectorAll(".map-mode-btn").forEach((btn) => btn.classList.toggle("active", btn.dataset.mapMode === mode));
|
||||
document.querySelectorAll(".map-mode-radar-only").forEach((node) => node.classList.toggle("hidden", mode !== "radar"));
|
||||
document.querySelectorAll(".map-mode-forecast-only").forEach((node) => node.classList.toggle("hidden", mode !== "forecast"));
|
||||
el("timelineTitle").textContent = mode === "radar" ? "Radar-Zeitleiste" : "Vorhersage-Zeitleiste";
|
||||
el("timelineHint").textContent = mode === "radar"
|
||||
? "Vergangenheit · 5-Min-Schritte · DWD-Radar"
|
||||
: "Zukunft · stündlich · Open-Meteo-Modell (~25 km)";
|
||||
|
||||
if (!map) return;
|
||||
if (mode === "radar") {
|
||||
clearPrecipLayer();
|
||||
if (radarLayer && !map.hasLayer(radarLayer)) radarLayer.addTo(map);
|
||||
showRadarLatest();
|
||||
void loadRadarTimes();
|
||||
} else {
|
||||
if (radarLayer && map.hasLayer(radarLayer)) map.removeLayer(radarLayer);
|
||||
loadPrecipitationMap();
|
||||
}
|
||||
}
|
||||
|
||||
function precipColor(mm, prob) {
|
||||
const score = Math.max(Number(mm) || 0, ((Number(prob) || 0) / 100) * 2.5);
|
||||
if (score < 0.08) return null;
|
||||
if (score < 0.35) return "rgba(57, 120, 255, 0.38)";
|
||||
if (score < 1) return "rgba(57, 120, 255, 0.58)";
|
||||
if (score < 2.5) return "rgba(255, 81, 249, 0.55)";
|
||||
return "rgba(255, 40, 120, 0.72)";
|
||||
}
|
||||
|
||||
function clearPrecipLayer() {
|
||||
if (precipLayerGroup && map) {
|
||||
map.removeLayer(precipLayerGroup);
|
||||
precipLayerGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
function renderPrecipLayer(hourIndex) {
|
||||
if (!map || !precipData?.cells?.length) return;
|
||||
clearPrecipLayer();
|
||||
precipLayerGroup = L.layerGroup().addTo(map);
|
||||
const half = (precipData.step_deg || 0.25) / 2;
|
||||
precipData.cells.forEach((cell) => {
|
||||
const color = precipColor(cell.precipitation[hourIndex], cell.probability[hourIndex]);
|
||||
if (!color) return;
|
||||
L.rectangle([[cell.lat - half, cell.lon - half], [cell.lat + half, cell.lon + half]], {
|
||||
color: "transparent",
|
||||
fillColor: color,
|
||||
fillOpacity: 1,
|
||||
weight: 0,
|
||||
interactive: false,
|
||||
}).addTo(precipLayerGroup);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPrecipitationMap() {
|
||||
const loc = getLocation();
|
||||
const hours = settings.forecastHours || 24;
|
||||
el("mapMeta").textContent = "Lade Regenvorhersage…";
|
||||
try {
|
||||
const data = await api(`/api/forecast/precipitation-map?lat=${loc.lat}&lon=${loc.lon}&hours=${hours}`);
|
||||
precipData = data;
|
||||
radarTimes = data.times || [];
|
||||
el("radarSlider").max = Math.max(0, radarTimes.length - 1);
|
||||
el("mapMeta").textContent = `Regenvorhersage · ${data.cell_count} Felder · ${hours} h${data.cached ? " · Cache" : ""}`;
|
||||
if (radarTimes.length) {
|
||||
setRadarTime(0);
|
||||
el("radarFrameInfo").textContent = `${radarTimes.length} Stunden · Modell Open-Meteo`;
|
||||
} else {
|
||||
el("radarTimeLabel").textContent = "Keine Vorhersagedaten";
|
||||
}
|
||||
} catch (err) {
|
||||
el("mapMeta").textContent = `Vorhersage: ${err.message}`;
|
||||
el("radarTimeLabel").textContent = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
function applyRadarWmsTime(time, isLatest) {
|
||||
if (!radarLayer) return;
|
||||
if (isLatest) {
|
||||
delete radarLayer.wmsParams.TIME;
|
||||
delete radarLayer.wmsParams.time;
|
||||
} else {
|
||||
radarLayer.setParams({ TIME: time });
|
||||
}
|
||||
radarLayer.setParams({ _cache: Date.now() });
|
||||
if (radarLayer._map) radarLayer.redraw();
|
||||
}
|
||||
|
||||
function showRadarLatest() {
|
||||
if (!radarLayer || mapMode !== "radar" || !map) return;
|
||||
applyRadarWmsTime(null, true);
|
||||
if (radarTimes.length) {
|
||||
radarFrameIndex = radarTimes.length - 1;
|
||||
el("radarSlider").value = radarFrameIndex;
|
||||
el("radarTimeLabel").textContent = `Aktuell · ${formatDateTime(radarTimes[radarFrameIndex])}`;
|
||||
} else {
|
||||
el("radarTimeLabel").textContent = "Aktuell";
|
||||
el("radarFrameInfo").textContent = "Lade Verlauf…";
|
||||
}
|
||||
}
|
||||
|
||||
function setRadarTime(index) {
|
||||
if (!radarTimes.length) return;
|
||||
radarFrameIndex = Math.max(0, Math.min(index, radarTimes.length - 1));
|
||||
const time = radarTimes[radarFrameIndex];
|
||||
const isLatest = mapMode === "radar" && radarFrameIndex === radarTimes.length - 1;
|
||||
el("radarSlider").value = radarFrameIndex;
|
||||
el("radarTimeLabel").textContent = formatDateTime(time);
|
||||
el("radarFrameInfo").textContent = `Frame ${radarFrameIndex + 1} / ${radarTimes.length}`;
|
||||
el("radarTimeLabel").textContent = isLatest
|
||||
? `Aktuell · ${formatDateTime(time)}`
|
||||
: formatDateTime(time);
|
||||
el("radarFrameInfo").textContent = mapMode === "forecast"
|
||||
? `Stunde ${radarFrameIndex + 1} / ${radarTimes.length} · +${radarFrameIndex} h`
|
||||
: `Frame ${radarFrameIndex + 1} / ${radarTimes.length}`;
|
||||
|
||||
if (radarLayer) {
|
||||
radarLayer.setParams({ TIME: time, _cache: Date.now() });
|
||||
if (mapMode === "forecast") {
|
||||
renderPrecipLayer(radarFrameIndex);
|
||||
} else if (radarLayer) {
|
||||
applyRadarWmsTime(time, isLatest);
|
||||
}
|
||||
}
|
||||
|
||||
function applyRadarTimeline(data, { previousTime = null } = {}) {
|
||||
radarTimes = data.times || [];
|
||||
el("radarSlider").max = Math.max(0, radarTimes.length - 1);
|
||||
if (!radarTimes.length) {
|
||||
el("radarTimeLabel").textContent = "Keine Zeitschritte";
|
||||
return;
|
||||
}
|
||||
|
||||
if (previousTime) {
|
||||
const idx = radarTimes.indexOf(previousTime);
|
||||
if (idx >= 0) {
|
||||
setRadarTime(idx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
radarFrameIndex = radarTimes.length - 1;
|
||||
el("radarSlider").value = radarFrameIndex;
|
||||
el("radarTimeLabel").textContent = `Aktuell · ${formatDateTime(data.latest || radarTimes[radarFrameIndex])}`;
|
||||
el("radarFrameInfo").textContent = data.partial
|
||||
? `${radarTimes.length} Frames · Verlauf wird verfeinert…`
|
||||
: `${radarTimes.length} Frames · bis ${formatDateTime(data.latest)}`;
|
||||
applyRadarWmsTime(radarTimes[radarFrameIndex], true);
|
||||
}
|
||||
|
||||
async function loadRadarTimes() {
|
||||
if (mapMode !== "radar") return;
|
||||
const layer = radarLayer?.wmsParams?.layers || "dwd:Niederschlagsradar";
|
||||
const minutes = settings.radarMinutes || 120;
|
||||
const previousTime = radarTimes.length && radarFrameIndex < radarTimes.length - 1
|
||||
? radarTimes[radarFrameIndex]
|
||||
: null;
|
||||
|
||||
try {
|
||||
const data = await api(`/api/radar/wms/times?layer=${encodeURIComponent(layer)}&minutes=${minutes}`);
|
||||
radarTimes = data.times || [];
|
||||
el("radarSlider").max = Math.max(0, radarTimes.length - 1);
|
||||
if (radarTimes.length) setRadarTime(radarTimes.length - 1);
|
||||
else el("radarTimeLabel").textContent = "Keine Zeitschritte";
|
||||
const quickData = await api(
|
||||
`/api/radar/wms/times?layer=${encodeURIComponent(layer)}&minutes=${minutes}&quick=1`,
|
||||
);
|
||||
applyRadarTimeline(quickData, { previousTime });
|
||||
|
||||
const fullData = await api(
|
||||
`/api/radar/wms/times?layer=${encodeURIComponent(layer)}&minutes=${minutes}`,
|
||||
);
|
||||
applyRadarTimeline(fullData, {
|
||||
previousTime: radarFrameIndex < radarTimes.length - 1 ? radarTimes[radarFrameIndex] : null,
|
||||
});
|
||||
} catch (err) {
|
||||
el("radarTimeLabel").textContent = `Zeitleiste: ${err.message}`;
|
||||
}
|
||||
@@ -494,7 +725,8 @@ async function initMap() {
|
||||
});
|
||||
layerControl = L.control.layers({ "OpenStreetMap": osmLayer }, overlays, { collapsed: false }).addTo(map);
|
||||
el("mapMeta").textContent = "DWD Radar + Warnungen aktiv";
|
||||
await loadRadarTimes();
|
||||
showRadarLatest();
|
||||
void loadRadarTimes();
|
||||
} catch (err) {
|
||||
el("mapMeta").textContent = `Karte: ${err.message}`;
|
||||
}
|
||||
@@ -508,10 +740,15 @@ function updateMapLocation() {
|
||||
}
|
||||
|
||||
function reloadRadarLayers() {
|
||||
if (mapMode === "forecast") {
|
||||
loadPrecipitationMap();
|
||||
return;
|
||||
}
|
||||
wmsLayers.forEach((layer) => {
|
||||
if (map?.hasLayer(layer.instance)) layer.instance.setParams({ _cache: Date.now() });
|
||||
});
|
||||
loadRadarTimes();
|
||||
showRadarLatest();
|
||||
void loadRadarTimes();
|
||||
}
|
||||
|
||||
function toggleWarningsOnMap() {
|
||||
@@ -598,6 +835,7 @@ function applySettingsToUI() {
|
||||
el("unitTemp").value = settings.unitTemp;
|
||||
el("unitWind").value = settings.unitWind;
|
||||
el("radarMinutes").value = String(settings.radarMinutes);
|
||||
el("forecastHours").value = String(settings.forecastHours || 24);
|
||||
applyTheme();
|
||||
}
|
||||
|
||||
@@ -636,14 +874,12 @@ async function loadAll() {
|
||||
await loadDashboard();
|
||||
} catch {
|
||||
await Promise.allSettled([loadForecast(), loadObservations(), loadWarnings()]);
|
||||
const radar = await api("/api/radar/latest?limit=12").catch(() => null);
|
||||
if (radar) {
|
||||
el("radarCount").textContent = `${radar.count} online`;
|
||||
el("radarMeta").textContent = `Produkt ${radar.product.toUpperCase()} · DWD OpenData`;
|
||||
}
|
||||
}
|
||||
updateMapLocation();
|
||||
if (map) loadRadarTimes();
|
||||
if (map) {
|
||||
if (mapMode === "forecast") loadPrecipitationMap();
|
||||
else loadRadarTimes();
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
@@ -655,6 +891,9 @@ async function init() {
|
||||
}
|
||||
|
||||
document.querySelectorAll(".tab").forEach((tab) => tab.addEventListener("click", () => activateView(tab.dataset.view)));
|
||||
document.querySelectorAll(".map-mode-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", () => setMapMode(btn.dataset.mapMode));
|
||||
});
|
||||
el("loadBtn").addEventListener("click", loadAll);
|
||||
el("centerMapBtn").addEventListener("click", updateMapLocation);
|
||||
el("reloadRadarLayersBtn").addEventListener("click", reloadRadarLayers);
|
||||
@@ -684,20 +923,22 @@ el("themeToggle").addEventListener("click", () => {
|
||||
if (latestForecast?.data?.hourly) renderHourlyChart(latestForecast.data.hourly);
|
||||
});
|
||||
|
||||
["unitTemp", "unitWind", "radarMinutes"].forEach((id) => {
|
||||
["unitTemp", "unitWind", "radarMinutes", "forecastHours"].forEach((id) => {
|
||||
el(id).addEventListener("change", (e) => {
|
||||
const key = id === "radarMinutes" ? "radarMinutes" : id.replace("unit", "unit").replace("Temp", "Temp").replace("Wind", "Wind");
|
||||
if (id === "unitTemp") settings.unitTemp = e.target.value;
|
||||
if (id === "unitWind") settings.unitWind = e.target.value;
|
||||
if (id === "radarMinutes") settings.radarMinutes = Number(e.target.value);
|
||||
if (id === "forecastHours") settings.forecastHours = Number(e.target.value);
|
||||
saveSettings();
|
||||
if (latestForecast) {
|
||||
renderDaily(latestForecast.data.daily);
|
||||
renderHourly(latestForecast.data.hourly);
|
||||
renderHourStrip(latestForecast.data.hourly);
|
||||
renderForecastOutlook(latestForecast.data.hourly);
|
||||
loadForecast();
|
||||
}
|
||||
if (id === "radarMinutes" && map) loadRadarTimes();
|
||||
if (id === "radarMinutes" && map && mapMode === "radar") loadRadarTimes();
|
||||
if (id === "forecastHours" && map && mapMode === "forecast") loadPrecipitationMap();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user