From c4485971fab556687b44314909966bbd294bdff4 Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Thu, 23 Oct 2025 19:32:27 +1100 Subject: [PATCH] fix(dashboard): fix persistent px values, auto-layout, widget loss, gaps, and tabs This commit resolves 6 critical dashboard issues reported by user: 1. **Persistent px values causing 264rem widget heights** - Root cause: state.js had hardcoded rowHeight: 80, gap: 12 (px) - Root cause: index.js double-loaded layout, overwriting migration - Fix: Changed state.js gridConfig to rem units (5, 0.75) - Fix: Removed redundant applyDashboardConfig in index.js - Fix: Added migration in layoutPersistence.js for old saves - Dashboard now uses rem consistently throughout 2. **Auto-layout on first load** - Added auto-layout in loadLayout() when no saved layout exists - Prevents overlap from hardcoded default positions - Saves auto-laid-out result as initial layout 3. **Reset layout causes overlap** - Added auto-layout loop in resetLayout() after applying config - Each tab auto-lays out to prevent widget overlap 4. **Auto-arrange loses inventory/social widgets** - Fixed autoLayoutWidgets to gather ALL widgets from ALL tabs - Previously only gathered current tab, lost other tabs - Now always uses multi-tab distribution to preserve all widgets 5. **Auto-arrange leaves 2x2 gaps** - Added compact pass in gridEngine.js after bin-packing - Moves widgets upward to fill gaps - Eliminates empty spaces at bottom of layout 6. **Tabs not compact (icon-only)** - Updated tab styling: icons only, names show on hover - Allows more tabs in compact space - min-width: 2.5rem, larger icon size Also added debug logging to track config values through initialization. Fixes refresh sizing bug, reset overlap, widget loss, and layout gaps. --- index.js | 12 +-- src/core/state.js | 4 +- src/systems/dashboard/dashboardManager.js | 96 +++++++++++++--------- src/systems/dashboard/gridEngine.js | 36 +++++++- src/systems/dashboard/layoutPersistence.js | 11 +++ style.css | 13 ++- 6 files changed, 121 insertions(+), 51 deletions(-) diff --git a/index.js b/index.js index 5e5a3f3..d8546ab 100644 --- a/index.js +++ b/index.js @@ -559,15 +559,9 @@ async function initUI() { console.log('[RPG Companion] Dashboard v2 initialized successfully'); console.log('[RPG Companion] Manager instance:', manager); - // Check if this is first time OR if dashboard is empty - create default layout - if (!extensionSettings.dashboard || !extensionSettings.dashboard.tabs || extensionSettings.dashboard.tabs.length === 0) { - console.log('[RPG Companion] Creating default dashboard layout...'); - createDefaultLayout(manager); - } else { - console.log('[RPG Companion] Loading saved dashboard layout with', extensionSettings.dashboard.tabs.length, 'tabs'); - // Apply the saved layout to the manager - manager.applyDashboardConfig(extensionSettings.dashboard); - } + // Dashboard manager already loaded its layout in init() via loadLayout() + // No need to load again here - that would overwrite the migrated values + console.log('[RPG Companion] Dashboard initialized and layout loaded via layoutPersistence'); } else { console.warn('[RPG Companion] Dashboard initialization returned null, falling back to legacy rendering'); throw new Error('Dashboard initialization failed'); diff --git a/src/core/state.js b/src/core/state.js index ee16064..ac4801a 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -87,8 +87,8 @@ export let extensionSettings = { // Columns calculated dynamically by GridEngine (2-4 based on panel width) // Mobile (≤1000px screen): always 2 columns // Desktop (>1000px screen): 2-4 columns based on panel width - rowHeight: 80, // Pixels per row - gap: 12, // Gap between widgets (px) + rowHeight: 5, // rem units for responsive scaling + gap: 0.75, // rem units (was 12px) snapToGrid: true, // Auto-snap enabled showGrid: true // Show grid lines in edit mode }, diff --git a/src/systems/dashboard/dashboardManager.js b/src/systems/dashboard/dashboardManager.js index 6de4f10..0a6144f 100644 --- a/src/systems/dashboard/dashboardManager.js +++ b/src/systems/dashboard/dashboardManager.js @@ -57,6 +57,12 @@ export class DashboardManager { ...config }; + console.log('[DashboardManager] Constructor config:', { + rowHeight: this.config.rowHeight, + gap: this.config.gap, + columns: this.config.columns + }); + // Dashboard state this.currentTabId = null; this.widgets = new Map(); // widgetId => { widget data, element, tab } @@ -1033,7 +1039,7 @@ export class DashboardManager { * @returns {Object} Dashboard configuration */ getDashboardConfig() { - return { + const config = { version: 2, gridConfig: { columns: this.config.columns, @@ -1049,6 +1055,12 @@ export class DashboardManager { })), defaultTab: this.dashboard.defaultTab }; + console.log('[DashboardManager] getDashboardConfig() returning:', { + rowHeight: config.gridConfig.rowHeight, + gap: config.gridConfig.gap, + columns: config.gridConfig.columns + }); + return config; } /** @@ -1124,8 +1136,19 @@ export class DashboardManager { if (saved) { this.applyDashboardConfig(saved); } else if (this.defaultLayout) { - console.log('[DashboardManager] No saved layout, using default'); + console.log('[DashboardManager] No saved layout, using default with auto-layout'); this.applyDashboardConfig(this.defaultLayout); + + // Auto-layout each tab to prevent overlap (default positions may not fit screen) + this.dashboard.tabs.forEach(tab => { + if (tab.widgets && tab.widgets.length > 0) { + console.log(`[DashboardManager] Auto-laying out default tab "${tab.name}" (${tab.widgets.length} widgets)`); + this.gridEngine.autoLayout(tab.widgets, { preserveOrder: true }); + } + }); + + // Save the auto-laid-out default as the initial saved layout + await this.saveLayout(true); } } catch (error) { console.error('[DashboardManager] Failed to load layout:', error); @@ -1172,10 +1195,25 @@ export class DashboardManager { await this.persistence.resetToDefault(this.defaultLayout); this.applyDashboardConfig(this.defaultLayout); + // Auto-layout each tab to prevent overlap (default positions may have changed) + this.dashboard.tabs.forEach(tab => { + if (tab.widgets && tab.widgets.length > 0) { + console.log(`[DashboardManager] Auto-laying out tab "${tab.name}" (${tab.widgets.length} widgets)`); + this.gridEngine.autoLayout(tab.widgets, { preserveOrder: true }); + } + }); + // Force re-render tabs this.renderTabs(); - console.log('[DashboardManager] Reset complete'); + // Re-render current tab's widgets + if (this.currentTabId) { + this.switchTab(this.currentTabId); + } else if (this.dashboard.tabs.length > 0) { + this.switchTab(this.dashboard.tabs[0].id); + } + + console.log('[DashboardManager] Reset complete with auto-layout'); } /** @@ -1199,15 +1237,24 @@ export class DashboardManager { autoLayoutWidgets(options = {}) { console.log('[DashboardManager] Auto-layout widgets requested'); - // Get current tab - const currentTab = this.tabManager.getTab(this.currentTabId); - if (!currentTab || !currentTab.widgets || currentTab.widgets.length === 0) { + // Gather ALL widgets from ALL tabs (don't lose inventory, social, etc.) + const allWidgets = []; + this.dashboard.tabs.forEach(tab => { + if (tab.widgets && tab.widgets.length > 0) { + console.log(`[DashboardManager] Gathering ${tab.widgets.length} widgets from tab "${tab.name}"`); + allWidgets.push(...tab.widgets); + } + }); + + if (allWidgets.length === 0) { console.warn('[DashboardManager] No widgets to auto-layout'); return; } + console.log(`[DashboardManager] Total widgets to layout: ${allWidgets.length}`); + // Smart category-aware sorting BEFORE auto-layout - const widgetsToLayout = this.sortWidgetsByCategory(currentTab.widgets); + const widgetsToLayout = this.sortWidgetsByCategory(allWidgets); // Calculate estimated height to determine if multi-tab distribution is needed const estimatedHeight = this.estimateLayoutHeight(widgetsToLayout); @@ -1215,37 +1262,12 @@ export class DashboardManager { console.log('[DashboardManager] Estimated height:', estimatedHeight + 'rem', 'Threshold:', heightThreshold + 'rem'); - // If widgets fit comfortably, use single-tab auto-layout - if (estimatedHeight <= heightThreshold) { - console.log('[DashboardManager] Using single-tab auto-layout'); + // Always use multi-tab distribution when we have many widgets + // This preserves all widgets (inventory, social, etc.) + console.log('[DashboardManager] Using multi-tab distribution to preserve all widgets'); + this.distributeWidgetsByCategory(widgetsToLayout); - // Run auto-layout algorithm on pre-sorted widgets - // (gridEngine will preserve this logical order instead of sorting by area) - this.gridEngine.autoLayout(widgetsToLayout, { preserveOrder: true }); - - // Update tab widgets with new positions - currentTab.widgets = widgetsToLayout; - } else { - // Too many widgets - distribute across multiple tabs by category - console.log('[DashboardManager] Height exceeds threshold, using multi-tab distribution'); - this.distributeWidgetsByCategory(widgetsToLayout); - return; // distributeWidgetsByCategory handles rendering - } - - // Re-render all widgets with new positions - this.clearGrid(); - widgetsToLayout.forEach(widget => { - const definition = this.registry.get(widget.type); - if (definition) { - this.renderWidget(widget, definition); - } - }); - - console.log('[DashboardManager] Auto-layout complete, re-rendered widgets'); - - // Save changes - this.triggerAutoSave(); - this.notifyChange('autoLayoutApplied', { tabId: this.currentTabId }); + // distributeWidgetsByCategory handles rendering and tab switching } /** diff --git a/src/systems/dashboard/gridEngine.js b/src/systems/dashboard/gridEngine.js index fbee389..0b2beb1 100644 --- a/src/systems/dashboard/gridEngine.js +++ b/src/systems/dashboard/gridEngine.js @@ -528,7 +528,41 @@ export class GridEngine { console.log(`[GridEngine] Auto-layout positioned: ${widget.id} at (${pos.x},${pos.y}) size ${targetW}×${targetH}`); }); - console.log('[GridEngine] Auto-layout complete'); + // Compact pass: Move widgets up to fill gaps + console.log('[GridEngine] Compacting layout to fill gaps...'); + let compactedCount = 0; + + // Sort widgets by current Y position (process top to bottom) + const sortedForCompact = [...sorted].sort((a, b) => a.y - b.y); + + sortedForCompact.forEach(widget => { + const originalY = widget.y; + + // Try to move widget up as far as possible + for (let tryY = 0; tryY < originalY; tryY++) { + // Clear current position from occupied map + for (let row = originalY; row < originalY + widget.h; row++) { + for (let col = widget.x; col < widget.x + widget.w; col++) { + occupied.delete(`${col},${row}`); + } + } + + // Check if new position is free + if (isFree(widget.x, tryY, widget.w, widget.h)) { + // Move widget up + widget.y = tryY; + markOccupied(widget, widget.x, tryY, widget.w, widget.h); + compactedCount++; + console.log(`[GridEngine] Compacted ${widget.id} from y=${originalY} to y=${tryY}`); + break; + } else { + // Re-mark original position and continue + markOccupied(widget, widget.x, originalY, widget.w, widget.h); + } + } + }); + + console.log(`[GridEngine] Auto-layout complete (compacted ${compactedCount} widgets)`); return widgets; } } diff --git a/src/systems/dashboard/layoutPersistence.js b/src/systems/dashboard/layoutPersistence.js index 6da95cf..896b38a 100644 --- a/src/systems/dashboard/layoutPersistence.js +++ b/src/systems/dashboard/layoutPersistence.js @@ -148,6 +148,17 @@ export class LayoutPersistence { const layoutData = JSON.parse(stored); + // Migrate old pixel values to rem units + if (layoutData.gridConfig) { + // Check if we have old pixel values (rowHeight > 20 is likely pixels) + if (layoutData.gridConfig.rowHeight > 20) { + console.log('[LayoutPersistence] Migrating old px values to rem'); + layoutData.gridConfig.rowHeight = 5; // 80px → 5rem + layoutData.gridConfig.gap = 0.75; // 12px → 0.75rem + console.log('[LayoutPersistence] Converted gridConfig: rowHeight=5rem, gap=0.75rem'); + } + } + // Validate loaded data if (!this.validateDashboard(layoutData)) { throw new Error('Loaded layout is invalid'); diff --git a/style.css b/style.css index 63b7f3f..0ae6f8e 100644 --- a/style.css +++ b/style.css @@ -1083,8 +1083,10 @@ body:has(.rpg-panel.rpg-position-left) #sheld { .rpg-dashboard-tab { display: inline-flex; align-items: center; + justify-content: center; gap: 0.3rem; - padding: 0.4rem 0.7rem; + padding: 0.5rem; + min-width: 2.5rem; font-size: 0.75rem; border: 1px solid transparent; background: transparent; @@ -1108,11 +1110,18 @@ body:has(.rpg-panel.rpg-position-left) #sheld { } .rpg-tab-icon { - font-size: 0.9rem; + font-size: 1.1rem; } .rpg-tab-name { font-size: 0.75rem; + display: none; +} + +/* Show name on hover */ +.rpg-dashboard-tab:hover .rpg-tab-name { + display: inline; + margin-left: 0.3rem; } .rpg-dashboard-btn {