1class WeatherWidget { 2 3 constructor(element) { 4 5 this.element = element; 6 7 // Configuración 8 this.locationId = element.dataset.locationId; 9 this.imagePath = element.dataset.imagePath; 10 this.imageExt = element.dataset.imageExt; 11 this.background = element.dataset.background; 12 13 // Elementos 14 this.toggle = element.querySelector('[data-weather-toggle]'); 15 this.panel = element.querySelector('[data-weather-panel]'); 16 this.locationSelect = element.querySelector('[data-weather-location-select]'); 17 18 // Elementos de datos 19 this.tempElements = element.querySelectorAll('[data-weather-temp]'); 20 this.iconElements = element.querySelectorAll('[data-weather-icon]'); 21 this.minElements = element.querySelectorAll('[data-weather-min]'); 22 this.maxElements = element.querySelectorAll('[data-weather-max]'); 23 this.humidityElement = element.querySelector('[data-weather-humidity]'); 24 this.pressureElement = element.querySelector('[data-weather-pressure]'); 25 this.precipitationElement = element.querySelector('[data-weather-precipitation]'); 26 this.windElement = element.querySelector('[data-weather-wind]'); 27 28 // Elementos de animaciones 29 this.animations = { 30 particles: element.querySelector('[data-weather-particles]'), 31 rain: element.querySelector('[data-weather-rain]'), 32 clouds: element.querySelector('[data-weather-clouds]'), 33 lightning: element.querySelector('[data-weather-lightning]'), 34 snow: element.querySelector('[data-weather-snow]') 35 }; 36 37 // Estado 38 this.isOpen = false; 39 this.collectionWeather = null; 40 this.currentWeather = null; 41 this.updateInterval = null; 42 43 // URLs de API 44 this.config = { 45 apiUrl: '/extras/js/clima.php?mode=id', 46 updateInterval: 600000 // 10 minutos 47 }; 48 49 this.init(); 50 } 51 52 init() { 53 54 console.log('weather WeatherWidget init'); 55 56 this.setupEventListeners(); 57 this.loadWeatherData(); 58 this.startAutoUpdate(); 59 } 60 61 setupEventListeners() { 62 // Toggle panel 63 if (this.toggle) { 64 this.toggle.addEventListener('click', () => { 65 this.togglePanel(); 66 }); 67 } 68 69 // Cambio de ubicación 70 if (this.locationSelect) { 71 this.locationSelect.addEventListener('change', (e) => { 72 this.locationId = e.target.value; 73 this.loadWeatherData(); 74 }); 75 } 76 77 // Cerrar al hacer click fuera 78 document.addEventListener('click', (e) => { 79 if (this.isOpen && 80 !this.element.contains(e.target) && 81 !e.target.closest('[data-weather-widget]')) { 82 this.closePanel(); 83 } 84 }); 85 86 // Cerrar con Escape 87 document.addEventListener('keydown', (e) => { 88 if (e.key === 'Escape' && this.isOpen) { 89 this.closePanel(); 90 } 91 }); 92 } 93 94 togglePanel() { 95 if (this.isOpen) { 96 this.closePanel(); 97 } else { 98 this.openPanel(); 99 } 100 } 101
102 openPanel() { 103 if (this.panel) { 104 this.panel.hidden = false; 105 this.isOpen = true; 106 107 if (this.toggle) { 108 this.toggle.setAttribute('aria-expanded', 'true'); 109 } 110 } 111 } 112 113 closePanel() { 114 if (this.panel) { 115 this.panel.hidden = true; 116 this.isOpen = false; 117 118 if (this.toggle) { 119 this.toggle.setAttribute('aria-expanded', 'false'); 120 } 121 } 122 } 123 124 async loadWeatherData() { 125 126 try { 127 128 const response = await fetch(this.config.apiUrl, { 129 method: 'POST', 130 headers: { 131 'Content-Type': 'application/x-www-form-urlencoded', 132 }, 133 body: new URLSearchParams({ 134 location_id: this.locationId 135 }) 136 }); 137 138 const data = await response.json(); 139 140 if (data.success) { 141 142 this.collectionWeather = data.weather; 143 this.currentWeather = this.collectionWeather[this.locationId]; 144 this.updateUI(); 145 this.updateAnimations(); 146 147 console.log('weather load data ok'); 148 console.log(data); 149 150 } 151 152 } catch (error) { 153 console.error('Error loading weather data:', error); 154 } 155 156 } 157 158 updateUI() { 159 160 if (!this.currentWeather) return; 161 162 const weather = this.currentWeather; 163 164 // Fecha hoy 165 const hoy = new Date(); 166 const dd = String(hoy.getDate()).padStart(2, '0'); 167 const mm = String(hoy.getMonth() + 1).padStart(2, '0'); // Enero es 0! 168 const yyyy = hoy.getFullYear(); 169 170 const fechaFormateada = `${yyyy}-${mm}-${dd}`; 171 172 173 // Temperatura actual 174 this.tempElements.forEach(el => { 175 // el.textContent = `${Math.round(weather.current_weather.temp)}°`; 176 el.textContent = weather.item.condition.temp + weather.units.temperature; 177 }); 178 179 // Ãcono 180 const iconUrl = `${this.imagePath}${weather.item.condition.code}.${this.imageExt}`; 181 this.iconElements.forEach(el => { 182 el.src = iconUrl; 183 // el.alt = weather.description; 184 }); 185 186 // Min/Max 187 this.minElements.forEach(el => { 188 el.textContent = weather.item.forecast[0].low + weather.units.temperature; // `${Math.round(weather.temp_min)}°`; 189 }); 190 191 this.maxElements.forEach(el => { 192 el.textContent = weather.item.forecast[0].high + weather.units.temperature; // `${Math.round(weather.temp_max)}°`; 193 }); 194 195 // Humedad 196 if (this.humidityElement) { 197 this.humidityElement.textContent = weather.atmosphere.humidity; // `${weather.humidity}%`; 198 } 199 200 // Presión 201 if (this.pressureElement) { 202 this.pressureElement.textContent = weather.atmosphere.pressure; // `${weather.pressure} hPa`; 203 } 204 205 // Precipitación 206 if (this.precipitationElement) { 207 this.precipitationElement.textContent = weather.atmosphere.rising + '%'; // `${weather.precipitation}%`; 208 } 209 210 // Viento 211 if (this.windElement) { 212 this.windElement.textContent = weather.wind.speed + 'km/h ' + weather.wind.direction; // `${weather.wind_speed} km/h`; 213 } 214 215 // Pronóstico extendido 216 if (weather.item.forecast) { 217 218 weather.item.forecast.forEach((day, index) => { 219 220 const dayElement = this.element.querySelector(`[data-weather-day="${index + 1}"]`); 221 222 if (dayElement) { 223 224 const iconEl = dayElement.querySelector('[data-weather-icon]'); 225 const minEl = dayElement.querySelector('[data-weather-min]'); 226 const maxEl = dayElement.querySelector('[data-weather-max]'); 227 228 if (iconEl) { 229 iconEl.src = `${this.imagePath}${day.code}.${this.imageExt}`; 230 } 231 232 if (minEl) { 233 minEl.textContent = `${day.low}° ${index}`; 234 } 235 236 if (maxEl) { 237 maxEl.textContent = `${day.high}° ${index}`; 238 } 239 } 240 }); 241 } 242 243 this.element.removeAttribute('hidden'); // setAttribute('hidden', ''); 244 } 245 246 updateAnimations() { 247 248 if (!this.currentWeather) return; 249 250 // Ocultar todas las animaciones 251 Object.values(this.animations).forEach(animation => { 252 if (animation) animation.hidden = true; 253 }); 254 255 // Mostrar animación según condición climática 256 /* 257 const condition = this.currentWeather.condition.toLowerCase(); 258 259 if (condition.includes('rain') || condition.includes('lluvia')) { 260 if (this.animations.rain) this.animations.rain.hidden = false;
261 if (this.animations.clouds) this.animations.clouds.hidden = false; 262 } else if (condition.includes('snow') || condition.includes('nieve')) { 263 if (this.animations.snow) this.animations.snow.hidden = false; 264 } else if (condition.includes('storm') || condition.includes('tormenta')) { 265 if (this.animations.lightning) this.animations.lightning.hidden = false; 266 if (this.animations.rain) this.animations.rain.hidden = false; 267 } else if (condition.includes('cloud') || condition.includes('nube')) { 268 if (this.animations.clouds) this.animations.clouds.hidden = false; 269 } else if (condition.includes('clear') || condition.includes('despejado')) { 270 if (this.animations.particles) this.animations.particles.hidden = false; 271 } 272 273 // Actualizar background 274 if (this.background === 'gradient') { 275 this.updateBackgroundGradient(); 276 } 277 */ 278 } 279 280 updateBackgroundGradient() { 281 const hour = new Date().getHours(); 282 let gradient; 283 284 if (hour >= 6 && hour < 12) { 285 // Mañana 286 gradient = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'; 287 } else if (hour >= 12 && hour < 18) { 288 // Tarde 289 gradient = 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)'; 290 } else if (hour >= 18 && hour < 21) { 291 // Atardecer 292 gradient = 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)'; 293 } else { 294 // Noche 295 gradient = 'linear-gradient(135deg, #2b5876 0%, #4e4376 100%)'; 296 } 297 298 const header = this.element.querySelector('.weather-widget__header'); 299 if (header) { 300 header.style.background = gradient; 301 } 302 303 const currentMain = this.element.querySelector('.weather-widget__current-main'); 304 if (currentMain) { 305 currentMain.style.background = gradient; 306 } 307 } 308 309 startAutoUpdate() { 310 // Actualizar cada 10 minutos 311 this.updateInterval = setInterval(() => { 312 this.loadWeatherData(); 313 }, this.config.updateInterval); 314 } 315 316 destroy() { 317 if (this.updateInterval) { 318 clearInterval(this.updateInterval); 319 } 320 } 321} 322 323console.log('weather.js loading'); 324 325const weatherWidgets = document.querySelectorAll('[data-weather-widget]'); 326weatherWidgets.forEach(widget => { 327 new WeatherWidget(widget); 328});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.