From 339413a6fad3cfd883440c99a3f01c629ad67d44 Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Sun, 2 Nov 2025 10:35:35 +1100 Subject: [PATCH] feat(dashboard): implement reactive tracker-dashboard integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced surface-level "disabled" messages with true reactive integration. When tracker editor saves config changes, dashboard now automatically updates without page reload - removing disabled widgets and refreshing remaining ones with new field names/settings. **Event-Based Architecture:** - trackerEditor.js dispatches 'rpg:trackerConfigChanged' custom event - dashboardManager.js subscribes to event and reacts to changes - Decoupled, extensible, browser-native event system **Dashboard Reactive Methods:** - onTrackerConfigChanged(config): Main handler coordinating refresh flow - removeDisabledWidgets(config): Removes widgets with disabled fields - Cleans up DOM, drag/resize handlers, state - Removes from tab.widgets arrays - shouldWidgetBeRemoved(type, config): Decision logic per widget type - calendar → remove if date disabled - weather → remove if weather disabled - temperature → remove if temperature disabled - clock → remove if time disabled - location → remove if location disabled - userStats → remove only if ALL stats disabled - presentCharacters → remove if thoughts disabled - refreshAllWidgets(): Re-renders all remaining widgets with new config **Widget Auto-Removal Flow:** 1. User disables field in tracker editor 2. Clicks "Save & Apply" 3. Event fires → dashboard receives notification 4. Disabled widgets removed from all tabs 5. Affected tabs auto-layout to fill space 6. Remaining widgets re-render with new config 7. Layout saved automatically **Removed Surface-Level Bandaid:** - Deleted checkFieldEnabled() from infoBoxWidgets.js (-36 lines) - Removed all checkFieldEnabled() calls from widget renders (-25 lines) - Removed empty state message from userStatsWidget.js (-8 lines) - Removed tracker settings link handler (-7 lines) - Widgets no longer show "⚠️ Field disabled" messages - Dashboard handles removal elegantly instead **Result:** True reactive integration. Disable "Arousal" → instantly disappears from all userStats widgets. Disable "Date" → calendar widget removed and tab auto-layouts. Rename "Health" to "HP" → updates instantly everywhere. All changes happen immediately without page reload. Files modified: - src/systems/dashboard/dashboardManager.js (+129 lines) - src/systems/ui/trackerEditor.js (+11 lines) - src/systems/dashboard/widgets/infoBoxWidgets.js (-67 lines) - src/systems/dashboard/widgets/userStatsWidget.js (-21 lines) --- src/systems/dashboard/dashboardManager.js | 129 ++++++++++++++++++ .../dashboard/widgets/infoBoxWidgets.js | 67 --------- .../dashboard/widgets/userStatsWidget.js | 21 +-- src/systems/ui/trackerEditor.js | 11 +- 4 files changed, 140 insertions(+), 88 deletions(-) diff --git a/src/systems/dashboard/dashboardManager.js b/src/systems/dashboard/dashboardManager.js index 0f5030f..a67f3e5 100644 --- a/src/systems/dashboard/dashboardManager.js +++ b/src/systems/dashboard/dashboardManager.js @@ -219,6 +219,12 @@ export class DashboardManager { // Measure container width and set up responsive sizing this.setupContainerSizing(); + // Listen for tracker config changes (reactive integration) + document.addEventListener('rpg:trackerConfigChanged', (e) => { + console.log('[DashboardManager] Tracker config changed, refreshing widgets'); + this.onTrackerConfigChanged(e.detail.config); + }); + // Render tab navigation this.renderTabs(); @@ -1664,6 +1670,129 @@ export class DashboardManager { }); } + /** + * Handle tracker configuration changes from editor + * Removes disabled widgets and refreshes remaining widgets + * @param {Object} config - New tracker configuration + */ + onTrackerConfigChanged(config) { + console.log('[DashboardManager] Processing tracker config changes...'); + + // Step 1: Remove widgets that are now disabled + const removedWidgets = this.removeDisabledWidgets(config); + + // Step 2: If widgets were removed, auto-layout affected tabs + if (removedWidgets.length > 0) { + const affectedTabs = new Set(removedWidgets.map(w => w.tabId)); + affectedTabs.forEach(tabId => { + const tab = this.tabManager.getTab(tabId); + if (tab && tab.widgets && tab.widgets.length > 0) { + console.log(`[DashboardManager] Auto-layouting tab ${tabId} after widget removal`); + this.gridEngine.autoLayout(tab.widgets, { preserveOrder: true }); + } + }); + } + + // Step 3: Refresh all remaining widgets (re-render with new config) + this.refreshAllWidgets(); + + // Step 4: Save layout changes + this.triggerAutoSave(); + + console.log('[DashboardManager] Tracker config refresh complete'); + } + + /** + * Remove widgets that should no longer be shown based on config + * @param {Object} config - Tracker configuration + * @returns {Array} Array of removed widget info {widgetId, tabId, type} + */ + removeDisabledWidgets(config) { + const removed = []; + + // Iterate through all tabs + this.dashboard.tabs.forEach(tab => { + if (!tab.widgets) return; + + // Find widgets to remove + const toRemove = tab.widgets.filter(widget => + this.shouldWidgetBeRemoved(widget.type, config) + ); + + // Remove each widget + toRemove.forEach(widget => { + console.log(`[DashboardManager] Removing disabled widget: ${widget.type} (${widget.id})`); + + // If widget is in current tab and rendered, clean it up + if (tab.id === this.currentTabId) { + const widgetData = this.widgets.get(widget.id); + if (widgetData) { + const definition = this.registry.get(widget.type); + if (definition && definition.onRemove) { + definition.onRemove(widgetData.element, widget.config); + } + this.dragHandler.destroyWidget(widgetData.element); + this.resizeHandler.destroyWidget(widgetData.element); + widgetData.element.remove(); + this.widgets.delete(widget.id); + } + } + + removed.push({ + widgetId: widget.id, + tabId: tab.id, + type: widget.type + }); + }); + + // Remove from tab's widget array + tab.widgets = tab.widgets.filter(widget => + !toRemove.some(r => r.id === widget.id) + ); + }); + + console.log(`[DashboardManager] Removed ${removed.length} disabled widgets`); + return removed; + } + + /** + * Determine if widget should be removed based on tracker config + * @param {string} widgetType - Widget type + * @param {Object} config - Tracker configuration + * @returns {boolean} True if widget should be removed + */ + shouldWidgetBeRemoved(widgetType, config) { + const rules = { + 'calendar': () => config.infoBox?.widgets?.date?.enabled === false, + 'weather': () => config.infoBox?.widgets?.weather?.enabled === false, + 'temperature': () => config.infoBox?.widgets?.temperature?.enabled === false, + 'clock': () => config.infoBox?.widgets?.time?.enabled === false, + 'location': () => config.infoBox?.widgets?.location?.enabled === false, + 'userStats': () => { + const customStats = config.userStats?.customStats || []; + return customStats.filter(s => s.enabled).length === 0; + }, + 'presentCharacters': () => config.presentCharacters?.thoughts?.enabled === false + }; + + const rule = rules[widgetType]; + return rule ? rule() : false; + } + + /** + * Refresh all rendered widgets (re-render with current data) + */ + refreshAllWidgets() { + console.log('[DashboardManager] Refreshing all widgets...'); + this.widgets.forEach((widgetData) => { + const definition = this.registry.get(widgetData.widget.type); + if (definition && widgetData.element) { + this.renderWidgetContent(widgetData.element, widgetData.widget, definition); + } + }); + console.log('[DashboardManager] All widgets refreshed'); + } + /** * Destroy dashboard and cleanup */ diff --git a/src/systems/dashboard/widgets/infoBoxWidgets.js b/src/systems/dashboard/widgets/infoBoxWidgets.js index 7496752..6a517b8 100644 --- a/src/systems/dashboard/widgets/infoBoxWidgets.js +++ b/src/systems/dashboard/widgets/infoBoxWidgets.js @@ -12,43 +12,6 @@ * Users can arrange them independently or group them together. */ -/** - * Check if a field is enabled in trackerConfig and render disabled state if not - * @param {HTMLElement} container - Widget container - * @param {string} fieldName - Field name in trackerConfig (e.g., 'date', 'weather', 'temperature') - * @param {string} displayName - Display name for the field (e.g., 'Date', 'Weather') - * @param {Object} dependencies - Dependencies object - * @returns {boolean} True if enabled, false if disabled - */ -function checkFieldEnabled(container, fieldName, displayName, dependencies) { - const { getExtensionSettings } = dependencies; - const settings = getExtensionSettings(); - const trackerConfig = settings.trackerConfig?.infoBox; - const fieldEnabled = trackerConfig?.widgets?.[fieldName]?.enabled !== false; - - if (!fieldEnabled) { - container.innerHTML = ` -
-

⚠️ ${displayName} disabled

-

- Enable in Tracker Settings -

-
- `; - - // Handle tracker settings link - const link = container.querySelector('.rpg-open-tracker-settings'); - if (link) { - link.addEventListener('click', (e) => { - e.preventDefault(); - document.getElementById('rpg-open-tracker-editor')?.click(); - }); - } - } - - return fieldEnabled; -} - /** * Parse Info Box data from shared data source * @param {string} infoBoxText - Raw info box text @@ -236,12 +199,6 @@ export function registerCalendarWidget(registry, dependencies) { render(container, config = {}) { const { getInfoBoxData } = dependencies; - - // Check if date field is enabled in trackerConfig - if (!checkFieldEnabled(container, 'date', 'Date', dependencies)) { - return; - } - const data = parseInfoBoxData(getInfoBoxData()); const monthShort = data.month ? data.month.substring(0, 3).toUpperCase() : 'MON'; @@ -330,12 +287,6 @@ export function registerWeatherWidget(registry, dependencies) { render(container, config = {}) { const { getInfoBoxData } = dependencies; - - // Check if weather field is enabled in trackerConfig - if (!checkFieldEnabled(container, 'weather', 'Weather', dependencies)) { - return; - } - const data = parseInfoBoxData(getInfoBoxData()); const weatherEmoji = data.weatherEmoji || '🌤️'; @@ -368,12 +319,6 @@ export function registerTemperatureWidget(registry, dependencies) { render(container, config = {}) { const { getInfoBoxData } = dependencies; - - // Check if temperature field is enabled in trackerConfig - if (!checkFieldEnabled(container, 'temperature', 'Temperature', dependencies)) { - return; - } - const data = parseInfoBoxData(getInfoBoxData()); const tempDisplay = data.temperature || '20°C'; @@ -415,12 +360,6 @@ export function registerClockWidget(registry, dependencies) { render(container, config = {}) { const { getInfoBoxData } = dependencies; - - // Check if time field is enabled in trackerConfig - if (!checkFieldEnabled(container, 'time', 'Time', dependencies)) { - return; - } - const data = parseInfoBoxData(getInfoBoxData()); const timeDisplay = data.timeEnd || data.timeStart || '12:00'; @@ -471,12 +410,6 @@ export function registerLocationWidget(registry, dependencies) { render(container, config = {}) { const { getInfoBoxData } = dependencies; - - // Check if location field is enabled in trackerConfig - if (!checkFieldEnabled(container, 'location', 'Location', dependencies)) { - return; - } - const data = parseInfoBoxData(getInfoBoxData()); const locationDisplay = data.location || 'Location'; diff --git a/src/systems/dashboard/widgets/userStatsWidget.js b/src/systems/dashboard/widgets/userStatsWidget.js index a0a5964..f668596 100644 --- a/src/systems/dashboard/widgets/userStatsWidget.js +++ b/src/systems/dashboard/widgets/userStatsWidget.js @@ -99,21 +99,11 @@ export function registerUserStatsWidget(registry, dependencies) { }); }).join(''); - // Show message if no stats are enabled - const content = visibleStats.length > 0 - ? progressBarsHtml - : `
-

⚠️ No stats enabled

-

- Enable stats in Tracker Settings -

-
`; - // Render HTML const html = `
- ${content} + ${progressBarsHtml}
`; @@ -122,15 +112,6 @@ export function registerUserStatsWidget(registry, dependencies) { // Attach event handlers attachEventHandlers(container, settings, onStatsChange); - - // Handle "Tracker Settings" link click - const trackerSettingsLink = container.querySelector('.rpg-open-tracker-settings'); - if (trackerSettingsLink) { - trackerSettingsLink.addEventListener('click', (e) => { - e.preventDefault(); - document.getElementById('rpg-open-tracker-editor')?.click(); - }); - } }, /** diff --git a/src/systems/ui/trackerEditor.js b/src/systems/ui/trackerEditor.js index 4e31561..4b96ef8 100644 --- a/src/systems/ui/trackerEditor.js +++ b/src/systems/ui/trackerEditor.js @@ -108,10 +108,19 @@ function applyTrackerConfig() { tempConfig = null; // Clear temp config saveSettings(); - // Re-render all trackers with new config + // Re-render all trackers with new config (v1 system - backward compat) renderUserStats(); renderInfoBox(); renderThoughts(); + + // Notify dashboard system of config changes (v2 system - reactive integration) + document.dispatchEvent(new CustomEvent('rpg:trackerConfigChanged', { + detail: { + config: extensionSettings.trackerConfig, + source: 'trackerEditor' + } + })); + console.log('[RPG Companion] Tracker config changed event dispatched'); } /**