From 1078313775a996480d1e040cc21049bc843a28c0 Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Thu, 23 Oct 2025 11:11:20 +1100 Subject: [PATCH] feat(dashboard): add dashboard template and integration module (Phase 3.1) - Create dashboardTemplate.html with dashboard container structure - Dashboard header with tab navigation and control buttons - Edit mode toggle, add widget, export/import layout buttons - Add widget modal for selecting and adding widgets - Widget configuration modal for widget settings - Dashboard grid container for widget placement - Create dashboardIntegration.js to handle dashboard initialization - Initialize dashboard system and register all widgets - Load dashboard template and inject into panel - Set up event listeners for edit mode, add widget, export/import - Create default layout with all core widgets - Provide refreshDashboard() for updating widgets after data changes - Support for fallback inline template if file load fails --- src/systems/dashboard/dashboardIntegration.js | 359 ++++++++++++++++++ src/systems/dashboard/dashboardTemplate.html | 98 +++++ 2 files changed, 457 insertions(+) create mode 100644 src/systems/dashboard/dashboardIntegration.js create mode 100644 src/systems/dashboard/dashboardTemplate.html diff --git a/src/systems/dashboard/dashboardIntegration.js b/src/systems/dashboard/dashboardIntegration.js new file mode 100644 index 0000000..cdb1595 --- /dev/null +++ b/src/systems/dashboard/dashboardIntegration.js @@ -0,0 +1,359 @@ +/** + * Dashboard Integration Module + * + * Handles initialization and integration of the v2 dashboard system + * with the main RPG Companion extension. + */ + +import { extensionName } from '../../core/config.js'; +import { extensionSettings } from '../../core/state.js'; +import { saveSettings } from '../../core/persistence.js'; +import { renderExtensionTemplateAsync } from '../../../../../extensions.js'; +import { DashboardManager } from './dashboardManager.js'; +import { WidgetRegistry } from './core/widgetRegistry.js'; + +// Widget imports +import { registerUserStatsWidget } from './widgets/userStatsWidget.js'; +import { registerCalendarWidget, registerWeatherWidget, registerTemperatureWidget, registerClockWidget, registerLocationWidget } from './widgets/infoBoxWidgets.js'; +import { registerPresentCharactersWidget } from './widgets/presentCharactersWidget.js'; +import { registerInventoryWidget } from './widgets/inventoryWidget.js'; + +// Global dashboard manager instance +let dashboardManager = null; + +/** + * Get the dashboard manager instance + */ +export function getDashboardManager() { + return dashboardManager; +} + +/** + * Initialize the dashboard system + * @param {Object} dependencies - Dependencies from main extension + */ +export async function initializeDashboard(dependencies) { + console.log('[RPG Companion] Initializing Dashboard v2 System...'); + + try { + // Load dashboard template + const dashboardHtml = await loadDashboardTemplate(); + + // Find or create dashboard container in the panel + const panelContent = document.querySelector('#rpg-panel-content'); + if (!panelContent) { + console.error('[RPG Companion] Panel content container not found'); + return null; + } + + // Insert dashboard HTML (replacing old content-box) + const contentBox = panelContent.querySelector('.rpg-content-box'); + if (contentBox) { + // Replace old content-box with dashboard + contentBox.replaceWith(createDashboardContainer(dashboardHtml)); + } else { + // If no content-box, insert dashboard after dice display + const diceDisplay = panelContent.querySelector('#rpg-dice-display'); + if (diceDisplay) { + diceDisplay.insertAdjacentHTML('afterend', dashboardHtml); + } else { + panelContent.insertAdjacentHTML('afterbegin', dashboardHtml); + } + } + + // Create widget registry + const registry = new WidgetRegistry(); + + // Register all widgets + registerAllWidgets(registry, dependencies); + + // Initialize dashboard manager + const container = document.querySelector('#rpg-dashboard-container'); + if (!container) { + console.error('[RPG Companion] Dashboard container not found after template load'); + return null; + } + + dashboardManager = new DashboardManager(container, { + registry, + autoSave: true, + onChange: (data) => { + // Handle dashboard changes + console.log('[RPG Companion] Dashboard changed:', data); + if (dependencies.onDashboardChange) { + dependencies.onDashboardChange(data); + } + } + }); + + // Initialize the dashboard + await dashboardManager.init(); + + // Set up dashboard event listeners + setupDashboardEventListeners(dependencies); + + console.log('[RPG Companion] Dashboard v2 initialized successfully'); + return dashboardManager; + + } catch (error) { + console.error('[RPG Companion] Failed to initialize dashboard:', error); + return null; + } +} + +/** + * Load dashboard template HTML + */ +async function loadDashboardTemplate() { + try { + // Try to load from dashboardTemplate.html + const html = await renderExtensionTemplateAsync(extensionName, 'src/systems/dashboard/dashboardTemplate'); + return html; + } catch (error) { + console.warn('[RPG Companion] Could not load dashboard template, using inline HTML'); + // Fallback to inline template + return getInlineDashboardTemplate(); + } +} + +/** + * Create dashboard container div + */ +function createDashboardContainer(dashboardHtml) { + const wrapper = document.createElement('div'); + wrapper.innerHTML = dashboardHtml; + return wrapper.firstElementChild; +} + +/** + * Get inline dashboard template (fallback) + */ +function getInlineDashboardTemplate() { + return ` +
+
+
+
+
+
+ + + + + +
+
+
+
+ `; +} + +/** + * Register all available widgets + */ +function registerAllWidgets(registry, dependencies) { + console.log('[RPG Companion] Registering widgets...'); + + // Core widgets + registerUserStatsWidget(registry, dependencies); + registerPresentCharactersWidget(registry, dependencies); + registerInventoryWidget(registry, dependencies); + + // Info Box modular widgets + registerCalendarWidget(registry, dependencies); + registerWeatherWidget(registry, dependencies); + registerTemperatureWidget(registry, dependencies); + registerClockWidget(registry, dependencies); + registerLocationWidget(registry, dependencies); + + console.log(`[RPG Companion] Registered ${registry.getAll().length} widgets`); +} + +/** + * Set up dashboard event listeners + */ +function setupDashboardEventListeners(dependencies) { + // Edit mode toggle + const editModeBtn = document.querySelector('#rpg-dashboard-edit-mode'); + if (editModeBtn) { + editModeBtn.addEventListener('click', () => { + if (dashboardManager) { + const isEditMode = dashboardManager.editModeManager.isEditMode(); + dashboardManager.editModeManager.setEditMode(!isEditMode); + } + }); + } + + // Add widget button + const addWidgetBtn = document.querySelector('#rpg-dashboard-add-widget'); + if (addWidgetBtn) { + addWidgetBtn.addEventListener('click', () => { + if (dashboardManager) { + showAddWidgetDialog(dashboardManager); + } + }); + } + + // Export layout button + const exportBtn = document.querySelector('#rpg-dashboard-export-layout'); + if (exportBtn) { + exportBtn.addEventListener('click', () => { + if (dashboardManager) { + dashboardManager.exportLayout(); + } + }); + } + + // Import layout button + const importBtn = document.querySelector('#rpg-dashboard-import-layout'); + const importFile = document.querySelector('#rpg-dashboard-import-file'); + + if (importBtn && importFile) { + importBtn.addEventListener('click', () => { + importFile.click(); + }); + + importFile.addEventListener('change', (e) => { + const file = e.target.files[0]; + if (file && dashboardManager) { + dashboardManager.importLayout(file); + importFile.value = ''; // Reset file input + } + }); + } +} + +/** + * Show add widget dialog + */ +function showAddWidgetDialog(manager) { + // Get all available widgets + const registry = manager.registry; + const widgets = registry.getAll(); + + // Create widget cards HTML + const widgetCardsHtml = widgets.map(([type, definition]) => ` +
+
${definition.icon}
+
${definition.name}
+
${definition.description}
+ +
+ `).join(''); + + // Show modal + const modal = document.querySelector('#rpg-add-widget-modal'); + if (!modal) { + console.warn('[RPG Companion] Add widget modal not found'); + return; + } + + const widgetSelector = modal.querySelector('#rpg-widget-selector'); + if (widgetSelector) { + widgetSelector.innerHTML = widgetCardsHtml; + + // Attach add button handlers + widgetSelector.querySelectorAll('.rpg-widget-card-add').forEach(btn => { + btn.addEventListener('click', () => { + const widgetType = btn.dataset.widgetType; + const activeTab = manager.tabManager.getActiveTabId(); + + manager.addWidget(widgetType, activeTab); + hideModal('rpg-add-widget-modal'); + }); + }); + } + + modal.style.display = 'flex'; + + // Set up modal close handlers + modal.querySelectorAll('[data-close="add-widget"]').forEach(btn => { + btn.onclick = () => hideModal('rpg-add-widget-modal'); + }); + + // Close on backdrop click + modal.onclick = (e) => { + if (e.target === modal) { + hideModal('rpg-add-widget-modal'); + } + }; +} + +/** + * Hide modal by ID + */ +function hideModal(modalId) { + const modal = document.querySelector(`#${modalId}`); + if (modal) { + modal.style.display = 'none'; + } +} + +/** + * Create default dashboard layout + */ +export function createDefaultLayout(manager) { + if (!manager) { + console.warn('[RPG Companion] Cannot create default layout - manager not initialized'); + return; + } + + console.log('[RPG Companion] Creating default dashboard layout...'); + + const mainTab = manager.tabManager.getActiveTabId(); + + // Add widgets with default positions + // Row 1: User Stats (left) + Calendar + Weather + Temperature + Clock (right) + manager.addWidget('userStats', mainTab, { x: 0, y: 0, w: 6, h: 4 }); + manager.addWidget('calendar', mainTab, { x: 6, y: 0, w: 2, h: 2 }); + manager.addWidget('weather', mainTab, { x: 8, y: 0, w: 3, h: 2 }); + manager.addWidget('temperature', mainTab, { x: 11, y: 0, w: 2, h: 2 }); + + // Row 2: Location (top right) + Clock (bottom right) + manager.addWidget('location', mainTab, { x: 6, y: 2, w: 6, h: 2 }); + manager.addWidget('clock', mainTab, { x: 10, y: 2, w: 2, h: 2 }); + + // Row 3: Present Characters + manager.addWidget('presentCharacters', mainTab, { x: 0, y: 4, w: 12, h: 4 }); + + // Row 4: Inventory + manager.addWidget('inventory', mainTab, { x: 0, y: 8, w: 12, h: 6 }); + + console.log('[RPG Companion] Default layout created'); +} + +/** + * Refresh all widgets (called after data updates) + */ +export function refreshDashboard() { + if (dashboardManager) { + // Get all active widgets and re-render them + const widgets = dashboardManager.getAllWidgets(); + widgets.forEach(widget => { + dashboardManager.renderWidget(widget.id); + }); + } +} + +/** + * Destroy dashboard instance + */ +export function destroyDashboard() { + if (dashboardManager) { + console.log('[RPG Companion] Destroying dashboard...'); + // Clean up would go here + dashboardManager = null; + } +} diff --git a/src/systems/dashboard/dashboardTemplate.html b/src/systems/dashboard/dashboardTemplate.html new file mode 100644 index 0000000..f2c51db --- /dev/null +++ b/src/systems/dashboard/dashboardTemplate.html @@ -0,0 +1,98 @@ + +
+ +
+
+ +
+
+ +
+ + + + + + + + + + +
+
+ + +
+ +
+ + + + + + +