Now I need to style it
This commit is contained in:
+11
-1
@@ -8,14 +8,24 @@ body {
|
|||||||
#weather-panel {
|
#weather-panel {
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
box-shadow: #999a 0 0 25px;
|
box-shadow: #999a 0 0 25px;
|
||||||
|
color: #fff;
|
||||||
font-family: Verdana, sans-serif;
|
font-family: Verdana, sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
left: calc(50% - 250px);
|
left: calc(50% - 250px);
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-shadow: #fff 0 0 5px, #fff 0 0 5px, #fff 0 0 5px, #fff 0 0 5px;
|
text-shadow: #000 1px 1px,
|
||||||
|
#000 -1px 1px,
|
||||||
|
#000 -1px -1px,
|
||||||
|
#000 1px -1px;
|
||||||
top: calc(50% - 250px);
|
top: calc(50% - 250px);
|
||||||
width: 500px;
|
width: 500px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
|
@supports (-webkit-text-stroke: 1px #000) {
|
||||||
|
-webkit-text-stroke: 1px #000;
|
||||||
|
-webkit-text-fill-color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+101
-99
@@ -1,4 +1,5 @@
|
|||||||
import { fetchWeatherApi } from 'openmeteo'
|
import { fetchWeatherApi } from "openmeteo"
|
||||||
|
import { VariablesWithTime } from "@openmeteo/sdk/variables-with-time"
|
||||||
|
|
||||||
export default class Weather {
|
export default class Weather {
|
||||||
private readonly apiUrl = "https://api.open-meteo.com/v1/forecast"
|
private readonly apiUrl = "https://api.open-meteo.com/v1/forecast"
|
||||||
@@ -15,113 +16,124 @@ export default class Weather {
|
|||||||
this.options = new WeatherOptions(latitude ?? 47.61002138071677, longitude ?? -122.17906310779568)
|
this.options = new WeatherOptions(latitude ?? 47.61002138071677, longitude ?? -122.17906310779568)
|
||||||
|
|
||||||
const me = this
|
const me = this
|
||||||
setInterval(() => me.fetchWeather().then(() => me.setDisplay()), 60 * 1000)
|
setInterval(() => me.fetchWeather().then(() => me.setDisplay()), 60000)
|
||||||
me.fetchWeather().then(() => me.setDisplay())
|
me.fetchWeather().then(() => me.setDisplay())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchWeather(): Promise<void> {
|
async fetchWeather(): Promise<void> {
|
||||||
const me = this
|
const me = this
|
||||||
const parameters: any = { ...me.options }
|
const parameters: any = { ...me.options }
|
||||||
let getData = false
|
if (!me.doFetchData(parameters)) return
|
||||||
|
|
||||||
|
const responses = await fetchWeatherApi(me.apiUrl, parameters)
|
||||||
|
for (const response of responses) {
|
||||||
|
const current = response.current()
|
||||||
|
const hourly = response.hourly()
|
||||||
|
const daily = response.daily()
|
||||||
|
|
||||||
|
if (current != null) me.setCurrentData(current)
|
||||||
|
if (hourly != null) me.setHourlyData(hourly)
|
||||||
|
if (daily != null) me.setDailyData(daily)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private doFetchData(parameters: any): boolean {
|
||||||
|
const me = this
|
||||||
if (!me.lastFetched) {
|
if (!me.lastFetched) {
|
||||||
parameters["daily"] = me.dailyVariables
|
parameters["daily"] = me.dailyVariables
|
||||||
parameters["hourly"] = me.hourlyVariables
|
parameters["hourly"] = me.hourlyVariables
|
||||||
parameters["current"] = me.currentVariables
|
parameters["current"] = me.currentVariables
|
||||||
me.lastFetched = new LastFetched()
|
me.lastFetched = new LastFetched()
|
||||||
getData = true
|
return true
|
||||||
} else {
|
} else {
|
||||||
// Reasonable-ish timers?
|
// Weather data is updated :00, :15, :30, :45 so wait at least a minute
|
||||||
if (me.lastFetched.minutesSinceCurrent() > 15) {
|
if (me.lastFetched.current.getMinutes() % 15 < 5) {
|
||||||
parameters["current"] = me.currentVariables
|
const now = new Date()
|
||||||
me.lastFetched.current = new Date()
|
// Make sure data is over 5 minutes old at least
|
||||||
getData = true
|
if ((now.getTime() - me.lastFetched.current.getTime()) > 300000) {
|
||||||
|
parameters["current"] = me.currentVariables
|
||||||
|
me.lastFetched.current = new Date()
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reasonable-ish timers?
|
||||||
if (me.lastFetched.hoursSinceHourly() > 3) {
|
if (me.lastFetched.hoursSinceHourly() > 3) {
|
||||||
parameters["hourly"] = me.hourlyVariables
|
parameters["hourly"] = me.hourlyVariables
|
||||||
me.lastFetched.hourly = new Date()
|
me.lastFetched.hourly = new Date()
|
||||||
getData = true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (me.lastFetched.hoursSinceDaily() > 8) {
|
if (me.lastFetched.hoursSinceDaily() > 8) {
|
||||||
parameters["daily"] = me.dailyVariables
|
parameters["daily"] = me.dailyVariables
|
||||||
me.lastFetched.daily = new Date()
|
me.lastFetched.daily = new Date()
|
||||||
getData = true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!getData) return
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const responses = await fetchWeatherApi(me.apiUrl, parameters)
|
private setCurrentData(current: VariablesWithTime) {
|
||||||
for (const response of responses) {
|
this.current = {
|
||||||
const utcOffsetSeconds = response.utcOffsetSeconds()
|
time: new Date(Number(current.time()) * 1000),
|
||||||
const current = response.current()
|
temperature: current.variables(0)!.value(),
|
||||||
const hourly = response.hourly()
|
humidity: current.variables(1)!.value(),
|
||||||
const daily = response.daily()
|
feelsLike: current.variables(2)!.value(),
|
||||||
|
precipitation: current.variables(3)!.value(),
|
||||||
|
windSpeed: current.variables(4)!.value(),
|
||||||
|
windGusts: current.variables(5)!.value(),
|
||||||
|
windDirection: current.variables(6)!.value(),
|
||||||
|
cloudCoverage: current.variables(7)!.value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (current != null) {
|
private setHourlyData(hourly: VariablesWithTime) {
|
||||||
me.current = {
|
const length = Number(hourly.timeEnd() - hourly.time()) / hourly.interval()
|
||||||
time: new Date(Number(current.time()) * 1000),
|
const temperatures = hourly.variables(0)!.valuesArray()
|
||||||
temperature: current.variables(0)!.value(),
|
const precipitationTotals = hourly.variables(1)!.valuesArray()
|
||||||
humidity: current.variables(1)!.value(),
|
const precipitationProbabilities = hourly.variables(2)!.valuesArray()
|
||||||
feelsLike: current.variables(2)!.value(),
|
this.hourly.length = 0
|
||||||
precipitation: current.variables(3)!.value(),
|
|
||||||
windSpeed: current.variables(4)!.value(),
|
|
||||||
windGusts: current.variables(5)!.value(),
|
|
||||||
windDirection: current.variables(6)!.value(),
|
|
||||||
cloudCoverage: current.variables(7)!.value()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hourly != null) {
|
for (let i = 0; i < length; i++) {
|
||||||
const length = Number(hourly.timeEnd() - hourly.time()) / hourly.interval()
|
this.hourly.push({
|
||||||
const temperatures = hourly.variables(0)!.valuesArray()
|
time: new Date((Number(hourly.time()) + i * hourly.interval()) * 1000),
|
||||||
const precipitationTotals = hourly.variables(1)!.valuesArray()
|
temperature: temperatures[i],
|
||||||
const precipitationProbabilities = hourly.variables(2)!.valuesArray()
|
precipitation: precipitationTotals[i],
|
||||||
me.hourly.length = 0
|
precipitationProbability: precipitationProbabilities[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
private setDailyData(daily: VariablesWithTime) {
|
||||||
me.hourly.push({
|
const length = Number(daily.timeEnd() - daily.time()) / daily.interval()
|
||||||
time: new Date((Number(hourly.time()) + i * hourly.interval() + utcOffsetSeconds) * 1000),
|
const maxTemperatures = daily.variables(0)!.valuesArray()
|
||||||
temperature: temperatures[i],
|
const minTemperatures = daily.variables(1)!.valuesArray()
|
||||||
precipitation: precipitationTotals[i],
|
const sunrise = daily.variables(2)
|
||||||
precipitationProbability: precipitationProbabilities[i]
|
const sunset = daily.variables(3)
|
||||||
})
|
const maxUVs = daily.variables(4)!.valuesArray()
|
||||||
}
|
const precipitationTotals = daily.variables(5)!.valuesArray()
|
||||||
}
|
const precipitationHours = daily.variables(6)!.valuesArray()
|
||||||
|
const maxWindSpeeds = daily.variables(7)!.valuesArray()
|
||||||
|
const maxWindGusts = daily.variables(8)!.valuesArray()
|
||||||
|
const prominentWindDirections = daily.variables(9)!.valuesArray()
|
||||||
|
|
||||||
if (daily != null) {
|
this.daily.length = 0
|
||||||
const length = Number(daily.timeEnd() - daily.time()) / daily.interval()
|
|
||||||
const maxTemperatures = daily.variables(0)!.valuesArray()
|
|
||||||
const minTemperatures = daily.variables(1)!.valuesArray()
|
|
||||||
const sunrise = daily.variables(2)
|
|
||||||
const sunset = daily.variables(3)
|
|
||||||
const maxUVs = daily.variables(4)!.valuesArray()
|
|
||||||
const precipitationTotals = daily.variables(5)!.valuesArray()
|
|
||||||
const precipitationHours = daily.variables(6)!.valuesArray()
|
|
||||||
const maxWindSpeeds = daily.variables(7)!.valuesArray()
|
|
||||||
const maxWindGusts = daily.variables(8)!.valuesArray()
|
|
||||||
const prominentWindDirections = daily.variables(9)!.valuesArray()
|
|
||||||
|
|
||||||
me.daily.length = 0
|
for (let i = 0; i < length; i++) {
|
||||||
|
this.daily.push({
|
||||||
for (let i = 0; i < length; i++) {
|
time: new Date((Number(daily.time()) + i * daily.interval()) * 1000),
|
||||||
me.daily.push({
|
maxTemperature: maxTemperatures[i],
|
||||||
time: new Date((Number(daily.time()) + i * daily.interval()) * 1000),
|
minTemperature: minTemperatures[i],
|
||||||
maxTemperature: maxTemperatures[i],
|
sunrise: new Date((Number(sunrise.valuesInt64(i))) * 1000),
|
||||||
minTemperature: minTemperatures[i],
|
sunset: new Date((Number(sunset.valuesInt64(i))) * 1000),
|
||||||
sunrise: new Date((Number(sunrise.valuesInt64(i))) * 1000),
|
maxUV: maxUVs[i],
|
||||||
sunset: new Date((Number(sunset.valuesInt64(i))) * 1000),
|
totalPrecipitation: precipitationTotals[i],
|
||||||
maxUV: maxUVs[i],
|
hoursOfPrecipitation: precipitationHours[i],
|
||||||
totalPrecipitation: precipitationTotals[i],
|
maxWindSpeed: maxWindSpeeds[i],
|
||||||
hoursOfPrecipitation: precipitationHours[i],
|
maxWindGusts: maxWindGusts[i],
|
||||||
maxWindSpeed: maxWindSpeeds[i],
|
prominentWindDirection: prominentWindDirections[i]
|
||||||
maxWindGusts: maxWindGusts[i],
|
})
|
||||||
prominentWindDirection: prominentWindDirections[i]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,31 +146,25 @@ export default class Weather {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while (panel.firstChild) panel.removeChild(panel.firstChild)
|
while (panel.firstChild) panel.removeChild(panel.firstChild)
|
||||||
|
this.setDisplayCurrent(panel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private setDisplayCurrent(panel: HTMLElement) {
|
||||||
const currentSection = document.createElement("section")
|
const currentSection = document.createElement("section")
|
||||||
currentSection.id = "current-section"
|
currentSection.id = "current-section"
|
||||||
panel.appendChild(currentSection)
|
panel.appendChild(currentSection)
|
||||||
this.setDisplayCurrent(currentSection)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setDisplayCurrent(section: HTMLElement) {
|
private InCardinals(direction: number): string {
|
||||||
const header = document.createElement("header")
|
if (direction > 337.5 || direction <= 22.5) return "N"
|
||||||
header.textContent = "Current"
|
if (direction > 22.5 || direction <= 67.5) return "NE"
|
||||||
|
if (direction > 67.5 || direction <= 112.5) return "E"
|
||||||
section.appendChild(header)
|
if (direction > 112.5 || direction <= 157.5) return "SE"
|
||||||
|
if (direction > 157.5 || direction <= 202.5) return "S"
|
||||||
const list = document.createElement("dl")
|
if (direction > 202.5 || direction <= 249.5) return "SW"
|
||||||
for (let key of Object.keys(this.current)) {
|
if (direction > 249.5 || direction <= 294.5) return "W"
|
||||||
const term = document.createElement("dt")
|
if (direction > 294.5 || direction <= 337.5) return "NW"
|
||||||
term.textContent = key
|
return null
|
||||||
|
|
||||||
const value = document.createElement("dd")
|
|
||||||
value.textContent = (this.current as any)[key]
|
|
||||||
|
|
||||||
list.append(term, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
section.appendChild(list)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -215,10 +221,6 @@ class LastFetched {
|
|||||||
hourly: Date = new Date()
|
hourly: Date = new Date()
|
||||||
daily: Date = new Date()
|
daily: Date = new Date()
|
||||||
|
|
||||||
minutesSinceCurrent(): number {
|
|
||||||
return (new Date().getTime() - this.current.getTime()) / 60000
|
|
||||||
}
|
|
||||||
|
|
||||||
hoursSinceHourly(): number {
|
hoursSinceHourly(): number {
|
||||||
return (new Date().getTime() - this.hourly.getTime()) / 3600000
|
return (new Date().getTime() - this.hourly.getTime()) / 3600000
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-1
@@ -9,13 +9,22 @@ html #weather-panel,
|
|||||||
body #weather-panel {
|
body #weather-panel {
|
||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
box-shadow: #999a 0 0 25px;
|
box-shadow: #999a 0 0 25px;
|
||||||
|
color: #fff;
|
||||||
font-family: Verdana, sans-serif;
|
font-family: Verdana, sans-serif;
|
||||||
|
font-weight: bold;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
left: calc(50% - 250px);
|
left: calc(50% - 250px);
|
||||||
padding: 40px;
|
padding: 40px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-shadow: #fff 0 0 5px, #fff 0 0 5px, #fff 0 0 5px, #fff 0 0 5px;
|
text-shadow: #000 1px 1px, #000 -1px 1px, #000 -1px -1px, #000 1px -1px;
|
||||||
top: calc(50% - 250px);
|
top: calc(50% - 250px);
|
||||||
width: 500px;
|
width: 500px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
@supports (-webkit-text-stroke: 1px #000) {
|
||||||
|
html #weather-panel,
|
||||||
|
body #weather-panel {
|
||||||
|
-webkit-text-stroke: 1px #000;
|
||||||
|
-webkit-text-fill-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user