Skip to content

Commit 50a06f9

Browse files
committed
add daily weather forecast
1 parent 1c12193 commit 50a06f9

File tree

4 files changed

+123
-22
lines changed

4 files changed

+123
-22
lines changed

src/lib/components/Settings.svelte

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,23 @@
327327
</div>
328328
{/if}
329329

330+
<div class="group">
331+
<div class="setting-label">weather forecast</div>
332+
<div class="radio-group">
333+
<RadioButton
334+
bind:group={settings.forecastMode}
335+
value="hourly"
336+
>
337+
hourly
338+
</RadioButton>
339+
<RadioButton
340+
bind:group={settings.forecastMode}
341+
value="daily"
342+
>
343+
daily
344+
</RadioButton>
345+
</div>
346+
</div>
330347
<div class="group">
331348
<div class="setting-label">weather location</div>
332349
<div class="radio-group">

src/lib/components/Weather.svelte

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
let loading = $state(false)
1111
let error = $state(null)
1212
let initialLoad = $state(true)
13+
let prevForecastMode = $state(settings.forecastMode)
1314
1415
const weatherAPI = new WeatherAPI()
1516
@@ -26,12 +27,20 @@
2627
const tempUnit = settings.tempUnit
2728
const speedUnit = settings.speedUnit
2829
const timeFormat = settings.timeFormat
30+
const forecastMode = settings.forecastMode
2931
3032
if (untrack(() => initialLoad)) {
3133
initialLoad = false
34+
prevForecastMode = forecastMode
3235
return
3336
}
3437
38+
// Clear cache if forecast mode changed
39+
if (untrack(() => prevForecastMode) !== forecastMode) {
40+
prevForecastMode = forecastMode
41+
weatherAPI.clearCache()
42+
}
43+
3544
refreshWeather()
3645
})
3746
@@ -88,7 +97,8 @@
8897
const cached = weatherAPI.getCachedWeather(
8998
settings.timeFormat,
9099
lat,
91-
lon
100+
lon,
101+
settings.forecastMode
92102
)
93103
if (cached.data) {
94104
current = cached.data.current
@@ -109,7 +119,8 @@
109119
lon,
110120
settings.tempUnit,
111121
settings.speedUnit,
112-
settings.timeFormat
122+
settings.timeFormat,
123+
settings.forecastMode
113124
)
114125
115126
current = data.current
@@ -186,7 +197,13 @@
186197
</div>
187198
<div class="col">
188199
{#each forecast as forecast}
189-
<div class="forecast-temp">{forecast.temperature}°</div>
200+
{#if settings.forecastMode === 'daily'}
201+
<div class="forecast-temp">
202+
{forecast.temperatureMax}° <span class="separator">/</span> {forecast.temperatureMin
203+
</div>
204+
{:else}
205+
<div class="forecast-temp">{forecast.temperature}°</div>
206+
{/if}
190207
{/each}
191208
</div>
192209
<div class="col">
@@ -233,6 +250,9 @@
233250
text-align: end;
234251
color: var(--txt-1);
235252
}
253+
.forecast-temp .separator {
254+
color: var(--txt-3);
255+
}
236256
.forecast-weather {
237257
color: var(--txt-3);
238258
}

src/lib/settings-store.svelte.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ let defaultSettings = {
1212
dateFormat: 'mdy',
1313
tempUnit: 'fahrenheit',
1414
speedUnit: 'mph',
15+
forecastMode: 'hourly',
1516
linksPerColumn: 4,
1617
linkTarget: '_self',
1718
links: [

src/lib/weather-api.js

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import descriptions from '../assets/descriptions.json'
66
class WeatherAPI {
77
constructor() {
88
this.baseUrl = 'https://api.open-meteo.com/v1/forecast'
9-
this.cacheKey = `weather_data`
9+
this.cacheKey = 'weather_data'
1010
this.cacheExpiry = 15 * 60 * 1000
1111
}
1212

@@ -18,43 +18,60 @@ class WeatherAPI {
1818
longitude,
1919
tempUnit,
2020
speedUnit,
21-
timeFormat = '12hr'
21+
timeFormat = '12hr',
22+
forecastMode = 'hourly'
2223
) {
2324
const rawData = await this._fetchWeatherData(
2425
latitude,
2526
longitude,
2627
tempUnit,
27-
speedUnit
28+
speedUnit,
29+
forecastMode
2830
)
2931
this._cacheWeather(rawData, latitude, longitude)
3032

33+
const forecast =
34+
forecastMode === 'daily'
35+
? this._processDailyForecast(rawData.daily, timeFormat)
36+
: this._processHourlyForecast(
37+
rawData.hourly,
38+
rawData.current.time,
39+
timeFormat
40+
)
41+
3142
return {
3243
current: this._processCurrentWeather(rawData.current),
33-
forecast: this._processHourlyForecast(
34-
rawData.hourly,
35-
rawData.current.time,
36-
timeFormat
37-
),
44+
forecast,
3845
}
3946
}
4047

4148
/**
4249
* Get cached weather data with staleness info
4350
*/
44-
getCachedWeather(timeFormat = '12hr', latitude = null, longitude = null) {
51+
getCachedWeather(
52+
timeFormat = '12hr',
53+
latitude = null,
54+
longitude = null,
55+
forecastMode = 'hourly'
56+
) {
4557
const cached = this._getCachedData(latitude, longitude)
4658

4759
if (!cached.data) {
4860
return { data: null, isStale: false }
4961
}
5062

63+
const forecast =
64+
forecastMode === 'daily'
65+
? this._processDailyForecast(cached.data.daily, timeFormat)
66+
: this._processHourlyForecast(
67+
cached.data.hourly,
68+
cached.data.current.time,
69+
timeFormat
70+
)
71+
5172
const processedData = {
5273
current: this._processCurrentWeather(cached.data.current),
53-
forecast: this._processHourlyForecast(
54-
cached.data.hourly,
55-
cached.data.current.time,
56-
timeFormat
57-
),
74+
forecast,
5875
}
5976

6077
return {
@@ -125,18 +142,32 @@ class WeatherAPI {
125142
/**
126143
* Private method to fetch raw weather data from API
127144
*/
128-
async _fetchWeatherData(latitude, longitude, tempUnit, speedUnit) {
129-
const params = new URLSearchParams({
145+
async _fetchWeatherData(
146+
latitude,
147+
longitude,
148+
tempUnit,
149+
speedUnit,
150+
forecastMode = 'hourly'
151+
) {
152+
const baseParams = {
130153
latitude: latitude.toString(),
131154
longitude: longitude.toString(),
132-
hourly: 'temperature_2m,weather_code,is_day',
133155
current:
134156
'temperature_2m,weather_code,relative_humidity_2m,precipitation_probability,wind_speed_10m,apparent_temperature,is_day',
135157
timezone: 'auto',
136-
forecast_hours: '24',
137158
temperature_unit: tempUnit,
138159
wind_speed_unit: speedUnit,
139-
})
160+
}
161+
162+
if (forecastMode === 'daily') {
163+
baseParams.daily = 'weather_code,temperature_2m_max,temperature_2m_min'
164+
baseParams.forecast_days = '7'
165+
} else {
166+
baseParams.hourly = 'temperature_2m,weather_code,is_day'
167+
baseParams.forecast_hours = '24'
168+
}
169+
170+
const params = new URLSearchParams(baseParams)
140171

141172
const response = await fetch(`${this.baseUrl}?${params}`)
142173
if (!response.ok) {
@@ -204,6 +235,30 @@ class WeatherAPI {
204235
return forecasts
205236
}
206237

238+
/**
239+
* Process daily forecast to get next 5 days
240+
*/
241+
_processDailyForecast(dailyData, timeFormat = '12hr') {
242+
const forecasts = []
243+
244+
// Get forecasts for the next 5 days (skip today, get days 1-5)
245+
for (let i = 1; i <= 5 && i < dailyData.time.length; i++) {
246+
forecasts.push({
247+
time: dailyData.time[i],
248+
temperatureMax: dailyData.temperature_2m_max[i].toFixed(0),
249+
temperatureMin: dailyData.temperature_2m_min[i].toFixed(0),
250+
weatherCode: dailyData.weather_code[i],
251+
description: this._getWeatherDescription(
252+
dailyData.weather_code[i],
253+
true
254+
),
255+
formattedTime: this._formatDate(dailyData.time[i]),
256+
})
257+
}
258+
259+
return forecasts
260+
}
261+
207262
/**
208263
* Get weather description from code
209264
*/
@@ -238,6 +293,14 @@ class WeatherAPI {
238293
}
239294
}
240295

296+
/**
297+
* Format date to display day name (e.g., "Mon", "Tue")
298+
*/
299+
_formatDate(dateString) {
300+
const date = new Date(dateString + 'T00:00:00')
301+
return date.toLocaleDateString('en-US', { weekday: 'short' }).toLowerCase()
302+
}
303+
241304
/**
242305
* Cache weather data with timestamp and coordinates
243306
*/

0 commit comments

Comments
 (0)