diff --git a/src/systems/dashboard/dashboardIntegration.js b/src/systems/dashboard/dashboardIntegration.js index 366b43a..16d23f3 100644 --- a/src/systems/dashboard/dashboardIntegration.js +++ b/src/systems/dashboard/dashboardIntegration.js @@ -138,15 +138,12 @@ function getInlineDashboardTemplate() {
diff --git a/src/systems/dashboard/dragDrop.js b/src/systems/dashboard/dragDrop.js index a17fed4..0f53cdb 100644 --- a/src/systems/dashboard/dragDrop.js +++ b/src/systems/dashboard/dragDrop.js @@ -51,8 +51,9 @@ export class DragDropHandler { * @param {HTMLElement} element - Widget DOM element * @param {Object} widget - Widget data object * @param {Function} onDragEnd - Callback when drag completes (widget, newX, newY) + * @param {Array} widgets - All widgets (for collision detection) */ - initWidget(element, widget, onDragEnd) { + initWidget(element, widget, onDragEnd, widgets = []) { // Store handler reference for cleanup const dragHandle = element.querySelector('.drag-handle') || element; @@ -65,7 +66,7 @@ export class DragDropHandler { } e.preventDefault(); - this.startDrag(e, element, widget, onDragEnd); + this.startDrag(e, element, widget, onDragEnd, widgets); }; const touchStartHandler = (e) => { @@ -77,7 +78,7 @@ export class DragDropHandler { // Delay touch drag to allow scrolling this.touchTimer = setTimeout(() => { e.preventDefault(); - this.startDrag(e.touches[0], element, widget, onDragEnd); + this.startDrag(e.touches[0], element, widget, onDragEnd, widgets); }, this.options.touchDelay); }; @@ -129,8 +130,9 @@ export class DragDropHandler { * @param {HTMLElement} element - Element being dragged * @param {Object} widget - Widget data * @param {Function} onDragEnd - Callback when drag completes + * @param {Array} widgets - All widgets (for collision detection) */ - startDrag(e, element, widget, onDragEnd) { + startDrag(e, element, widget, onDragEnd, widgets = []) { // Calculate pointer offset from element top-left const rect = element.getBoundingClientRect(); const offsetX = e.clientX - rect.left; @@ -148,7 +150,10 @@ export class DragDropHandler { offsetY, ghost, isDragging: true, - onDragEnd + onDragEnd, + widgets, + originalX: widget.x, + originalY: widget.y }; // Change cursor @@ -263,7 +268,7 @@ export class DragDropHandler { endDrag() { if (!this.dragState) return; - const { element, widget, onDragEnd } = this.dragState; + const { element, widget, onDragEnd, widgets, originalX, originalY } = this.dragState; // Restore original element element.style.opacity = '1'; @@ -272,9 +277,51 @@ export class DragDropHandler { const dragHandle = element.querySelector('.drag-handle') || element; dragHandle.style.cursor = 'grab'; - // Call callback with new position - if (onDragEnd) { - onDragEnd(widget, widget.x, widget.y); + // Check for collision before committing + const otherWidgets = widgets.filter(w => w.id !== widget.id); + const collision = this.gridEngine.detectCollision(widget, otherWidgets); + + if (collision) { + // Find which widget we collided with + const collidedWidget = otherWidgets.find(other => { + return !( + widget.x + widget.w <= other.x || + widget.x >= other.x + other.w || + widget.y + widget.h <= other.y || + widget.y >= other.y + other.h + ); + }); + + // If same size, swap positions + if (collidedWidget && widget.w === collidedWidget.w && widget.h === collidedWidget.h) { + console.log('[DragDrop] Swapping positions with:', collidedWidget.id); + const tempX = collidedWidget.x; + const tempY = collidedWidget.y; + collidedWidget.x = widget.x; + collidedWidget.y = widget.y; + widget.x = tempX; + widget.y = tempY; + + // Call callback with swapped position + if (onDragEnd) { + onDragEnd(widget, widget.x, widget.y); + } + } else { + // Different sizes or multiple collisions - revert to original + console.warn('[DragDrop] Collision detected, reverting to original position'); + widget.x = originalX; + widget.y = originalY; + + // Call callback with original position (no change) + if (onDragEnd) { + onDragEnd(widget, widget.x, widget.y); + } + } + } else { + // No collision, commit new position + if (onDragEnd) { + onDragEnd(widget, widget.x, widget.y); + } } this.cleanup(); diff --git a/src/systems/dashboard/resizeHandler.js b/src/systems/dashboard/resizeHandler.js index 10fa1e5..dbbb03f 100644 --- a/src/systems/dashboard/resizeHandler.js +++ b/src/systems/dashboard/resizeHandler.js @@ -284,8 +284,7 @@ export class ResizeHandler { const deltaX = clientX - startX; const deltaY = clientY - startY; - // Get column/row size in pixels - this.gridEngine.updateContainerWidth(); + // Get column/row size in pixels (containerWidth already set by ResizeObserver in DashboardManager) const totalGaps = this.gridEngine.gap * (this.gridEngine.columns + 1); const colWidth = (this.gridEngine.containerWidth - totalGaps) / this.gridEngine.columns; const rowHeight = this.gridEngine.rowHeight; diff --git a/style.css b/style.css index dee543e..3e31cb1 100644 --- a/style.css +++ b/style.css @@ -1073,12 +1073,56 @@ body:has(.rpg-panel.rpg-position-left) #sheld { flex-wrap: wrap; } -.rpg-dashboard-btn { +.rpg-dashboard-tabs { + display: flex; + align-items: center; + gap: 0.25rem; + flex-wrap: wrap; +} + +.rpg-dashboard-tab { display: inline-flex; align-items: center; gap: 0.3rem; - padding: 0.4rem 0.6rem; + padding: 0.4rem 0.7rem; font-size: 0.75rem; + border: 1px solid transparent; + background: transparent; + color: var(--SmartThemeBodyColor); + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + opacity: 0.6; +} + +.rpg-dashboard-tab:hover { + background: var(--SmartThemeBlurTintColor); + opacity: 0.9; +} + +.rpg-dashboard-tab.active { + background: var(--SmartThemeBlurTintColor); + border-color: var(--SmartThemeBorderColor); + opacity: 1; + font-weight: 600; +} + +.rpg-tab-icon { + font-size: 0.9rem; +} + +.rpg-tab-name { + font-size: 0.75rem; +} + +.rpg-dashboard-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem; + width: 2rem; + height: 2rem; + font-size: 0.9rem; border: 1px solid var(--SmartThemeBorderColor); background: var(--SmartThemeBlurTintColor); color: var(--SmartThemeBodyColor); @@ -1093,7 +1137,7 @@ body:has(.rpg-panel.rpg-position-left) #sheld { } .rpg-dashboard-btn i { - font-size: 0.85rem; + font-size: 0.9rem; } .rpg-dashboard-grid { @@ -1102,6 +1146,19 @@ body:has(.rpg-panel.rpg-position-left) #sheld { min-height: 200px; } +/* Hide resize handles by default */ +.resize-handles { + opacity: 0; + pointer-events: none; + transition: opacity 0.2s; +} + +/* Show resize handles in edit mode */ +.edit-mode .resize-handles { + opacity: 1; + pointer-events: auto; +} + /* ======================================== DASHBOARD V2 WIDGET STYLES ======================================== @@ -1116,6 +1173,19 @@ body:has(.rpg-panel.rpg-position-left) #sheld { - %: Container-relative sizing ======================================== */ +/* Base widget container - ensures content stays within bounds */ +.rpg-widget { + box-sizing: border-box; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.rpg-widget > * { + box-sizing: border-box; + max-width: 100%; +} + .rpg-widget .rpg-stats-content { display: flex; flex-direction: column; /* Stack vertically */