diff --git a/WIDGETS_GUIDE.md b/WIDGETS_GUIDE.md new file mode 100644 index 0000000..7e2e64a --- /dev/null +++ b/WIDGETS_GUIDE.md @@ -0,0 +1,720 @@ +# 🎨 Leon AI Dashboard Widgets Guide + +## Overview + +The Leon AI Dashboard now includes a powerful **Widgets System** that allows you to customize your dashboard with interactive UI elements. This guide will help you understand, use, and create your own widgets. + +--- + +## 📋 Table of Contents + +1. [Getting Started](#getting-started) +2. [Available Widget Types](#available-widget-types) +3. [Using Widgets](#using-widgets) +4. [Creating Custom Widgets](#creating-custom-widgets) +5. [Widget Placement](#widget-placement) +6. [API Reference](#api-reference) +7. [Beginner Examples](#beginner-examples) +8. [Advanced Customization](#advanced-customization) + +--- + +## 🚀 Getting Started + +### Accessing the Widgets Dashboard + +1. Start your Leon AI server +2. Open the web interface +3. Click the **"Widgets"** button in the top navigation +4. Click **"Add Widget"** to start adding widgets + +### Quick Start for Beginners + +The easiest way to add widgets is through the UI: + +1. Click **"Widgets"** button +2. Click **"Add Widget"** +3. Choose a widget type from the dialog +4. The widget will appear on your dashboard! + +--- + +## 🧩 Available Widget Types + +### 1. 🕐 Clock Widget +**Purpose:** Display current time and date + +**Features:** +- Real-time clock updates +- Customizable locale +- Optional seconds display +- Date formatting + +**Use Cases:** +- Keep track of time while working +- Display time in different timezones +- Quick reference for date and time + +--- + +### 2. 🌤️ Weather Widget +**Purpose:** Show weather information + +**Features:** +- Temperature display +- Weather condition +- Location name +- Refreshable data + +**Use Cases:** +- Check weather at a glance +- Plan your day +- Monitor conditions + +**Note:** Currently shows placeholder data. Can be connected to a weather API. + +--- + +### 3. 📝 Notes Widget +**Purpose:** Quick notepad for thoughts and reminders + +**Features:** +- Simple text area +- Auto-save on blur +- Persistent storage +- Resizable + +**Use Cases:** +- Quick notes during conversations +- Temporary reminders +- Draft messages +- Brainstorming + +--- + +### 4. 🔗 Quick Links Widget +**Purpose:** Bookmark your favorite websites + +**Features:** +- Clickable links +- Custom icons (emoji) +- Link titles +- Opens in new tab + +**Use Cases:** +- Frequently visited sites +- Project resources +- Documentation links +- Social media shortcuts + +**Example Links:** +```javascript +{ + links: [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Docs', url: 'https://docs.example.com', icon: '📖' } + ] +} +``` + +--- + +### 5. 📊 Stats Widget +**Purpose:** Display key metrics and statistics + +**Features:** +- Multiple stat items +- Value and label display +- Grid layout +- Color-coded values + +**Use Cases:** +- Dashboard KPIs +- Task completion rates +- System metrics +- Progress tracking + +**Example Stats:** +```javascript +{ + stats: [ + { label: 'Tasks', value: '12' }, + { label: 'Completed', value: '8' }, + { label: 'Pending', value: '4' } + ] +} +``` + +--- + +### 6. ✅ Todo List Widget +**Purpose:** Manage your tasks and todos + +**Features:** +- Add new tasks +- Check off completed items +- Delete tasks +- Persistent storage +- Visual completion state + +**Use Cases:** +- Daily task management +- Project checklists +- Shopping lists +- Goal tracking + +--- + +### 7. 🎨 Custom Widget +**Purpose:** Create your own widget with custom HTML + +**Features:** +- Full HTML support +- Custom styling +- Flexible content +- Unlimited possibilities + +**Use Cases:** +- Embed external content +- Custom visualizations +- Specialized tools +- Unique layouts + +--- + +## 💡 Using Widgets + +### Adding a Widget (UI Method) + +1. Click **"Add Widget"** button +2. Select widget type from the dialog +3. Widget appears with default configuration +4. Customize the widget content + +### Widget Controls + +Each widget has three control buttons: + +- **↻ Refresh:** Reload widget data +- **⚙ Settings:** Configure widget (coming soon) +- **× Remove:** Delete the widget + +### Drag and Drop + +- **Grab:** Click and hold the widget header +- **Move:** Drag to desired position +- **Drop:** Release to place widget +- Widgets automatically reorder + +### Editing Widget Content + +Different widgets have different editing methods: + +- **Notes:** Type directly in the text area +- **Todo:** Use the input field and + button +- **Quick Links:** Edit via code (see API section) +- **Stats:** Update via code (see API section) + +--- + +## 🛠️ Creating Custom Widgets + +### Method 1: Using the Custom Widget Type + +```javascript +const myWidget = { + type: 'custom', + title: 'My Custom Widget', + content: ` +
This is my custom widget
+ +Your personal AI assistant
+ +Unknown widget type
' + } +} + +renderMyCustomWidget(widget) { + return ` + + ` +} +``` + +#### Step 2: Add Styles in widgets.scss + +```scss +.my-custom-widget { + padding: 1rem; + + h3 { + color: rgba(255, 255, 255, 0.9); + margin-bottom: 0.5rem; + } + + .custom-content { + background: rgba(255, 255, 255, 0.05); + padding: 1rem; + border-radius: 8px; + } +} +``` + +#### Step 3: Add Event Listeners (if needed) + +```javascript +attachWidgetTypeListeners(widgetElement, widget) { + switch (widget.type) { + // ... existing cases + case 'my-custom-type': + const button = widgetElement.querySelector('.custom-button') + button?.addEventListener('click', () => { + // Handle click + this.updateWidget(widget.id, { + data: { clicked: true } + }) + }) + break + } +} +``` + +#### Step 4: Use Your New Widget + +```javascript +const myWidget = { + type: 'my-custom-type', + title: 'My Custom Widget', + data: { + title: 'Hello', + description: 'This is my custom widget type', + content: 'Custom content here' + } +} + +await widgetManager.addWidget(myWidget) +``` + +### Connecting to External APIs + +```javascript +// Example: Real weather widget +async function createLiveWeatherWidget(city) { + // Fetch weather data from API + const response = await fetch( + `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY` + ) + const data = await response.json() + + return { + type: 'weather', + title: 'Live Weather', + config: { location: city }, + data: { + temp: Math.round(data.main.temp - 273.15), // Convert to Celsius + condition: data.weather[0].description, + location: city + } + } +} + +// Use it +const weatherWidget = await createLiveWeatherWidget('London') +await widgetManager.addWidget(weatherWidget) +``` + +### Widget Persistence + +Widgets are automatically saved to the server. To implement database persistence: + +1. Replace the `Map` in `old-server.js` with a database +2. Implement save/load functions +3. Connect to your preferred database (MongoDB, PostgreSQL, etc.) + +```javascript +// Example with a database +const widgetsStore = { + async get(userId) { + return await db.widgets.find({ userId }) + }, + async set(userId, widgets) { + await db.widgets.updateOne( + { userId }, + { $set: { widgets } }, + { upsert: true } + ) + } +} +``` + +--- + +## 🎯 Best Practices + +### 1. Widget Design +- Keep widgets focused on one purpose +- Use clear, descriptive titles +- Provide visual feedback for interactions +- Make content scannable + +### 2. Performance +- Avoid heavy computations in render functions +- Use efficient update mechanisms +- Limit the number of widgets (recommended: 6-12) +- Clean up event listeners when widgets are removed + +### 3. User Experience +- Provide default data for new widgets +- Show loading states when fetching data +- Handle errors gracefully +- Make widgets responsive + +### 4. Code Organization +- Keep widget logic in separate files +- Use the examples file as a template +- Document custom widget types +- Follow the existing code style + +--- + +## 🐛 Troubleshooting + +### Widget Not Appearing +- Check browser console for errors +- Verify widget type is valid +- Ensure widget-manager is initialized +- Check if widgets container exists + +### Styles Not Applied +- Verify widgets.scss is imported +- Check for CSS conflicts +- Clear browser cache +- Rebuild the application + +### Data Not Persisting +- Check server API endpoints +- Verify network requests in DevTools +- Check server console for errors +- Ensure userId is consistent + +### Drag and Drop Not Working +- Verify draggable attribute is set +- Check event listeners are attached +- Ensure no CSS conflicts with pointer-events +- Test in different browsers + +--- + +## 📖 Additional Resources + +- **Leon AI Documentation:** https://docs.getleon.ai +- **Widget Examples:** See `app/src/js/widget-examples.js` +- **Widget Manager:** See `app/src/js/widget-manager.js` +- **Styles:** See `app/src/css/widgets.scss` + +--- + +## 🤝 Contributing + +Want to add new widget types or improve existing ones? + +1. Fork the repository +2. Create a new widget type +3. Add documentation +4. Submit a pull request + +--- + +## 📝 License + +This widgets system is part of Leon AI and follows the same license. + +--- + +## 🎉 Happy Widget Building! + +Start customizing your Leon AI dashboard today. If you create something cool, share it with the community! + +**Questions?** Join the Leon AI Discord: https://discord.gg/MNQqqKg diff --git a/WIDGETS_QUICKSTART.md b/WIDGETS_QUICKSTART.md new file mode 100644 index 0000000..afbcd40 --- /dev/null +++ b/WIDGETS_QUICKSTART.md @@ -0,0 +1,457 @@ +# 🚀 Widgets Quick Start Guide + +## For Complete Beginners + +This guide will help you add customizable widgets to your Leon AI dashboard in just a few minutes! + +--- + +## ✨ What Are Widgets? + +Widgets are small, interactive UI elements you can add to your dashboard. Think of them like apps on your phone's home screen - each one does something useful! + +**Available Widgets:** +- 🕐 **Clock** - Shows current time and date +- 📝 **Notes** - Quick notepad for ideas +- ✅ **Todo List** - Manage your tasks +- 🔗 **Quick Links** - Bookmark favorite websites +- 📊 **Stats** - Display numbers and metrics +- 🌤️ **Weather** - Show weather info + +--- + +## 🎯 Method 1: Using the UI (Easiest!) + +### Step 1: Access Widgets +1. Start your Leon AI server +2. Open the web interface in your browser +3. Look for the **"Widgets"** button at the top +4. Click it! + +### Step 2: Add Your First Widget +1. Click the **"Add Widget"** button +2. You'll see a dialog with widget types +3. Click on any widget type (try **Clock** first!) +4. The widget appears instantly! 🎉 + +### Step 3: Customize Your Widget +- **Move it:** Drag the widget header to reorder +- **Remove it:** Click the **×** button +- **Refresh it:** Click the **↻** button +- **Edit content:** Type directly in Notes or Todo widgets + +### Step 4: Add More Widgets +Repeat Step 2 to add as many widgets as you want! + +--- + +## 💻 Method 2: Using Code (For Developers) + +### Basic Example + +Open your browser's developer console (F12) and paste this: + +```javascript +// Get the widget manager instance +const widgetManager = window.widgetManager + +// Add a clock widget +await widgetManager.addWidget({ + type: 'clock', + title: 'My Clock', + position: 0 +}) + +// Add a notes widget +await widgetManager.addWidget({ + type: 'notes', + title: 'Quick Notes', + data: { notes: 'Write your notes here...' }, + position: 1 +}) + +// Add a todo list +await widgetManager.addWidget({ + type: 'todo', + title: 'My Tasks', + data: { + todos: [ + { text: 'Learn Leon AI', completed: false }, + { text: 'Add widgets', completed: true } + ] + }, + position: 2 +}) +``` + +--- + +## 🎨 Widget Types Explained + +### 1. Clock Widget +```javascript +{ + type: 'clock', + title: 'Clock', + config: { + showSeconds: true, // Show seconds? true/false + locale: 'en-US' // Language format + } +} +``` + +### 2. Notes Widget +```javascript +{ + type: 'notes', + title: 'My Notes', + data: { + notes: 'Your text here...' + } +} +``` + +### 3. Todo Widget +```javascript +{ + type: 'todo', + title: 'Tasks', + data: { + todos: [ + { text: 'Task 1', completed: false }, + { text: 'Task 2', completed: true } + ] + } +} +``` + +### 4. Quick Links Widget +```javascript +{ + type: 'quick-links', + title: 'Bookmarks', + data: { + links: [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Google', url: 'https://google.com', icon: '🔍' } + ] + } +} +``` + +### 5. Stats Widget +```javascript +{ + type: 'stats', + title: 'My Stats', + data: { + stats: [ + { label: 'Tasks', value: '10' }, + { label: 'Done', value: '7' } + ] + } +} +``` + +### 6. Weather Widget +```javascript +{ + type: 'weather', + title: 'Weather', + data: { + temp: '72', + condition: 'Sunny', + location: 'New York' + } +} +``` + +--- + +## 🛠️ Common Tasks + +### How to Update a Widget + +```javascript +// First, find the widget ID (shown in browser console) +const widgetId = 'widget-1234567890-abc' + +// Update the widget +await widgetManager.updateWidget(widgetId, { + title: 'New Title', + data: { notes: 'Updated content' } +}) +``` + +### How to Delete a Widget + +**Method 1 (Easy):** Click the **×** button on the widget + +**Method 2 (Code):** +```javascript +await widgetManager.deleteWidget('widget-1234567890-abc') +``` + +### How to Create a Custom Widget + +```javascript +await widgetManager.addWidget({ + type: 'custom', + title: 'My Custom Widget', + content: ` +This is my custom widget
+ +You can start to interact with Leon, don't be shy. diff --git a/app/src/js/main.js b/app/src/js/main.js index ba63f54..688a792 100644 --- a/app/src/js/main.js +++ b/app/src/js/main.js @@ -8,6 +8,7 @@ import Client from './client' // import Recorder from './recorder' // import listener from './listener' import { onkeydownstartrecording, onkeydowninput } from './onkeydown' +import WidgetManager from './widget-manager' const config = { app: 'webapp', @@ -60,6 +61,72 @@ document.addEventListener('DOMContentLoaded', async () => { client.updateMood(window.leonConfigInfo.mood) client.init() + // Initialize Widget Manager + const widgetManager = new WidgetManager() + await widgetManager.init('widgets-container') + + // Toggle widgets section + const toggleWidgetsBtn = document.querySelector('#toggle-widgets') + const widgetsSection = document.querySelector('#widgets-section') + const feedSection = document.querySelector('#feed') + + toggleWidgetsBtn.addEventListener('click', () => { + const isHidden = widgetsSection.classList.contains('hide') + if (isHidden) { + widgetsSection.classList.remove('hide') + feedSection.classList.add('hide') + toggleWidgetsBtn.textContent = 'Chat' + } else { + widgetsSection.classList.add('hide') + feedSection.classList.remove('hide') + toggleWidgetsBtn.textContent = 'Widgets' + } + }) + + // Add widget dialog handlers + const addWidgetDialog = document.querySelector('#add-widget-dialog') + const closeDialogBtn = document.querySelector('#close-dialog') + + closeDialogBtn.addEventListener('click', () => { + widgetManager.hideAddWidgetDialog() + }) + + // Handle widget type selection + document.querySelectorAll('.widget-type-card').forEach(card => { + card.addEventListener('click', async () => { + const widgetType = card.dataset.widgetType + const widgetConfig = { + type: widgetType, + title: card.querySelector('.widget-type-name').textContent, + position: widgetManager.widgets.length, + config: {}, + data: {} + } + + // Add default data for specific widget types + if (widgetType === 'quick-links') { + widgetConfig.data.links = [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Stack Overflow', url: 'https://stackoverflow.com', icon: '📚' } + ] + } else if (widgetType === 'stats') { + widgetConfig.data.stats = [ + { label: 'Tasks', value: '12' }, + { label: 'Completed', value: '8' } + ] + } else if (widgetType === 'weather') { + widgetConfig.data = { + temp: '72', + condition: 'Sunny', + location: 'Your City' + } + } + + await widgetManager.addWidget(widgetConfig) + widgetManager.hideAddWidgetDialog() + }) + }) + infoButton.addEventListener('click', () => { alert(JSON.stringify(infoToDisplay, null, 2)) }) diff --git a/app/src/js/widget-examples.js b/app/src/js/widget-examples.js new file mode 100644 index 0000000..3fd4bb5 --- /dev/null +++ b/app/src/js/widget-examples.js @@ -0,0 +1,235 @@ +/** + * Widget Examples for Beginners + * Simple, easy-to-understand widget implementations + */ + +/** + * EXAMPLE 1: Simple Clock Widget + * Shows current time that updates every second + */ +export const createSimpleClockWidget = () => { + return { + type: 'clock', + title: 'Clock', + config: { + showSeconds: true, + locale: 'en-US' + }, + position: 0 + } +} + +/** + * EXAMPLE 2: Personal Notes Widget + * A simple notepad for quick notes + */ +export const createNotesWidget = () => { + return { + type: 'notes', + title: 'My Notes', + data: { + notes: 'Write your notes here...' + }, + position: 1 + } +} + +/** + * EXAMPLE 3: Todo List Widget + * Manage your daily tasks + */ +export const createTodoWidget = () => { + return { + type: 'todo', + title: 'My Tasks', + data: { + todos: [ + { text: 'Learn about Leon AI', completed: false }, + { text: 'Create custom widgets', completed: false }, + { text: 'Explore widget features', completed: false } + ] + }, + position: 2 + } +} + +/** + * EXAMPLE 4: Quick Links Widget + * Bookmark your favorite websites + */ +export const createQuickLinksWidget = () => { + return { + type: 'quick-links', + title: 'Quick Links', + data: { + links: [ + { title: 'GitHub', url: 'https://github.com', icon: '🐙' }, + { title: 'Stack Overflow', url: 'https://stackoverflow.com', icon: '📚' }, + { title: 'MDN Docs', url: 'https://developer.mozilla.org', icon: '📖' }, + { title: 'Leon AI', url: 'https://getleon.ai', icon: '🤖' } + ] + }, + position: 3 + } +} + +/** + * EXAMPLE 5: Stats Widget + * Display key metrics and statistics + */ +export const createStatsWidget = () => { + return { + type: 'stats', + title: 'Dashboard Stats', + data: { + stats: [ + { label: 'Tasks', value: '12' }, + { label: 'Completed', value: '8' }, + { label: 'Pending', value: '4' } + ] + }, + position: 4 + } +} + +/** + * EXAMPLE 6: Weather Widget + * Display weather information + */ +export const createWeatherWidget = (location = 'Your City') => { + return { + type: 'weather', + title: 'Weather', + config: { + location: location + }, + data: { + temp: '72', + condition: 'Sunny', + location: location + }, + position: 5 + } +} + +/** + * EXAMPLE 7: Custom HTML Widget + * Create your own custom widget with HTML + */ +export const createCustomWidget = (title, htmlContent) => { + return { + type: 'custom', + title: title, + content: htmlContent, + position: 6 + } +} + +/** + * HOW TO USE THESE EXAMPLES: + * + * 1. Import the widget manager in your code: + * import WidgetManager from './widget-manager' + * import { createSimpleClockWidget } from './widget-examples' + * + * 2. Initialize the widget manager: + * const widgetManager = new WidgetManager() + * await widgetManager.init('widgets-container') + * + * 3. Add a widget using the examples: + * const clockWidget = createSimpleClockWidget() + * await widgetManager.addWidget(clockWidget) + * + * 4. Or create your own custom widget: + * const myWidget = { + * type: 'custom', + * title: 'My Custom Widget', + * content: '
This is my custom widget
', + * position: 0 + * } + * await widgetManager.addWidget(myWidget) + */ + +/** + * BEGINNER TIPS: + * + * 1. Widget Types: + * - 'clock': Shows current time + * - 'weather': Displays weather info + * - 'notes': Simple notepad + * - 'quick-links': Bookmark links + * - 'stats': Show statistics + * - 'todo': Task list + * - 'custom': Your own HTML + * + * 2. Widget Structure: + * { + * type: 'widget-type', // Required: Type of widget + * title: 'Widget Title', // Required: Display title + * config: { ... }, // Optional: Widget settings + * data: { ... }, // Optional: Widget data + * position: 0 // Optional: Display order + * } + * + * 3. Updating Widget Data: + * await widgetManager.updateWidget(widgetId, { + * data: { newData: 'value' } + * }) + * + * 4. Deleting a Widget: + * await widgetManager.deleteWidget(widgetId) + * + * 5. Widget Positions: + * - Widgets are displayed in a grid layout + * - Position determines the order (0, 1, 2, ...) + * - You can drag and drop to reorder + */ + +/** + * ADVANCED: Creating a Custom Widget Type + * + * To create a completely new widget type: + * + * 1. Add a new case in widget-manager.js renderWidgetContent(): + * case 'my-widget': + * return this.renderMyWidget(widget) + * + * 2. Create the render function: + * renderMyWidget(widget) { + * return ` + * + * ` + * } + * + * 3. Add styles in widgets.scss: + * .my-widget { + * padding: 1rem; + * h3 { color: #fff; } + * } + * + * 4. Use your new widget: + * const myCustomWidget = { + * type: 'my-widget', + * title: 'My Widget', + * data: { + * title: 'Hello', + * content: 'This is my custom widget type!' + * } + * } + */ + +// Export all examples as a collection +export const widgetExamples = { + clock: createSimpleClockWidget, + notes: createNotesWidget, + todo: createTodoWidget, + quickLinks: createQuickLinksWidget, + stats: createStatsWidget, + weather: createWeatherWidget, + custom: createCustomWidget +} + +export default widgetExamples diff --git a/app/src/js/widget-manager.js b/app/src/js/widget-manager.js new file mode 100644 index 0000000..8c748d3 --- /dev/null +++ b/app/src/js/widget-manager.js @@ -0,0 +1,483 @@ +/** + * Widget Manager - Handles dashboard widgets + * Provides functionality to create, manage, and render customizable widgets + */ + +class WidgetManager { + constructor() { + this.widgets = [] + this.container = null + this.apiBaseUrl = '/api/widgets' + this.userId = 'default' + this.draggedWidget = null + } + + /** + * Initialize the widget manager + */ + async init(containerId = 'widgets-container') { + this.container = document.getElementById(containerId) + if (!this.container) { + console.error(`Widget container #${containerId} not found`) + return + } + + await this.loadWidgets() + this.renderWidgets() + this.setupEventListeners() + } + + /** + * Load widgets from the server + */ + async loadWidgets() { + try { + const response = await fetch(`${this.apiBaseUrl}?userId=${this.userId}`) + const data = await response.json() + if (data.success) { + this.widgets = data.widgets + } + } catch (error) { + console.error('Failed to load widgets:', error) + } + } + + /** + * Add a new widget + */ + async addWidget(widgetConfig) { + try { + const response = await fetch(this.apiBaseUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userId: this.userId, + widget: widgetConfig + }) + }) + const data = await response.json() + if (data.success) { + this.widgets.push(data.widget) + this.renderWidgets() + return data.widget + } + } catch (error) { + console.error('Failed to add widget:', error) + } + } + + /** + * Update an existing widget + */ + async updateWidget(widgetId, updates) { + try { + const response = await fetch(`${this.apiBaseUrl}/${widgetId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + userId: this.userId, + widget: updates + }) + }) + const data = await response.json() + if (data.success) { + const index = this.widgets.findIndex(w => w.id === widgetId) + if (index !== -1) { + this.widgets[index] = data.widget + this.renderWidgets() + } + } + } catch (error) { + console.error('Failed to update widget:', error) + } + } + + /** + * Delete a widget + */ + async deleteWidget(widgetId) { + try { + const response = await fetch(`${this.apiBaseUrl}/${widgetId}?userId=${this.userId}`, { + method: 'DELETE' + }) + const data = await response.json() + if (data.success) { + this.widgets = this.widgets.filter(w => w.id !== widgetId) + this.renderWidgets() + } + } catch (error) { + console.error('Failed to delete widget:', error) + } + } + + /** + * Render all widgets + */ + renderWidgets() { + if (!this.container) return + + this.container.innerHTML = '' + + // Sort widgets by position + const sortedWidgets = [...this.widgets].sort((a, b) => { + return (a.position || 0) - (b.position || 0) + }) + + sortedWidgets.forEach(widget => { + const widgetElement = this.createWidgetElement(widget) + this.container.appendChild(widgetElement) + }) + } + + /** + * Create a widget DOM element + */ + createWidgetElement(widget) { + const widgetDiv = document.createElement('div') + widgetDiv.className = `widget widget-${widget.type}` + widgetDiv.id = widget.id + widgetDiv.draggable = true + widgetDiv.dataset.widgetId = widget.id + + // Widget header + const header = document.createElement('div') + header.className = 'widget-header' + header.innerHTML = ` +Custom widget content
' + default: + return 'Unknown widget type
' + } + } + + /** + * Clock widget renderer + */ + renderClockWidget(widget) { + const now = new Date() + const timeString = now.toLocaleTimeString(widget.config?.locale || 'en-US', { + hour: '2-digit', + minute: '2-digit', + second: widget.config?.showSeconds ? '2-digit' : undefined + }) + const dateString = now.toLocaleDateString(widget.config?.locale || 'en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }) + + return ` + + ` + } + + /** + * Weather widget renderer + */ + renderWeatherWidget(widget) { + const weather = widget.data || { + temp: '--', + condition: 'Loading...', + location: widget.config?.location || 'Unknown' + } + + return ` + + ` + } + + /** + * Notes widget renderer + */ + renderNotesWidget(widget) { + const notes = widget.data?.notes || '' + return ` + + ` + } + + /** + * Quick links widget renderer + */ + renderQuickLinksWidget(widget) { + const links = widget.data?.links || [] + const linksHtml = links.map(link => ` + + + ${link.title} + + `).join('') + + return ` + + ` + } + + /** + * Stats widget renderer + */ + renderStatsWidget(widget) { + const stats = widget.data?.stats || [] + const statsHtml = stats.map(stat => ` +