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:
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 = `
|
||||
<div class="rpg-widget-empty-state">
|
||||
<p>⚠️ ${displayName} disabled</p>
|
||||
<p style="font-size: 0.85em; opacity: 0.7;">
|
||||
Enable in <a href="#" class="rpg-open-tracker-settings">Tracker Settings</a>
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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';
|
||||
|
||||
@@ -99,21 +99,11 @@ export function registerUserStatsWidget(registry, dependencies) {
|
||||
});
|
||||
}).join('');
|
||||
|
||||
// Show message if no stats are enabled
|
||||
const content = visibleStats.length > 0
|
||||
? progressBarsHtml
|
||||
: `<div class="rpg-widget-empty-state">
|
||||
<p>⚠️ No stats enabled</p>
|
||||
<p style="font-size: 0.85em; opacity: 0.7;">
|
||||
Enable stats in <a href="#" class="rpg-open-tracker-settings">Tracker Settings</a>
|
||||
</p>
|
||||
</div>`;
|
||||
|
||||
// Render HTML
|
||||
const html = `
|
||||
<div class="rpg-stats-content rpg-stats-modular">
|
||||
<div class="rpg-stats-grid">
|
||||
${content}
|
||||
${progressBarsHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user