feat(dashboard): implement smart widget collision and category-aware layout

Complete dashboard v2 improvements for better UX and visual consistency:

**1. Push-Aside Drag/Drop (dragDrop.js)**
- Replace swap/revert logic with intelligent reflow algorithm
- When widgets collide on drag, automatically push overlapping widgets down
- All affected widgets repositioned after reflow completes
- Eliminates widget overlap issues

**2. Unified Widget Styling (style.css)**
- Add consistent .rpg-widget container styling for all widgets
- Background: rgba(0,0,0,0.2) for visual separation
- Border-left: 3px highlight for category identification
- Box-shadow and border-radius for depth and polish
- Maintain individual widget decorative styles

**3. Logical Default Layout (defaultLayout.js)**
- Reorganize widgets into semantic clusters with clear comments:
  - USER CLUSTER (top): userInfo → userStats → userMood + userAttributes
  - SCENE CLUSTER (middle): calendar + weather → temp + clock → location
  - SOCIAL CLUSTER (bottom): presentCharacters
- userInfo widget now at top (y=0) as expected
- All positions use rem units for responsive scaling

**4. Category-Aware Auto-Layout (dashboardManager.js)**
- Implement sortWidgetsByCategory() with priority ordering:
  user → scene → social → inventory
- Within user category, specific ordering:
  userInfo → userStats → userMood → userAttributes
- Add preserveOrder option to gridEngine.autoLayout()
- Auto-arrange now uses logical grouping instead of random bin-packing

**5. Multi-Tab Auto-Distribution (dashboardManager.js)**
- Add estimateLayoutHeight() to detect when content exceeds threshold
- Implement distributeWidgetsByCategory() for automatic tab creation:
  - "Status" tab: user + scene widgets
  - "Social" tab: social widgets (if any)
  - "Inventory" tab: inventory widgets (if any)
- Each tab gets category-aware auto-layout
- 80rem height threshold for single-tab limit

**6. Widget Category Metadata (widgets/)**
- Add category field to all widget definitions:
  - userInfo, userStats, userMood, userAttributes: 'user'
  - calendar, weather, temperature, clock, location: 'scene'
  - presentCharacters: 'social'
  - inventory: 'inventory'

**7. Integration Improvements (dashboardIntegration.js)**
- Set default layout on initialization for reset functionality
- Add reset layout button to dashboard header
- Wire up reset button event handler

**8. Core State Management (index.js)**
- Add getInfoBoxData() and setInfoBoxData() to state API
- Ensure info box data persists across sessions

**Technical Details:**
- Rem units throughout for 1080p→4K→mobile responsive scaling
- Reflow algorithm leverages existing gridEngine collision detection
- Category-aware sorting preserves logical relationships
- Multi-tab distribution prevents single-page scroll fatigue
- All changes maintain backwards compatibility with existing layouts

Fixes dashboard issues after rem unit conversion introduced massive positioning bugs.
Users reported widgets overlapping on drag, visual inconsistency, and random auto-arrange behavior.

Related: Epic 2 (Dashboard v2), Phase 3.2
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-10-23 18:06:44 +11:00
parent aeb3ad1b9b
commit b3a86d4609
9 changed files with 434 additions and 138 deletions
+51 -17
View File
@@ -24,8 +24,9 @@ export function generateDefaultDashboard() {
gridConfig: {
// Columns calculated dynamically by GridEngine (2-4 based on panel width)
// Mobile: always 2, Desktop: 2-4 based on width
rowHeight: 80,
gap: 12,
columns: 2, // Default to 2 columns (will be recalculated on init)
rowHeight: 5, // rem units for responsive scaling (1080p → 4K → mobile)
gap: 0.75, // rem units (scales with screen DPI)
snapToGrid: true,
showGrid: true
},
@@ -37,25 +38,56 @@ export function generateDefaultDashboard() {
icon: '📊',
order: 0,
widgets: [
// Row 1: User Stats (full width)
// === USER CLUSTER (Top) ===
// Row 0: User Info (avatar, name, level) - AT TOP
{
id: 'widget-userinfo',
type: 'userInfo',
x: 0,
y: 0,
w: 2,
h: 1,
config: {}
},
// Row 1-2: User Stats (health/energy bars)
{
id: 'widget-userstats',
type: 'userStats',
x: 0,
y: 0,
y: 1,
w: 2,
h: 3,
h: 2,
config: {
showClassicStats: true,
statBarStyle: 'gradient'
statBarGradient: true
}
},
// Row 2: Calendar (left) + Weather (right)
// Row 3-4: User Mood (left) + User Attributes (right)
{
id: 'widget-usermood',
type: 'userMood',
x: 0,
y: 3,
w: 1,
h: 1,
config: {}
},
{
id: 'widget-userattributes',
type: 'userAttributes',
x: 1,
y: 3,
w: 1,
h: 2,
config: {}
},
// === SCENE CLUSTER (Middle) ===
// Row 5-6: Calendar (left) + Weather (right)
{
id: 'widget-calendar',
type: 'calendar',
x: 0,
y: 3,
y: 5,
w: 1,
h: 2,
config: {}
@@ -64,19 +96,19 @@ export function generateDefaultDashboard() {
id: 'widget-weather',
type: 'weather',
x: 1,
y: 3,
y: 5,
w: 1,
h: 2,
config: {
compact: false
}
},
// Row 3: Temperature (left) + Clock (right)
// Row 7-8: Temperature (left) + Clock (right)
{
id: 'widget-temperature',
type: 'temperature',
x: 0,
y: 5,
y: 7,
w: 1,
h: 2,
config: {
@@ -87,29 +119,31 @@ export function generateDefaultDashboard() {
id: 'widget-clock',
type: 'clock',
x: 1,
y: 5,
y: 7,
w: 1,
h: 2,
config: {
format: 'digital'
}
},
// Row 4: Location (full width)
// Row 9-10: Location (full width)
{
id: 'widget-location',
type: 'location',
x: 0,
y: 7,
y: 9,
w: 2,
h: 2,
config: {}
},
// Row 5-6: Present Characters (full width)
// === SOCIAL CLUSTER (Bottom) ===
// Row 11-13: Present Characters (full width)
{
id: 'widget-presentchars',
type: 'presentCharacters',
x: 0,
y: 9,
y: 11,
w: 2,
h: 3,
config: {