From 590303dad88a2b46ab0b93d08e434bfcbdb49a11 Mon Sep 17 00:00:00 2001 From: MayaChat Date: Mon, 24 Nov 2025 12:49:04 -0500 Subject: [PATCH] Add dashboard widgets for clock, weather, and daily quote with settings management --- js/widgets.js | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 js/widgets.js diff --git a/js/widgets.js b/js/widgets.js new file mode 100644 index 0000000..5d0318d --- /dev/null +++ b/js/widgets.js @@ -0,0 +1,315 @@ +/** + * widgets.js - Dashboard widgets (time, weather, etc.) + */ + +const STORAGE_KEY_WIDGETS = 'enabled-widgets'; + +// Widget configurations +const widgetConfigs = { + clock: { + name: 'Clock', + icon: '🕐', + enabled: true + }, + weather: { + name: 'Weather', + icon: '🌤️', + enabled: false, + apiKey: '', // User needs to set this + location: 'auto' + }, + quote: { + name: 'Daily Quote', + icon: '💭', + enabled: false + } +}; + +// Load widget settings +function loadWidgetSettings() { + try { + const saved = localStorage.getItem(STORAGE_KEY_WIDGETS); + return saved ? JSON.parse(saved) : widgetConfigs; + } catch (e) { + console.error('Error loading widget settings:', e); + return widgetConfigs; + } +} + +// Save widget settings +function saveWidgetSettings(settings) { + try { + localStorage.setItem(STORAGE_KEY_WIDGETS, JSON.stringify(settings)); + } catch (e) { + console.error('Error saving widget settings:', e); + } +} + +// Clock Widget +function createClockWidget() { + const widget = document.createElement('div'); + widget.className = 'widget widget-clock'; + + const timeDisplay = document.createElement('div'); + timeDisplay.className = 'widget-time'; + + const dateDisplay = document.createElement('div'); + dateDisplay.className = 'widget-date'; + + function updateTime() { + const now = new Date(); + + // Time + const hours = now.getHours().toString().padStart(2, '0'); + const minutes = now.getMinutes().toString().padStart(2, '0'); + const seconds = now.getSeconds().toString().padStart(2, '0'); + timeDisplay.textContent = `${hours}:${minutes}:${seconds}`; + + // Date + const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; + dateDisplay.textContent = now.toLocaleDateString('en-US', options); + } + + updateTime(); + setInterval(updateTime, 1000); + + widget.appendChild(timeDisplay); + widget.appendChild(dateDisplay); + + return widget; +} + +// Weather Widget +function createWeatherWidget(settings) { + const widget = document.createElement('div'); + widget.className = 'widget widget-weather'; + + const loading = document.createElement('div'); + loading.className = 'widget-loading'; + loading.textContent = '🌤️ Loading weather...'; + widget.appendChild(loading); + + // Check if API key is set + if (!settings.apiKey) { + widget.innerHTML = ` +
+

🌤️ Weather widget requires an API key

+ Get a free key from OpenWeatherMap +
+ `; + return widget; + } + + // Fetch weather data + let url = `https://api.openweathermap.org/data/2.5/weather?appid=${settings.apiKey}&units=metric`; + + if (settings.location === 'auto') { + // Use geolocation + navigator.geolocation.getCurrentPosition( + (position) => { + url += `&lat=${position.coords.latitude}&lon=${position.coords.longitude}`; + fetchWeather(url, widget); + }, + () => { + widget.innerHTML = '
Unable to get location
'; + } + ); + } else { + url += `&q=${settings.location}`; + fetchWeather(url, widget); + } + + return widget; +} + +function fetchWeather(url, widget) { + fetch(url) + .then(res => res.json()) + .then(data => { + widget.innerHTML = ` +
+
${data.name}
+
${Math.round(data.main.temp)}°C
+
${data.weather[0].description}
+
+ 💨 ${data.wind.speed} m/s + 💧 ${data.main.humidity}% +
+
+ `; + }) + .catch(error => { + console.error('Weather fetch error:', error); + widget.innerHTML = '
Failed to load weather
'; + }); +} + +// Quote Widget +function createQuoteWidget() { + const widget = document.createElement('div'); + widget.className = 'widget widget-quote'; + + const loading = document.createElement('div'); + loading.className = 'widget-loading'; + loading.textContent = '💭 Loading quote...'; + widget.appendChild(loading); + + fetch('https://api.quotable.io/random') + .then(res => res.json()) + .then(data => { + widget.innerHTML = ` +
+

"${data.content}"

+

— ${data.author}

+
+ `; + }) + .catch(error => { + console.error('Quote fetch error:', error); + widget.innerHTML = '
Failed to load quote
'; + }); + + return widget; +} + +// Create widget container +function createWidgetContainer() { + const container = document.createElement('div'); + container.id = 'widgets-container'; + container.className = 'widgets-container'; + + return container; +} + +// Initialize widgets +function initWidgets() { + const settings = loadWidgetSettings(); + const container = createWidgetContainer(); + + // Add enabled widgets + if (settings.clock?.enabled) { + container.appendChild(createClockWidget()); + } + + if (settings.weather?.enabled) { + container.appendChild(createWeatherWidget(settings.weather)); + } + + if (settings.quote?.enabled) { + container.appendChild(createQuoteWidget()); + } + + // Add to header if any widgets are enabled + if (container.children.length > 0) { + const header = document.querySelector('header'); + if (header) { + header.appendChild(container); + } + } + + // Create widget settings button + createWidgetSettings(); +} + +// Create widget settings UI +function createWidgetSettings() { + const button = document.createElement('button'); + button.className = 'widget-settings-btn control-btn'; + button.innerHTML = '⚙️ Widgets'; + button.title = 'Widget Settings'; + + button.addEventListener('click', showWidgetSettingsModal); + + const header = document.querySelector('header'); + if (header) { + let controls = header.querySelector('.import-export-controls'); + if (!controls) { + controls = document.createElement('div'); + controls.className = 'import-export-controls'; + header.appendChild(controls); + } + controls.appendChild(button); + } +} + +// Show widget settings modal +function showWidgetSettingsModal() { + const settings = loadWidgetSettings(); + + const modal = document.createElement('div'); + modal.className = 'modal'; + modal.innerHTML = ` + + `; + + document.body.appendChild(modal); + + // Event listeners + modal.querySelector('.modal-close').addEventListener('click', () => modal.remove()); + modal.querySelector('.btn-cancel').addEventListener('click', () => modal.remove()); + modal.querySelector('.btn-save').addEventListener('click', () => { + const newSettings = { + clock: { + ...widgetConfigs.clock, + enabled: modal.querySelector('#widget-clock').checked + }, + weather: { + ...widgetConfigs.weather, + enabled: modal.querySelector('#widget-weather').checked, + apiKey: modal.querySelector('#weather-api-key').value, + location: modal.querySelector('#weather-location').value || 'auto' + }, + quote: { + ...widgetConfigs.quote, + enabled: modal.querySelector('#widget-quote').checked + } + }; + + saveWidgetSettings(newSettings); + modal.remove(); + location.reload(); + }); + + // Close on background click + modal.addEventListener('click', (e) => { + if (e.target === modal) modal.remove(); + }); +} + +// Export for use in other modules +window.widgetsModule = { + init: initWidgets +};