Add dashboard widgets for clock, weather, and daily quote with settings management
This commit is contained in:
315
js/widgets.js
Normal file
315
js/widgets.js
Normal file
@@ -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 = `
|
||||
<div class="widget-error">
|
||||
<p>🌤️ Weather widget requires an API key</p>
|
||||
<small>Get a free key from <a href="https://openweathermap.org/api" target="_blank">OpenWeatherMap</a></small>
|
||||
</div>
|
||||
`;
|
||||
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 = '<div class="widget-error">Unable to get location</div>';
|
||||
}
|
||||
);
|
||||
} else {
|
||||
url += `&q=${settings.location}`;
|
||||
fetchWeather(url, widget);
|
||||
}
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
function fetchWeather(url, widget) {
|
||||
fetch(url)
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
widget.innerHTML = `
|
||||
<div class="weather-info">
|
||||
<div class="weather-location">${data.name}</div>
|
||||
<div class="weather-temp">${Math.round(data.main.temp)}°C</div>
|
||||
<div class="weather-desc">${data.weather[0].description}</div>
|
||||
<div class="weather-details">
|
||||
<span>💨 ${data.wind.speed} m/s</span>
|
||||
<span>💧 ${data.main.humidity}%</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Weather fetch error:', error);
|
||||
widget.innerHTML = '<div class="widget-error">Failed to load weather</div>';
|
||||
});
|
||||
}
|
||||
|
||||
// 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 = `
|
||||
<div class="quote-content">
|
||||
<p class="quote-text">"${data.content}"</p>
|
||||
<p class="quote-author">— ${data.author}</p>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Quote fetch error:', error);
|
||||
widget.innerHTML = '<div class="widget-error">Failed to load quote</div>';
|
||||
});
|
||||
|
||||
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 = `
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>Widget Settings</h2>
|
||||
<button class="modal-close">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="widget-setting">
|
||||
<label>
|
||||
<input type="checkbox" id="widget-clock" ${settings.clock?.enabled ? 'checked' : ''}>
|
||||
🕐 Clock
|
||||
</label>
|
||||
</div>
|
||||
<div class="widget-setting">
|
||||
<label>
|
||||
<input type="checkbox" id="widget-weather" ${settings.weather?.enabled ? 'checked' : ''}>
|
||||
🌤️ Weather
|
||||
</label>
|
||||
<div class="widget-subsetting">
|
||||
<input type="text" id="weather-api-key" placeholder="OpenWeatherMap API Key" value="${settings.weather?.apiKey || ''}">
|
||||
<input type="text" id="weather-location" placeholder="Location (or 'auto')" value="${settings.weather?.location || 'auto'}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-setting">
|
||||
<label>
|
||||
<input type="checkbox" id="widget-quote" ${settings.quote?.enabled ? 'checked' : ''}>
|
||||
💭 Daily Quote
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-save">Save & Reload</button>
|
||||
<button class="btn-cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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
|
||||
};
|
||||
Reference in New Issue
Block a user