From 1d82695d74c7f1c4732cd3342fe1917621964821 Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Thu, 6 Nov 2025 20:56:32 +1100 Subject: [PATCH] feat: make auto-arrange and sort use default layout positions Modified autoLayoutCurrentTab() and autoLayoutWidgets() to detect when widgets match the default layout and apply the exact positions from defaultLayout.js instead of using the gridEngine.autoLayout packing algorithm. This ensures that: - "Reset Layout" button uses default positions - "Auto Arrange All Widgets" button uses default positions (if widgets match) - "Sort Current Page" button uses default positions (if widgets match) All three operations now produce identical layouts for the default widget set. Changes: - Added tryApplyDefaultLayoutToTab() helper for single tab layout - Added tryApplyDefaultLayout() helper for all tabs layout - Modified autoLayoutCurrentTab() to try default layout first - Modified autoLayoutWidgets() to try default layout first - Falls back to gridEngine.autoLayout for custom widgets Fixes responsive dashboard layout consistency. --- src/systems/dashboard/dashboardManager.js | 284 +++++++++++++++++----- 1 file changed, 220 insertions(+), 64 deletions(-) diff --git a/src/systems/dashboard/dashboardManager.js b/src/systems/dashboard/dashboardManager.js index 9b0720e..c0003d9 100644 --- a/src/systems/dashboard/dashboardManager.js +++ b/src/systems/dashboard/dashboardManager.js @@ -1629,6 +1629,146 @@ export class DashboardManager { console.log(`[DashboardManager] Reset ${resetCount} widgets to default sizes`); } + /** + * Try to apply default layout positions to current tab + * + * Checks if the current tab's widgets match the default layout and applies + * the default positions if they do. This ensures "Sort Current Page" produces + * the same layout as "Reset Layout" for default widgets. + * + * @param {Object} tab - Tab to apply default layout to + * @param {Object} options - Layout options + * @returns {boolean} True if default layout was applied, false otherwise + */ + tryApplyDefaultLayoutToTab(tab, options = {}) { + if (!this.defaultLayout || !this.defaultLayout.tabs) { + console.log('[DashboardManager] No default layout available'); + return false; + } + + // Find matching default tab by ID + const defaultTab = this.defaultLayout.tabs.find(t => t.id === tab.id); + if (!defaultTab) { + console.log(`[DashboardManager] No default layout for tab "${tab.name}" (${tab.id})`); + return false; + } + + // Check if widgets match (same types, possibly different IDs) + const currentTypes = tab.widgets.map(w => w.type).sort(); + const defaultTypes = defaultTab.widgets.map(w => w.type).sort(); + + if (currentTypes.length !== defaultTypes.length || + !currentTypes.every((type, i) => type === defaultTypes[i])) { + console.log('[DashboardManager] Tab widgets do not match default layout (custom widgets present)'); + return false; + } + + console.log('[DashboardManager] Applying default layout positions to current tab'); + + // Reset widget sizes to defaults (unless explicitly disabled) + if (options.resetSizes !== false) { + this.resetWidgetSizesToDefault(tab.widgets); + } + + // Apply default positions to each widget + tab.widgets.forEach(widget => { + const defaultWidget = defaultTab.widgets.find(w => w.type === widget.type); + if (defaultWidget) { + widget.x = defaultWidget.x; + widget.y = defaultWidget.y; + // Size is already set by resetWidgetSizesToDefault + console.log(`[DashboardManager] Set ${widget.type} to default position (${widget.x}, ${widget.y})`); + } + }); + + return true; + } + + /** + * Try to apply default layout to all tabs + * + * Checks if the current dashboard widgets match the default layout and applies + * the default positions if they do. This ensures "Auto Arrange" produces + * the same layout as "Reset Layout" for default widgets. + * + * @param {Object} options - Layout options + * @returns {boolean} True if default layout was applied, false otherwise + */ + tryApplyDefaultLayout(options = {}) { + if (!this.defaultLayout || !this.defaultLayout.tabs) { + console.log('[DashboardManager] No default layout available'); + return false; + } + + // Check if tabs match default layout + if (this.dashboard.tabs.length !== this.defaultLayout.tabs.length) { + console.log('[DashboardManager] Tab count does not match default layout'); + return false; + } + + // Check if all tabs and widgets match + for (let i = 0; i < this.dashboard.tabs.length; i++) { + const tab = this.dashboard.tabs[i]; + const defaultTab = this.defaultLayout.tabs.find(t => t.id === tab.id); + + if (!defaultTab) { + console.log(`[DashboardManager] No default tab found for "${tab.name}" (${tab.id})`); + return false; + } + + const currentTypes = tab.widgets.map(w => w.type).sort(); + const defaultTypes = defaultTab.widgets.map(w => w.type).sort(); + + if (currentTypes.length !== defaultTypes.length || + !currentTypes.every((type, j) => type === defaultTypes[j])) { + console.log(`[DashboardManager] Tab "${tab.name}" widgets do not match default layout`); + return false; + } + } + + console.log('[DashboardManager] Applying default layout positions to all tabs'); + + // Gather all widgets from all tabs + const allWidgets = []; + this.dashboard.tabs.forEach(tab => { + if (tab.widgets && tab.widgets.length > 0) { + allWidgets.push(...tab.widgets); + } + }); + + // Reset widget sizes to defaults (unless explicitly disabled) + if (options.resetSizes !== false) { + this.resetWidgetSizesToDefault(allWidgets); + } + + // Apply default positions to each tab + this.dashboard.tabs.forEach(tab => { + const defaultTab = this.defaultLayout.tabs.find(t => t.id === tab.id); + if (defaultTab) { + tab.widgets.forEach(widget => { + const defaultWidget = defaultTab.widgets.find(w => w.type === widget.type); + if (defaultWidget) { + widget.x = defaultWidget.x; + widget.y = defaultWidget.y; + console.log(`[DashboardManager] Set ${widget.type} to default position (${widget.x}, ${widget.y})`); + } + }); + } + }); + + // Re-render tabs and switch to first tab + this.renderTabs(); + if (this.dashboard.tabs.length > 0) { + this.switchTab(this.dashboard.tabs[0].id); + } + + // Save layout + this.triggerAutoSave(); + + console.log('[DashboardManager] Default layout applied successfully'); + return true; + } + /** * Auto-layout widgets on current tab only * Sorts and arranges widgets on the current tab to maximize space usage @@ -1654,40 +1794,48 @@ export class DashboardManager { console.log(`[DashboardManager] Laying out ${currentTab.widgets.length} widgets on tab "${currentTab.name}"`); - // Reset widget sizes to defaults (unless explicitly disabled) - if (options.resetSizes !== false) { - this.resetWidgetSizesToDefault(currentTab.widgets); - } + // Check if we can use default layout positions + const useDefaultLayout = this.tryApplyDefaultLayoutToTab(currentTab, options); - // Sort widgets by category for better organization - const sortedWidgets = this.sortWidgetsByCategory(currentTab.widgets); + if (!useDefaultLayout) { + // Fallback to traditional auto-layout + console.log('[DashboardManager] Using gridEngine.autoLayout (custom widgets or no default layout)'); - // Update tab's widgets array with sorted order - currentTab.widgets = sortedWidgets; - - // Store current widget dimensions before auto-layout - const dimensionsBefore = new Map(); - currentTab.widgets.forEach(widget => { - dimensionsBefore.set(widget.id, { w: widget.w, h: widget.h }); - }); - - // Auto-layout widgets on the current tab - this.gridEngine.autoLayout(currentTab.widgets, { - preserveOrder: options.preserveOrder !== false - }); - - // Call onResize handlers for widgets whose dimensions changed - // This allows widgets to update internal layouts (e.g., User Attributes grid columns) - currentTab.widgets.forEach(widget => { - const before = dimensionsBefore.get(widget.id); - if (before && (before.w !== widget.w || before.h !== widget.h)) { - const widgetData = this.widgets.get(widget.id); - if (widgetData?.definition?.onResize && widgetData.element) { - console.log(`[DashboardManager] Calling onResize for ${widget.type} (${before.w}x${before.h} → ${widget.w}x${widget.h})`); - widgetData.definition.onResize(widgetData.element, widget.w, widget.h); - } + // Reset widget sizes to defaults (unless explicitly disabled) + if (options.resetSizes !== false) { + this.resetWidgetSizesToDefault(currentTab.widgets); } - }); + + // Sort widgets by category for better organization + const sortedWidgets = this.sortWidgetsByCategory(currentTab.widgets); + + // Update tab's widgets array with sorted order + currentTab.widgets = sortedWidgets; + + // Store current widget dimensions before auto-layout + const dimensionsBefore = new Map(); + currentTab.widgets.forEach(widget => { + dimensionsBefore.set(widget.id, { w: widget.w, h: widget.h }); + }); + + // Auto-layout widgets on the current tab + this.gridEngine.autoLayout(currentTab.widgets, { + preserveOrder: options.preserveOrder !== false + }); + + // Call onResize handlers for widgets whose dimensions changed + // This allows widgets to update internal layouts (e.g., User Attributes grid columns) + currentTab.widgets.forEach(widget => { + const before = dimensionsBefore.get(widget.id); + if (before && (before.w !== widget.w || before.h !== widget.h)) { + const widgetData = this.widgets.get(widget.id); + if (widgetData?.definition?.onResize && widgetData.element) { + console.log(`[DashboardManager] Calling onResize for ${widget.type} (${before.w}x${before.h} → ${widget.w}x${widget.h})`); + widgetData.definition.onResize(widgetData.element, widget.w, widget.h); + } + } + }); + } // Re-render all widgets with new positions this.clearGrid(); @@ -1719,42 +1867,50 @@ export class DashboardManager { console.log('[DashboardManager] ===== AUTO-LAYOUT WIDGETS CALLED ====='); console.log('[DashboardManager] Auto-layout widgets requested'); - // 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); + // Check if we can use default layout + const useDefaultLayout = this.tryApplyDefaultLayout(options); + + if (!useDefaultLayout) { + // Fallback to traditional auto-layout + console.log('[DashboardManager] Using traditional auto-layout (custom widgets or no default layout)'); + + // 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; } - }); - if (allWidgets.length === 0) { - console.warn('[DashboardManager] No widgets to auto-layout'); - return; + console.log(`[DashboardManager] Total widgets to layout: ${allWidgets.length}`); + + // Reset widget sizes to defaults (unless explicitly disabled) + if (options.resetSizes !== false) { + this.resetWidgetSizesToDefault(allWidgets); + } + + // Smart category-aware sorting BEFORE auto-layout + const widgetsToLayout = this.sortWidgetsByCategory(allWidgets); + + // Calculate estimated height to determine if multi-tab distribution is needed + const estimatedHeight = this.estimateLayoutHeight(widgetsToLayout); + const heightThreshold = 80; // rem - reasonable max height for single tab + + console.log('[DashboardManager] Estimated height:', estimatedHeight + 'rem', 'Threshold:', heightThreshold + 'rem'); + + // 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); + + // distributeWidgetsByCategory handles rendering and tab switching } - - console.log(`[DashboardManager] Total widgets to layout: ${allWidgets.length}`); - - // Reset widget sizes to defaults (unless explicitly disabled) - if (options.resetSizes !== false) { - this.resetWidgetSizesToDefault(allWidgets); - } - - // Smart category-aware sorting BEFORE auto-layout - const widgetsToLayout = this.sortWidgetsByCategory(allWidgets); - - // Calculate estimated height to determine if multi-tab distribution is needed - const estimatedHeight = this.estimateLayoutHeight(widgetsToLayout); - const heightThreshold = 80; // rem - reasonable max height for single tab - - console.log('[DashboardManager] Estimated height:', estimatedHeight + 'rem', 'Threshold:', heightThreshold + 'rem'); - - // 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); - - // distributeWidgetsByCategory handles rendering and tab switching } /**