Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
# macOS metadata
._*
.DS_Store
130 changes: 123 additions & 7 deletions Weather.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ import { css } from 'uebersicht'

/***********************Options***********************/
// Open Weather API Key https://openweathermap.org/api
// IMPORTANT: This widget uses the 5 Day / 3 Hour Forecast API (FREE tier):
// - Provides forecasts every 3 hours for 5 days (40 data points)
// - Current weather is derived from the first forecast item
// - Hourly forecasts are taken from the 3-hour intervals
// - Daily forecasts are aggregated from the 3-hour data
// Documentation: https://openweathermap.org/api/forecast5
const KEY = ''
const LANG = 'zh' // language: en for English, zh for Chinese
const LANG = 'en' // language: en for English, zh for Chinese
// weather data units
// standard(default), metric and imperial units are available.
const UNITS = 'metric'
Expand Down Expand Up @@ -46,6 +52,100 @@ export const initialState = {
showMore: false
}

// Transform 5 Day Forecast API response to match widget's expected structure
const transformForecastData = (apiData) => {
if (!apiData || !apiData.list || apiData.list.length === 0) {
throw new Error('Invalid API response: missing list data')
}

// Current weather is the first item in the list
const firstItem = apiData.list[0]
const current = {
dt: firstItem.dt,
temp: firstItem.main.temp,
feels_like: firstItem.main.feels_like,
weather: firstItem.weather,
main: firstItem.main,
clouds: firstItem.clouds,
wind: firstItem.wind,
visibility: firstItem.visibility,
sys: firstItem.sys,
dt_txt: firstItem.dt_txt
}

// Hourly forecasts - take first 6 items (18 hours of 3-hour intervals)
const hourly = apiData.list.slice(0, 6).map(item => ({
dt: item.dt,
temp: item.main.temp,
weather: item.weather,
pop: item.pop || 0,
main: item.main,
clouds: item.clouds,
wind: item.wind,
visibility: item.visibility,
sys: item.sys,
dt_txt: item.dt_txt
}))

// Aggregate 3-hour forecasts into daily summaries
const daily = aggregateToDaily(apiData.list)

return {
current,
hourly,
daily
}
}

// Aggregate 3-hour forecasts into daily summaries
const aggregateToDaily = (forecastList) => {
const dailyMap = new Map()

forecastList.forEach((item) => {
const date = new Date(item.dt * 1000)
const dateKey = date.toDateString() // e.g., "Mon Jan 01 2024"

if (!dailyMap.has(dateKey)) {
dailyMap.set(dateKey, {
dt: item.dt,
temp: {
min: item.main.temp_min,
max: item.main.temp_max
},
weather: item.weather,
items: []
})
}

const dayData = dailyMap.get(dateKey)
dayData.items.push(item)

// Update min/max temperatures
if (item.main.temp_min < dayData.temp.min) {
dayData.temp.min = item.main.temp_min
}
if (item.main.temp_max > dayData.temp.max) {
dayData.temp.max = item.main.temp_max
}

// Use the weather from the middle of the day (around noon) for better representation
const hour = date.getHours()
if (hour >= 11 && hour <= 13) {
dayData.weather = item.weather
dayData.dt = item.dt // Update to noon timestamp
}
})

// Convert map to array and take first 7 days
return Array.from(dailyMap.values())
.slice(0, 7)
.map(day => ({
dt: day.dt,
temp: day.temp,
weather: day.weather
}))
}

const WEEK_DAY = {
en: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
zh: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
Expand Down Expand Up @@ -118,21 +218,37 @@ export const command = (dispatch) => {
const lon = geo.position.coords.longitude
const proxy = 'http://127.0.0.1:41417'
const server = 'https://api.openweathermap.org'
const path = `/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=minutely&appid=${KEY}&units=${UNITS}&lang=${LANG}`

// Using 5 Day / 3 Hour Forecast API (FREE tier)
// Provides forecasts every 3 hours for 5 days (40 data points)
const path = `/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${KEY}&units=${UNITS}&lang=${LANG}`

fetch(`${proxy}/${server}${path}`)
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
return response.json()
})
.catch((error) => {
return dispatch({ type: 'FETCH_FAILED', error: error })
})
.then((data) => {
.then((apiData) => {
// Check if API returned an error
if (apiData.cod && apiData.cod !== '200' && apiData.cod !== 200) {
throw new Error(apiData.message || 'API error')
}

// Transform 5 Day Forecast API response to match widget's expected structure
const transformedData = transformForecastData(apiData)

return dispatch({
type: 'FETCH_SUCCEDED',
data: data,
data: transformedData,
city: address
})
})
.catch((error) => {
console.error('Weather API error:', error)
return dispatch({ type: 'FETCH_FAILED', error: error.message || error })
})
},
(error) => {
return dispatch({ type: 'GEO_FAILED', error: error })
Expand Down