/** * weather.js - A module for weather-related functionality * Provides real-time weather data and sunset/sunrise information * Uses OpenWeatherMap API for weather data */ class WeatherManager { constructor(options = {}) { // Default options this.options = { latitude: 59.3293, // Stockholm latitude longitude: 18.0686, // Stockholm longitude apiKey: options.apiKey || '4d8fb5b93d4af21d66a2948710284366', // OpenWeatherMap API key refreshInterval: 30 * 60 * 1000, // 30 minutes in milliseconds ...options }; // State this.weatherData = null; this.forecastData = null; this.sunTimes = null; this.isDarkMode = false; this.lastUpdated = null; // Initialize this.init(); } /** * Initialize the weather manager */ async init() { try { // Fetch weather data await this.fetchWeatherData(); // Check if it's dark outside (only affects auto mode) this.updateDarkModeBasedOnTime(); // Set up interval to check dark mode every minute (only affects auto mode) this.darkModeCheckInterval = setInterval(() => { // Only update dark mode based on time if ConfigManager has dark mode set to 'auto' if (this.shouldUseAutoDarkMode()) { this.updateDarkModeBasedOnTime(); } }, 60000); // Set up interval to refresh weather data setInterval(() => this.fetchWeatherData(), this.options.refreshInterval); // Dispatch initial dark mode state this.dispatchDarkModeEvent(); console.log('WeatherManager initialized'); } catch (error) { console.error('Error initializing WeatherManager:', error); // Fallback to calculated sun times if API fails await this.updateSunTimesFromCalculation(); this.updateDarkModeBasedOnTime(); this.dispatchDarkModeEvent(); } } /** * Check if we should use automatic dark mode based on ConfigManager settings */ shouldUseAutoDarkMode() { // If there's a ConfigManager instance with a config if (window.configManager && window.configManager.config) { // Only use auto dark mode if the setting is 'auto' return window.configManager.config.darkMode === 'auto'; } // Default to true if no ConfigManager is available return true; } /** * Fetch weather data from OpenWeatherMap API */ async fetchWeatherData() { try { // Fetch current weather const currentWeatherUrl = `https://api.openweathermap.org/data/2.5/weather?lat=${this.options.latitude}&lon=${this.options.longitude}&units=metric&appid=${this.options.apiKey}`; const currentWeatherResponse = await fetch(currentWeatherUrl); const currentWeatherData = await currentWeatherResponse.json(); if (currentWeatherData.cod !== 200) { throw new Error(`API Error: ${currentWeatherData.message}`); } // Fetch hourly forecast const forecastUrl = `https://api.openweathermap.org/data/2.5/forecast?lat=${this.options.latitude}&lon=${this.options.longitude}&units=metric&appid=${this.options.apiKey}`; const forecastResponse = await fetch(forecastUrl); const forecastData = await forecastResponse.json(); if (forecastData.cod !== "200") { throw new Error(`API Error: ${forecastData.message}`); } // Process and store the data this.weatherData = this.processCurrentWeather(currentWeatherData); this.forecastData = this.processForecast(forecastData); this.lastUpdated = new Date(); // Extract sunrise and sunset times from the API response this.updateSunTimesFromApi(currentWeatherData); // Update the UI with the new data this.updateWeatherUI(); console.log('Weather data updated:', this.weatherData); return this.weatherData; } catch (error) { console.error('Error fetching weather data:', error); // If we don't have any weather data yet, create some default data if (!this.weatherData) { this.weatherData = this.createDefaultWeatherData(); this.forecastData = this.createDefaultForecastData(); } // Fallback to calculated sun times await this.updateSunTimesFromCalculation(); return this.weatherData; } } /** * Process current weather data from API response */ processCurrentWeather(data) { return { temperature: Math.round(data.main.temp * 10) / 10, // Round to 1 decimal place condition: data.weather[0].main, description: data.weather[0].description, icon: this.getWeatherIconUrl(data.weather[0].icon), wind: { speed: Math.round(data.wind.speed * 3.6), // Convert m/s to km/h direction: data.wind.deg }, humidity: data.main.humidity, pressure: data.main.pressure, precipitation: data.rain ? (data.rain['1h'] || 0) : 0, location: data.name, country: data.sys.country, timestamp: new Date(data.dt * 1000) }; } /** * Process forecast data from API response */ processForecast(data) { // Get the next 7 forecasts (covering about 24 hours) return data.list.slice(0, 7).map(item => { return { temperature: Math.round(item.main.temp * 10) / 10, condition: item.weather[0].main, description: item.weather[0].description, icon: this.getWeatherIconUrl(item.weather[0].icon), timestamp: new Date(item.dt * 1000), precipitation: item.rain ? (item.rain['3h'] || 0) : 0 }; }); } /** * Get weather icon URL from icon code */ getWeatherIconUrl(iconCode) { return `https://openweathermap.org/img/wn/${iconCode}@2x.png`; } /** * Create default weather data for fallback */ createDefaultWeatherData() { return { temperature: 7.1, condition: 'Clear', description: 'clear sky', icon: 'https://openweathermap.org/img/wn/01d@2x.png', wind: { speed: 14.8, direction: 270 }, humidity: 65, pressure: 1012.0, precipitation: 0.00, location: 'Stockholm', country: 'SE', timestamp: new Date() }; } /** * Create default forecast data for fallback */ createDefaultForecastData() { const now = new Date(); const forecasts = []; // Create 7 forecast entries for (let i = 0; i < 7; i++) { const forecastTime = new Date(now); forecastTime.setHours(now.getHours() + i); forecasts.push({ temperature: 7.1 - (i * 0.3), // Decrease temperature slightly each hour condition: i < 2 ? 'Clear' : 'Partly Cloudy', description: i < 2 ? 'clear sky' : 'few clouds', icon: i < 2 ? 'https://openweathermap.org/img/wn/01n@2x.png' : 'https://openweathermap.org/img/wn/02n@2x.png', timestamp: forecastTime, precipitation: 0 }); } return forecasts; } /** * Update the weather UI with current data */ updateWeatherUI() { if (!this.weatherData || !this.forecastData) return; try { // Update current weather const locationElement = document.querySelector('#custom-weather h3'); if (locationElement) { locationElement.textContent = this.weatherData.location; } const conditionElement = document.querySelector('#custom-weather .weather-icon div'); if (conditionElement) { conditionElement.textContent = this.weatherData.condition; } const iconElement = document.querySelector('#custom-weather .weather-icon img'); if (iconElement) { iconElement.src = this.weatherData.icon; iconElement.alt = this.weatherData.description; } const temperatureElement = document.querySelector('#custom-weather .temperature'); if (temperatureElement) { temperatureElement.textContent = `${this.weatherData.temperature} °C`; } // Update forecast const forecastContainer = document.querySelector('#custom-weather .forecast'); if (forecastContainer) { // Clear existing forecast forecastContainer.innerHTML = ''; // Add current weather as "Now" const nowElement = document.createElement('div'); nowElement.className = 'forecast-hour'; nowElement.innerHTML = `
Now
${this.weatherData.description}
${this.weatherData.temperature} °C
`; forecastContainer.appendChild(nowElement); // Add hourly forecasts this.forecastData.forEach(forecast => { const forecastTime = forecast.timestamp; const timeString = forecastTime.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit' }); const forecastElement = document.createElement('div'); forecastElement.className = 'forecast-hour'; forecastElement.innerHTML = `
${timeString}
${forecast.description}
${forecast.temperature} °C
`; forecastContainer.appendChild(forecastElement); }); } // Update sun times const sunTimesElement = document.querySelector('#custom-weather .sun-times'); if (sunTimesElement && this.sunTimes) { const sunriseTime = this.formatTime(this.sunTimes.today.sunrise); const sunsetTime = this.formatTime(this.sunTimes.today.sunset); sunTimesElement.textContent = `Sunrise: ${sunriseTime} | Sunset: ${sunsetTime}`; } } catch (error) { console.error('Error updating weather UI:', error); } } /** * Update sunrise and sunset times from API data */ updateSunTimesFromApi(data) { if (!data || !data.sys || !data.sys.sunrise || !data.sys.sunset) { console.warn('No sunrise/sunset data in API response, using calculated times'); this.updateSunTimesFromCalculation(); return; } try { const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); // Create Date objects from Unix timestamps const sunrise = new Date(data.sys.sunrise * 1000); const sunset = new Date(data.sys.sunset * 1000); // Use calculated times for tomorrow const tomorrowTimes = this.calculateSunTimes(tomorrow); this.sunTimes = { today: { sunrise, sunset }, tomorrow: tomorrowTimes }; console.log('Sun times updated from API:', this.sunTimes); return this.sunTimes; } catch (error) { console.error('Error updating sun times from API:', error); this.updateSunTimesFromCalculation(); } } /** * Update sunrise and sunset times using calculation */ async updateSunTimesFromCalculation() { try { // Calculate sun times based on date and location const today = new Date(); const tomorrow = new Date(today); tomorrow.setDate(tomorrow.getDate() + 1); this.sunTimes = { today: this.calculateSunTimes(today), tomorrow: this.calculateSunTimes(tomorrow) }; console.log('Sun times updated from calculation:', this.sunTimes); return this.sunTimes; } catch (error) { console.error('Error updating sun times from calculation:', error); // Fallback to default times if calculation fails const defaultSunrise = new Date(); defaultSunrise.setHours(6, 45, 0, 0); const defaultSunset = new Date(); defaultSunset.setHours(17, 32, 0, 0); this.sunTimes = { today: { sunrise: defaultSunrise, sunset: defaultSunset }, tomorrow: { sunrise: defaultSunrise, sunset: defaultSunset } }; return this.sunTimes; } } /** * Calculate sunrise and sunset times for a given date * Uses a simplified algorithm */ calculateSunTimes(date) { // This is a simplified calculation // For more accuracy, you would use a proper astronomical calculation // Get day of year const start = new Date(date.getFullYear(), 0, 0); const diff = date - start; const oneDay = 1000 * 60 * 60 * 24; const dayOfYear = Math.floor(diff / oneDay); // Calculate sunrise and sunset times based on latitude and day of year // This is a very simplified model const latitude = this.options.latitude; // Base sunrise and sunset times (in hours) let baseSunrise = 6; // 6 AM let baseSunset = 18; // 6 PM // Adjust for latitude and season // Northern hemisphere seasonal adjustment const seasonalAdjustment = Math.sin((dayOfYear - 81) / 365 * 2 * Math.PI) * 3; // Latitude adjustment (higher latitudes have more extreme day lengths) const latitudeAdjustment = Math.abs(latitude) / 90 * 2; // Apply adjustments baseSunrise += seasonalAdjustment * latitudeAdjustment * -1; baseSunset += seasonalAdjustment * latitudeAdjustment; // Create Date objects const sunrise = new Date(date); sunrise.setHours(Math.floor(baseSunrise), Math.round((baseSunrise % 1) * 60), 0, 0); const sunset = new Date(date); sunset.setHours(Math.floor(baseSunset), Math.round((baseSunset % 1) * 60), 0, 0); return { sunrise, sunset }; } /** * Check if it's currently dark outside based on sun times */ isDark() { if (!this.sunTimes) return false; const now = new Date(); const today = this.sunTimes.today; // Check if current time is after today's sunset or before today's sunrise return now > today.sunset || now < today.sunrise; } /** * Update dark mode state based on current time */ updateDarkModeBasedOnTime() { const wasDarkMode = this.isDarkMode; this.isDarkMode = this.isDark(); // If dark mode state changed, dispatch event if (wasDarkMode !== this.isDarkMode) { this.dispatchDarkModeEvent(); } } /** * Set dark mode state manually */ setDarkMode(isDarkMode) { if (this.isDarkMode !== isDarkMode) { this.isDarkMode = isDarkMode; this.dispatchDarkModeEvent(); } } /** * Toggle dark mode */ toggleDarkMode() { this.isDarkMode = !this.isDarkMode; this.dispatchDarkModeEvent(); return this.isDarkMode; } /** * Dispatch dark mode change event */ dispatchDarkModeEvent() { const event = new CustomEvent('darkModeChanged', { detail: { isDarkMode: this.isDarkMode, automatic: true } }); document.dispatchEvent(event); console.log('Dark mode ' + (this.isDarkMode ? 'enabled' : 'disabled')); } /** * Get formatted sunrise time */ getSunriseTime() { if (!this.sunTimes) return '06:45'; return this.formatTime(this.sunTimes.today.sunrise); } /** * Get formatted sunset time */ getSunsetTime() { if (!this.sunTimes) return '17:32'; return this.formatTime(this.sunTimes.today.sunset); } /** * Format time as HH:MM */ formatTime(date) { return date.toLocaleTimeString('sv-SE', { hour: '2-digit', minute: '2-digit' }); } /** * Get the last updated time */ getLastUpdatedTime() { if (!this.lastUpdated) return 'Never'; return this.formatTime(this.lastUpdated); } } // Export the WeatherManager class for use in other modules window.WeatherManager = WeatherManager;