Add frontend

This commit is contained in:
Gal 2024-04-06 09:57:13 +02:00
parent 862bb3c9e1
commit f9f1153299
Signed by: gal
GPG Key ID: F035BC65003BC00B
2 changed files with 395 additions and 65 deletions

View File

@ -4,7 +4,8 @@ body {
}
.weather-container {
width: 700px;
width: 800px;
max-height: 480px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
@ -96,3 +97,109 @@ h2 {
font-size: 16px;
color: #333;
}
html {
background-color: #f9f9f9;
}
body {
padding: 35px;
max-width: 800px;
height: 480px;
margin: auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header-left {
flex: 1;
text-align: left;
padding: 0 2rem;
}
.card-header-right {
flex: 1;
text-align: right;
}
.is-vertical-center {
display: flex;
align-items: center;
}
.params {
width: 100%;
margin: 25px auto;
}
.params .card {
border-radius: 20px;
}
.weather .media {
padding: 0 2rem;
}
.weather figure {
font-size: 6rem;
margin-bottom: -2rem;
}
.weather-details {
font-size: 0.9rem;
}
.weather-details i {
text-align: center;
width: 20px;
margin-right: 7px;
}
.weather .level-item i {
margin: .4rem 0;
font-size: 2rem;
}
.alerts-details {
font-size: 0.8rem;
}
.alerts-details i {
text-align: center;
width: 20px;
margin-right: 7px;
}
.alerts .level-item i {
margin: .4rem 0;
font-size: 2rem;
}
#no-weather-info {
top: 48px;
background-image: repeating-linear-gradient(-45deg,
transparent,
transparent 20px,
rgba(0, 0, 0, .2) 20px,
rgba(0, 0, 0, .2) 40px);
}
#no-weather-info .notification {
margin: 0 auto;
border-radius: 10px;
padding: 20px 40px;
}
#no-alerts-info {
top: 48px;
}
#no-alerts-info .notification {
margin: 0 auto;
border-radius: 10px;
padding: 20px 40px;
}

View File

@ -1,3 +1,4 @@
<!-- Credit https://github.com/jdemaeyer/brightsky/blob/master/docs/demo/alerts/index.html */ -->
<!DOCTYPE html>
<html lang="en">
<head>
@ -5,76 +6,298 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weather Dashboard</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css" type="text/css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/weather-icons/2.0.10/css/weather-icons.min.css" type="text/css">
<script type="module">
const BRIGHTSKY_URL = 'https://api.brightsky.dev/';
// Berlin
const LOCATION_LAT=52.52;
const LOCATION_LONG=13.4;
function dailyTotal(weather, key) {
return weather.reduce((total, record) => total + (record[key] || 0), 0.);
}
function dailyAvg(weather, key) {
return dailyTotal(weather, key) / weather.length;
}
function dailyMax(weather, key) {
return weather.reduce((max, record) => record[key] >= (max || record[key]) ? record[key] : max, null);
}
function dailyMin(weather, key) {
return weather.reduce((min, record) => record[key] <= (min || record[key]) ? record[key] : min, null);
}
function mode(arr) {
return arr.sort((a,b) =>
arr.filter(v => v===a).length
- arr.filter(v => v===b).length
).pop();
}
function setProperty(id, value) {
const el = document.getElementById(`weather-${id}`);
el.innerHTML = value;
}
function setPropertyAlert(id, value) {
const el = document.getElementById(`alerts-${id}`);
el.innerHTML = value;
}
function getTodayDate() {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0'); // Months are zero-based
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function mockAlertsData() {
return new Promise((resolve) => {
const mockedData = {
"alerts": [
{
"id": 279977,
"alert_id": "2.49.0.0.276.0.DWD.PVW.1691344680000.2cf9fad6-dc83-44ba-9e88-f2827439da59",
"effective": "2023-08-06T17:58:00+00:00",
"onset": "2023-08-07T08:00:00+00:00",
"expires": "2023-08-07T19:00:00+00:00",
"category": "met",
"response_type": "prepare",
"urgency": "immediate",
"severity": "minor",
"certainty": "likely",
"event_code": 51,
"event_en": "wind gusts",
"event_de": "WINDBÖEN",
"headline_en": "Official WARNING of WIND GUSTS",
"headline_de": "Amtliche WARNUNG vor WINDBÖEN",
"description_en": "There is a risk of wind gusts (level 1 of 4).\nMax. gusts: 50-60 km/h; Wind direction: west; Increased gusts: near showers and in exposed locations < 70 km/h",
"description_de": "Es treten Windböen mit Geschwindigkeiten zwischen 50 km/h (14 m/s, 28 kn, Bft 7) und 60 km/h (17 m/s, 33 kn, Bft 7) aus westlicher Richtung auf. In Schauernähe sowie in exponierten Lagen muss mit Sturmböen bis 70 km/h (20 m/s, 38 kn, Bft 8) gerechnet werden.",
"instruction_en": null,
"instruction_de": null
}
],
"location": {
"warn_cell_id": 705515101,
"name": "Münster-Nord",
"name_short": "Münster-Nord",
"district": "Münster",
"state": "Nordrhein-Westfalen",
"state_short": "NW"
}
};
resolve(mockedData);
});
}
async function getWeather() {
const lat = LOCATION_LAT;
const lon = LOCATION_LONG;
const date = new Date(getTodayDate());
const data = await fetchWeather(lat, lon, date);
if (data.weather && data.weather.length > 0) {
updateSummary(data);
updateHourly(data);
}
}
async function fetchWeather(lat, lon, date) {
const dateStr = date.toISOString().split('T')[0];
const url = BRIGHTSKY_URL + `weather?lat=${lat}&lon=${lon}&date=${dateStr}&tz=Europe/Berlin`;
const response = await fetch(url);
const data = await response.json();
return data;
}
async function getAlerts() {
const lat = LOCATION_LAT;
const lon = LOCATION_LONG;
const data = await fetchAlerts(lat, lon);
//const data = await mockAlertsData();
if (data.alerts && data.alerts.length > 0) {
document.getElementById('no-alerts-info').classList.add('is-hidden');
const event = data.alerts[0].event_de;
const alertHeadline = data.alerts[0].headline_de;
const alertDescription = data.alerts[0].description_de;
setPropertyAlert('headline', alertHeadline);
setPropertyAlert('description', alertDescription);
document.getElementById('alert-icon').className = `wi ${ ALERT_ICON_MAPPING[event] }`;
} else {
document.getElementById('no-alerts-info');
document.getElementById('alerts-info').classList.add('is-hidden');
}
}
async function fetchAlerts(lat, lon) {
const url = BRIGHTSKY_URL + `alerts?lat=${lat}&lon=${lon}&tz=Europe/Berlin`;
const response = await fetch(url);
const data = await response.json();
return data;
}
const ICON_MAPPING = {
'clear-day': 'wi-day-sunny',
'clear-night': 'wi-night-clear',
'partly-cloudy-day': 'wi-day-cloudy',
'partly-cloudy-night': 'wi-night-cloudy',
'cloudy': 'wi-cloud',
'fog': 'wi-fog',
'wind': 'wi-strong-wind',
'rain': 'wi-rain',
'sleet': 'wi-sleet',
'snow': 'wi-snow',
'hail': 'wi-hail',
'thunderstorm': 'wi-thunderstorm',
}
const ALERT_ICON_MAPPING = {
'FROST': 'wi-snow',
'WINDBÖEN': 'wi-strong-wind',
}
function updateTodayDate() {
const today = new Date();
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
const formattedDate = today.toLocaleDateString('en-US', options);
document.getElementById('today-date').textContent = formattedDate;
}
function updateSummary(data) {
const sourceName = data.sources[0].station_name.replace(
/\B\w/g,
(txt) => txt.charAt(0).toLowerCase(),
);
const maxTemp = dailyMax(data.weather, 'temperature');
const minTemp = dailyMin(data.weather, 'temperature');
const maxPrecipitation = dailyMax(data.weather, 'precipitation');
const avgWindSpeed = dailyAvg(data.weather, 'wind_speed');
const totalSunshine = dailyTotal(data.weather, 'sunshine');
const totalSunshineHours = Math.floor(totalSunshine / 60);
const totalSunshineMinutes = Math.floor(totalSunshine - 60 * totalSunshineHours);
const avgCloudCover = dailyAvg(data.weather, 'cloud_cover');
const avgPressure = dailyAvg(data.weather, 'pressure_msl');
setProperty('station-name', sourceName);
setProperty('max-temp', maxTemp.toFixed(1));
setProperty('min-temp', minTemp.toFixed(1));
setProperty('max-precipitation', maxPrecipitation.toFixed(1));
setProperty('avg-wind-speed', avgWindSpeed.toFixed(1));
setProperty('total-sunshine', `${totalSunshineHours}:${(totalSunshineMinutes < 10 ? '0' : '') + totalSunshineMinutes}`);
setProperty('avg-cloud-cover', avgCloudCover.toFixed(0));
setProperty('avg-pressure', avgPressure.toFixed(1));
const icons = data.weather.map((record) => record.icon.replace('-night', '-day'));
document.getElementById('weather-day-icon').className = `wi ${ ICON_MAPPING[mode(icons)] }`;
}
function updateHourly(data) {
var hourly = '';
for (let i = 4; i < data.weather.length - 1; i += 4) {
var weather = data.weather[i];
var timestamp = new Date(weather.timestamp).toLocaleTimeString('en-us', {hour12: false, hour: 'numeric', minute: 'numeric'});
hourly += `
<div class="level-item has-text-centered">
<div>
<p class="heading">${ timestamp }</p>
<p>
<i class="wi ${ ICON_MAPPING[weather.icon] }"></i><br>
${ weather.temperature.toFixed(0) } °C
</p>
</div>
</div>
`;
}
setProperty('hourly', hourly);
}
getWeather();
getAlerts();
updateTodayDate();
</script>
</head>
<body>
<div class="weather-container">
<h1>Berlin &dot; 13.12.2024 &dot; 09:00</h1>
<div class="current-weather">
<div class="weather-icon">
<header class="card-header has-background-link-light">
<div class="card-header-left">
<p class="card-header-title has-text-grey" style="display: block">
<span id="today-date">Monday, 27 July 2020</span>
</p>
</div>
</header>
<div id="weather" class="card weather">
<div class="card-content">
<div id="no-weather-info" class="is-overlay is-vertical-center is-hidden">
<div class="notification is-warning has-text-centered">
No data available.
</div>
</div>
<div class="media">
<div class="media-content">
<p class="title is-3">
<span title="Maximum temperature"><span id="weather-max-temp">19.3</span> °C</span>
<span class="has-text-grey-lighter">· <span title="Minimum temperature"><span id="weather-min-temp">14.3</span> °C</span></span>
</p>
<p class="subtitle is-5" title="Weather station name"><span id="weather-station-name">Münster/Osnabrück</span></p>
<div class="columns">
<div class="column">
<div class="weather-details has-text-grey">
<span title="Maximum hourly precipitation">
<i class="wi wi-umbrella"></i><span id="weather-max-precipitation">0.4</span> mm/h
</span><br>
<span title="Average wind speed">
<i class="wi wi-strong-wind"></i><span id="weather-avg-wind-speed">9</span> km/h
</span><br>
<span title="Total sunshine duration">
<i class="wi wi-day-sunny"></i><span id="weather-total-sunshine">6:49</span> h
</span><br>
</div>
</div>
<div class="column">
<div class="weather-details has-text-grey">
<span title="Average cloud cover">
<i class="wi wi-cloudy"></i><span id="weather-avg-cloud-cover">72</span> %
</span><br>
<span title="Average atmospheric pressure, reduced to mean sea level">
<i class="wi wi-barometer"></i><span id="weather-avg-pressure">1022.3</span> hPa
</span><br>
</div>
</div>
</div>
</div>
<div class="temperature">
23°C
</div>
</div>
<div class="additional-info">
<div class="info-item">
<div class="info-label">Wind Gust</div>
<div class="info-value">10 km/h</div>
</div>
<div class="info-item">
<div class="info-label">Sunrise</div>
<div class="info-value">06:30 AM</div>
</div>
<div class="info-item">
<div class="info-label">Sunset</div>
<div class="info-value">04:30 PM</div>
</div>
<div class="info-item">
<div class="info-label">Precipitation Rate</div>
<div class="info-value">20%</div>
</div>
</div>
<h2>Today's Forecast</h2>
<div class="forecast">
<div class="forecast-item">
<div class="forecast-time">09:00</div>
<div class="forecast-icon">
</div>
<div class="forecast-temperature">20°C</div>
</div>
<div class="forecast-item">
<div class="forecast-time">11:00</div>
<div class="forecast-icon">
</div>
<div class="forecast-temperature">18°C</div>
</div>
<div class="forecast-item">
<div class="forecast-time">13:00</div>
<div class="forecast-icon">
</div>
<div class="forecast-temperature">18°C</div>
</div>
<div class="forecast-item">
<div class="forecast-time">15:00</div>
<div class="forecast-icon">
</div>
<div class="forecast-temperature">18°C</div>
</div>
<div class="forecast-item">
<div class="forecast-time">17:00</div>
<div class="forecast-icon">
</div>
<div class="forecast-temperature">18°C</div>
</div>
<div class="forecast-item">
<div class="forecast-time">19:00</div>
<div class="forecast-icon">
</div>
<div class="forecast-temperature">18°C</div>
<div class="media-right" style="margin: auto 0">
<figure>
<i id="weather-day-icon" class="wi wi-day-cloudy"></i>
</figure>
</div>
</div>
<div id="weather-hourly" class="level is-mobile"></div>
<div id="no-alerts-info" class="level is-mobile">
<div class="notification is-success has-text-centered">
No weather alerts! :)
</div>
</div>
<div id ="alerts-info" class="alerts-details has-text-grey box">
<span title="Alert Headline">
<i id="alert-icon" class="wi wi-cloudy"></i><span id="alerts-headline" class="has-text-weight-bold">No warning</span>:
</span>
<span title="Alert Description">
<span id="alerts-description">No description</span>
</span><br>
</div>
</div>
</div>
</div>
</body>
</html>