feat(dashboard): implement reactive tracker-dashboard integration

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)
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-11-02 10:35:35 +11:00
parent d3c1f0a137
commit 339413a6fa
4 changed files with 140 additions and 88 deletions
+129
View File
@@ -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
*/