feat(dashboard): add confirmations, fix UX issues, and optimize grid rendering
BREAKING CHANGE: applyDashboardConfig() now accepts optional second parameter for optimization Add auto-arrange confirmation dialog: - Add warning popup before auto-arranging widgets across all tabs - Follows same pattern as reset layout confirmation - Prevents accidental destructive operations Fix text selection interference: - Apply user-select: none to entire .rpg-widget in edit mode - Previously only applied to .rpg-widget-content - Prevents text selection when dragging widgets, especially in attribute boxes - Improves drag interaction on both desktop and mobile Fix edit controls visibility: - Add CSS rule to completely hide edit controls outside edit mode - Controls now properly hidden when not in edit mode - Settings button (⚙) kept for future widget configuration Fix input disabling in edit mode: - Call disableContentEditing() after tab switches in onTabChange() - Call disableContentEditing() in syncAllControls() - Ensures text fields remain non-editable in edit mode after all operations Fix resize grid background rendering: - Copy drag handler grid logic EXACTLY to resize handler - Use gridEngine.calculateGridHeight(widgets) instead of manual calculation - Add proper remToPixels() conversions for gap and rowHeight - Pass widgets array through initWidget() and startResize() - Grid now renders correctly during resize, matching drag behavior Optimize layout operations: - Add skipInitialSwitch option to applyDashboardConfig() - Prevents redundant clearGrid() calls during resetLayout() - Defers rendering until after layout calculations complete Files modified: - dashboardIntegration.js: Auto-arrange confirmation - dashboardManager.js: skipInitialSwitch option, input disabling, widgets array - editModeManager.js: Input disabling in syncAllControls() - resizeHandler.js: Grid rendering fixes with proper unit conversions - style.css: Text selection prevention, edit controls visibility
This commit is contained in:
@@ -246,11 +246,21 @@ function setupDashboardEventListeners(dependencies) {
|
|||||||
// Auto-layout button
|
// Auto-layout button
|
||||||
const autoLayoutBtn = document.querySelector('#rpg-dashboard-auto-layout');
|
const autoLayoutBtn = document.querySelector('#rpg-dashboard-auto-layout');
|
||||||
if (autoLayoutBtn) {
|
if (autoLayoutBtn) {
|
||||||
autoLayoutBtn.addEventListener('click', () => {
|
autoLayoutBtn.addEventListener('click', async () => {
|
||||||
if (dashboardManager) {
|
if (dashboardManager) {
|
||||||
|
const confirmed = await showConfirmDialog({
|
||||||
|
title: 'Auto-Arrange All Widgets?',
|
||||||
|
message: 'This will reorganize all widgets across all tabs and may change their positions. This action cannot be undone.',
|
||||||
|
variant: 'warning',
|
||||||
|
confirmText: 'Auto-Arrange',
|
||||||
|
cancelText: 'Cancel'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
console.log('[RPG Companion] Auto-layout button clicked');
|
console.log('[RPG Companion] Auto-layout button clicked');
|
||||||
dashboardManager.autoLayoutWidgets();
|
dashboardManager.autoLayoutWidgets();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -705,7 +705,7 @@ export class DashboardManager {
|
|||||||
}, {
|
}, {
|
||||||
minW: definition.minSize.w,
|
minW: definition.minSize.w,
|
||||||
minH: definition.minSize.h
|
minH: definition.minSize.h
|
||||||
});
|
}, allWidgets);
|
||||||
|
|
||||||
// Add edit mode controls
|
// Add edit mode controls
|
||||||
if (this.editManager) {
|
if (this.editManager) {
|
||||||
@@ -1104,6 +1104,11 @@ export class DashboardManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable content editing if in edit mode
|
||||||
|
if (this.editManager && this.editManager.isEditMode) {
|
||||||
|
this.editManager.disableContentEditing();
|
||||||
|
}
|
||||||
|
|
||||||
this.notifyChange('tabChanged', { tabId });
|
this.notifyChange('tabChanged', { tabId });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1283,8 +1288,10 @@ export class DashboardManager {
|
|||||||
/**
|
/**
|
||||||
* Apply dashboard configuration
|
* Apply dashboard configuration
|
||||||
* @param {Object} config - Dashboard configuration
|
* @param {Object} config - Dashboard configuration
|
||||||
|
* @param {Object} options - Optional parameters
|
||||||
|
* @param {boolean} options.skipInitialSwitch - Skip switching to first tab (caller will handle)
|
||||||
*/
|
*/
|
||||||
applyDashboardConfig(config) {
|
applyDashboardConfig(config, options = {}) {
|
||||||
console.log('[DashboardManager] Applying dashboard config');
|
console.log('[DashboardManager] Applying dashboard config');
|
||||||
|
|
||||||
// Migrate emoji icons to Font Awesome
|
// Migrate emoji icons to Font Awesome
|
||||||
@@ -1330,8 +1337,8 @@ export class DashboardManager {
|
|||||||
this.dashboard.defaultTab = this.dashboard.tabs[0].id;
|
this.dashboard.defaultTab = this.dashboard.tabs[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to first tab
|
// Switch to first tab (unless caller will handle it)
|
||||||
if (config.tabs.length > 0) {
|
if (!options.skipInitialSwitch && config.tabs.length > 0) {
|
||||||
this.switchTab(config.tabs[0].id);
|
this.switchTab(config.tabs[0].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1418,7 +1425,8 @@ export class DashboardManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.persistence.resetToDefault(this.defaultLayout);
|
await this.persistence.resetToDefault(this.defaultLayout);
|
||||||
this.applyDashboardConfig(this.defaultLayout);
|
// Skip initial switch in applyDashboardConfig since we'll switch after layout calculations
|
||||||
|
this.applyDashboardConfig(this.defaultLayout, { skipInitialSwitch: true });
|
||||||
|
|
||||||
// Reset all widgets to default sizes
|
// Reset all widgets to default sizes
|
||||||
const allWidgets = [];
|
const allWidgets = [];
|
||||||
|
|||||||
@@ -482,6 +482,9 @@ export class EditModeManager {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Ensure content editing is disabled for all widgets
|
||||||
|
this.disableContentEditing();
|
||||||
|
|
||||||
console.log('[EditModeManager] Synced controls for', widgets.length, 'widgets');
|
console.log('[EditModeManager] Synced controls for', widgets.length, 'widgets');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,8 +71,9 @@ export class ResizeHandler {
|
|||||||
* @param {Object} widget - Widget data object
|
* @param {Object} widget - Widget data object
|
||||||
* @param {Function} onResizeEnd - Callback when resize completes (widget, newW, newH, newX, newY)
|
* @param {Function} onResizeEnd - Callback when resize completes (widget, newW, newH, newX, newY)
|
||||||
* @param {Object} constraints - Size constraints {minW, minH, maxW, maxH}
|
* @param {Object} constraints - Size constraints {minW, minH, maxW, maxH}
|
||||||
|
* @param {Array<Object>} widgets - All widgets (for grid height calculation)
|
||||||
*/
|
*/
|
||||||
initWidget(element, widget, onResizeEnd, constraints = {}) {
|
initWidget(element, widget, onResizeEnd, constraints = {}, widgets = []) {
|
||||||
// Create resize handles
|
// Create resize handles
|
||||||
const handles = this.createResizeHandles();
|
const handles = this.createResizeHandles();
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ export class ResizeHandler {
|
|||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.startResize(e, handleType, element, widget, onResizeEnd, widgetConstraints);
|
this.startResize(e, handleType, element, widget, onResizeEnd, widgetConstraints, widgets);
|
||||||
};
|
};
|
||||||
|
|
||||||
const touchStartHandler = (e) => {
|
const touchStartHandler = (e) => {
|
||||||
@@ -123,7 +124,7 @@ export class ResizeHandler {
|
|||||||
this.touchTimer = setTimeout(() => {
|
this.touchTimer = setTimeout(() => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.startResize(e.touches[0], handleType, element, widget, onResizeEnd, widgetConstraints);
|
this.startResize(e.touches[0], handleType, element, widget, onResizeEnd, widgetConstraints, widgets);
|
||||||
}, this.options.touchDelay);
|
}, this.options.touchDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -255,8 +256,9 @@ export class ResizeHandler {
|
|||||||
* @param {Object} widget - Widget data
|
* @param {Object} widget - Widget data
|
||||||
* @param {Function} onResizeEnd - Callback when resize completes
|
* @param {Function} onResizeEnd - Callback when resize completes
|
||||||
* @param {Object} constraints - Size constraints
|
* @param {Object} constraints - Size constraints
|
||||||
|
* @param {Array<Object>} widgets - All widgets (for grid height calculation)
|
||||||
*/
|
*/
|
||||||
startResize(e, handleType, element, widget, onResizeEnd, constraints) {
|
startResize(e, handleType, element, widget, onResizeEnd, constraints, widgets = []) {
|
||||||
// Create dimension overlay
|
// Create dimension overlay
|
||||||
const overlay = this.createDimensionOverlay();
|
const overlay = this.createDimensionOverlay();
|
||||||
|
|
||||||
@@ -273,7 +275,8 @@ export class ResizeHandler {
|
|||||||
overlay,
|
overlay,
|
||||||
isResizing: true,
|
isResizing: true,
|
||||||
onResizeEnd,
|
onResizeEnd,
|
||||||
constraints
|
constraints,
|
||||||
|
widgets
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add event listeners
|
// Add event listeners
|
||||||
@@ -551,13 +554,18 @@ export class ResizeHandler {
|
|||||||
showGridOverlay() {
|
showGridOverlay() {
|
||||||
if (this.gridOverlay) return;
|
if (this.gridOverlay) return;
|
||||||
|
|
||||||
|
// Calculate actual grid height based on widget positions (returns rem)
|
||||||
|
const widgets = this.resizeState?.widgets || [];
|
||||||
|
const gridHeightRem = this.gridEngine.calculateGridHeight(widgets);
|
||||||
|
const gridHeightPx = this.gridEngine.remToPixels(gridHeightRem);
|
||||||
|
|
||||||
this.gridOverlay = document.createElement('div');
|
this.gridOverlay = document.createElement('div');
|
||||||
this.gridOverlay.className = 'grid-overlay';
|
this.gridOverlay.className = 'grid-overlay';
|
||||||
this.gridOverlay.style.position = 'absolute';
|
this.gridOverlay.style.position = 'absolute';
|
||||||
this.gridOverlay.style.top = '0';
|
this.gridOverlay.style.top = '0';
|
||||||
this.gridOverlay.style.left = '0';
|
this.gridOverlay.style.left = '0';
|
||||||
this.gridOverlay.style.width = '100%';
|
this.gridOverlay.style.width = '100%';
|
||||||
this.gridOverlay.style.height = '100%';
|
this.gridOverlay.style.height = gridHeightPx + 'px';
|
||||||
this.gridOverlay.style.pointerEvents = 'none';
|
this.gridOverlay.style.pointerEvents = 'none';
|
||||||
this.gridOverlay.style.zIndex = '9999';
|
this.gridOverlay.style.zIndex = '9999';
|
||||||
|
|
||||||
@@ -584,19 +592,25 @@ export class ResizeHandler {
|
|||||||
highlightGridCells(x, y, w, h) {
|
highlightGridCells(x, y, w, h) {
|
||||||
if (!this.gridOverlay) return;
|
if (!this.gridOverlay) return;
|
||||||
|
|
||||||
|
// Clear previous highlights
|
||||||
this.gridOverlay.innerHTML = '';
|
this.gridOverlay.innerHTML = '';
|
||||||
|
|
||||||
const totalGaps = this.gridEngine.gap * (this.gridEngine.columns + 1);
|
// Convert rem to pixels for calculations
|
||||||
|
const gapPx = this.gridEngine.remToPixels(this.gridEngine.gap);
|
||||||
|
const rowHeightPx = this.gridEngine.remToPixels(this.gridEngine.rowHeight);
|
||||||
|
|
||||||
|
// Calculate column width in pixels
|
||||||
|
const totalGaps = gapPx * (this.gridEngine.columns + 1);
|
||||||
const colWidth = (this.gridEngine.containerWidth - totalGaps) / this.gridEngine.columns;
|
const colWidth = (this.gridEngine.containerWidth - totalGaps) / this.gridEngine.columns;
|
||||||
|
|
||||||
for (let row = y; row < y + h; row++) {
|
for (let row = y; row < y + h; row++) {
|
||||||
for (let col = x; col < x + w; col++) {
|
for (let col = x; col < x + w; col++) {
|
||||||
const cell = document.createElement('div');
|
const cell = document.createElement('div');
|
||||||
cell.style.position = 'absolute';
|
cell.style.position = 'absolute';
|
||||||
cell.style.left = (col * (colWidth + this.gridEngine.gap) + this.gridEngine.gap) + 'px';
|
cell.style.left = (col * (colWidth + gapPx) + gapPx) + 'px';
|
||||||
cell.style.top = (row * (this.gridEngine.rowHeight + this.gridEngine.gap) + this.gridEngine.gap) + 'px';
|
cell.style.top = (row * (rowHeightPx + gapPx) + gapPx) + 'px';
|
||||||
cell.style.width = colWidth + 'px';
|
cell.style.width = colWidth + 'px';
|
||||||
cell.style.height = this.gridEngine.rowHeight + 'px';
|
cell.style.height = rowHeightPx + 'px';
|
||||||
cell.style.backgroundColor = 'rgba(78, 204, 163, 0.3)';
|
cell.style.backgroundColor = 'rgba(78, 204, 163, 0.3)';
|
||||||
cell.style.border = '2px solid rgba(78, 204, 163, 0.6)';
|
cell.style.border = '2px solid rgba(78, 204, 163, 0.6)';
|
||||||
cell.style.borderRadius = '4px';
|
cell.style.borderRadius = '4px';
|
||||||
|
|||||||
@@ -1803,7 +1803,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Prevent text selection in edit mode (especially important for mobile) */
|
/* Prevent text selection in edit mode (especially important for mobile) */
|
||||||
.edit-mode .rpg-widget-content {
|
.edit-mode .rpg-widget {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
@@ -1811,6 +1811,11 @@ body:has(.rpg-panel.rpg-position-left) #sheld {
|
|||||||
-webkit-touch-callout: none; /* Prevent iOS callout menu */
|
-webkit-touch-callout: none; /* Prevent iOS callout menu */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide edit controls completely when not in edit mode */
|
||||||
|
.rpg-dashboard-container:not(.edit-mode) .widget-edit-controls {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide resize handles when widgets are locked */
|
/* Hide resize handles when widgets are locked */
|
||||||
.widgets-locked .resize-handles {
|
.widgets-locked .resize-handles {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user