From d4491a47054648b44121c01fa2d08fc6fa9f3016 Mon Sep 17 00:00:00 2001 From: Lucas 'Paperboy' Rose-Winters Date: Wed, 22 Oct 2025 08:29:58 +1100 Subject: [PATCH] fix: add debug toggle as draggable mobile FAB button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROBLEM: - Debug logs only accessible via browser console (impractical on mobile) - User (Salixfire) reporting parsing issues but can't debug on mobile device - Need mobile-friendly debug mode for troubleshooting data display issues SOLUTION: Implemented debug toggle FAB button following exact pattern of existing mobile FABs: Files Changed: - src/core/state.js: Added debugFabPosition and debugMode to extensionSettings - src/core/config.js: Added debugFabPosition to defaultSettings (reference) - index.js: Created debug toggle button, imported setupDebugButtonDrag - style.css: Added debug toggle CSS matching mobile FAB pattern (44px, grab cursor, theme colors) - src/systems/ui/mobile.js: Added setupDebugButtonDrag() with drag-to-reposition - src/systems/ui/debug.js: Removed button creation, added just-dragged check, updated visibility control Implementation Details: - Button created in index.js (not debug.js) following mobile FAB pattern - CSS matches mobile toggle/refresh buttons (44px, theme colors, grab cursor, user-select: none) - Drag support with touch/mouse handlers, 200ms/10px threshold - Position saved to extensionSettings.debugFabPosition - Just-dragged flag prevents accidental clicks after drag - Mobile (≤1000px): slide from right with rpg-mobile-open/closing classes - Desktop (>1000px): slide from bottom with rpg-debug-open class - Event delegation for reliable click handling - Default position: bottom 140px, left 20px (below other FABs) Bug Fix: - Initial implementation had debugFabPosition only in config.js - extensionSettings uses state.js as source, not config.js - Without debugFabPosition in state.js, button had no position and was invisible - Now properly initialized in both files The debug button is hidden by default (debugMode: false) and shown when user enables debug mode in RPG Companion settings. This allows Salixfire to view parser logs on mobile and troubleshoot the data display issues. --- index.js | 12 ++- src/core/config.js | 4 + src/core/state.js | 7 +- src/systems/ui/debug.js | 101 ++++++++++++++---- src/systems/ui/mobile.js | 215 +++++++++++++++++++++++++++++++++++++++ style.css | 109 +++++++++++++++----- 6 files changed, 402 insertions(+), 46 deletions(-) diff --git a/index.js b/index.js index 6bbc55d..5d276fd 100644 --- a/index.js +++ b/index.js @@ -99,7 +99,8 @@ import { removeMobileTabs, setupMobileKeyboardHandling, setupContentEditableScrolling, - setupRefreshButtonDrag + setupRefreshButtonDrag, + setupDebugButtonDrag } from './src/systems/ui/mobile.js'; import { setupDesktopTabs, @@ -221,6 +222,14 @@ async function initUI() { `; $('body').append(mobileRefreshHtml); + // Add debug toggle FAB button (same pattern as other mobile FABs) + const debugToggleHtml = ` + + `; + $('body').append(debugToggleHtml); + // Cache UI elements using state setters setPanelContainer($('#rpg-companion-panel')); setUserStatsContainer($('#rpg-user-stats')); @@ -464,6 +473,7 @@ async function initUI() { setupMobileKeyboardHandling(); setupContentEditableScrolling(); setupRefreshButtonDrag(); + setupDebugButtonDrag(); initInventoryEventListeners(); // Initialize debug UI if debug mode is enabled diff --git a/src/core/config.js b/src/core/config.js index 82ad9c7..4287b6e 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -53,6 +53,10 @@ export const defaultSettings = { bottom: '80px', right: '20px' }, // Saved position for mobile refresh button + debugFabPosition: { + bottom: '140px', + left: '20px' + }, // Saved position for debug FAB button userStats: { health: 100, satiety: 100, diff --git a/src/core/state.js b/src/core/state.js index 6cff0d4..3442c6a 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -41,6 +41,10 @@ export let extensionSettings = { bottom: '80px', right: '20px' }, // Saved position for mobile refresh button + debugFabPosition: { + bottom: '140px', + left: '20px' + }, // Saved position for debug FAB button userStats: { health: 100, satiety: 100, @@ -72,7 +76,8 @@ export let extensionSettings = { onPerson: 'list', // 'list' or 'grid' view mode for On Person section stored: 'list', // 'list' or 'grid' view mode for Stored section assets: 'list' // 'list' or 'grid' view mode for Assets section - } + }, + debugMode: false // Enable debug logging visible in UI (for mobile debugging) }; /** diff --git a/src/systems/ui/debug.js b/src/systems/ui/debug.js index 2836f68..c6030b0 100644 --- a/src/systems/ui/debug.js +++ b/src/systems/ui/debug.js @@ -7,11 +7,11 @@ import { extensionSettings, getDebugLogs, clearDebugLogs } from '../../core/stat /** * Creates and injects the debug panel into the page + * Note: Debug toggle button is created in index.js, not here */ export function createDebugPanel() { // Remove existing debug panel if any $('#rpg-debug-panel').remove(); - $('#rpg-debug-toggle').remove(); // Create debug panel HTML const debugPanelHtml = ` @@ -34,16 +34,8 @@ export function createDebugPanel() { `; - // Create debug toggle button (FAB-style) - const debugToggleHtml = ` - - `; - // Append to body $('body').append(debugPanelHtml); - $('body').append(debugToggleHtml); // Set up event handlers setupDebugEventHandlers(); @@ -53,25 +45,85 @@ export function createDebugPanel() { } /** - * Sets up event handlers for debug panel + * Closes the debug panel with proper animation (mobile or desktop) + */ +function closeDebugPanel() { + const $panel = $('#rpg-debug-panel'); + const isMobile = window.innerWidth <= 1000; + + if (isMobile) { + // Mobile: animate slide-out to right + $panel.removeClass('rpg-mobile-open').addClass('rpg-mobile-closing'); + + // Wait for animation to complete before hiding + $panel.one('animationend', function() { + $panel.removeClass('rpg-mobile-closing'); + $('.rpg-mobile-overlay').remove(); + }); + } else { + // Desktop: simple slide-down + $panel.removeClass('rpg-debug-open'); + } +} + +/** + * Sets up event handlers for debug panel using event delegation for mobile compatibility */ function setupDebugEventHandlers() { + // Use event delegation for better mobile compatibility and reliability with dynamic elements + // Remove any existing handlers first to prevent duplicates + $(document).off('click.rpgDebug'); + // Toggle button - $('#rpg-debug-toggle').on('click', function() { - $('#rpg-debug-panel').toggleClass('rpg-debug-open'); - renderDebugLogs(); // Refresh logs when opening + $(document).on('click.rpgDebug', '#rpg-debug-toggle', function() { + const $debugToggle = $(this); + + // Skip if we just finished dragging + if ($debugToggle.data('just-dragged')) { + console.log('[RPG Debug] Click blocked - just finished dragging'); + return; + } + + const $panel = $('#rpg-debug-panel'); + const isMobile = window.innerWidth <= 1000; + + if (isMobile) { + // Mobile: use rpg-mobile-open class with slide-from-right animation + const isOpen = $panel.hasClass('rpg-mobile-open'); + + if (isOpen) { + // Close with animation + closeDebugPanel(); + } else { + // Open with animation + $panel.addClass('rpg-mobile-open'); + renderDebugLogs(); + + // Create overlay for mobile + const $overlay = $('
'); + $('body').append($overlay); + + // Close when clicking overlay + $overlay.on('click', function() { + closeDebugPanel(); + }); + } + } else { + // Desktop: use rpg-debug-open class with slide-from-bottom animation + $panel.toggleClass('rpg-debug-open'); + renderDebugLogs(); + } }); // Close button - $('#rpg-debug-close').on('click', function(e) { + $(document).on('click.rpgDebug', '#rpg-debug-close', function(e) { e.preventDefault(); e.stopPropagation(); - console.log('[RPG Debug] Close button clicked'); - $('#rpg-debug-panel').removeClass('rpg-debug-open'); + closeDebugPanel(); }); // Copy button - $('#rpg-debug-copy').on('click', function() { + $(document).on('click.rpgDebug', '#rpg-debug-copy', function() { const logs = getDebugLogs(); const logsText = logs.map(log => { let text = `[${log.timestamp}] ${log.message}`; @@ -96,7 +148,7 @@ function setupDebugEventHandlers() { }); // Clear button - $('#rpg-debug-clear').on('click', function() { + $(document).on('click.rpgDebug', '#rpg-debug-clear', function() { if (confirm('Clear all debug logs?')) { clearDebugLogs(); renderDebugLogs(); @@ -145,15 +197,24 @@ function escapeHtml(text) { /** * Shows or hides debug UI based on debug mode setting + * Note: Debug toggle button always exists in DOM (created in index.js) */ export function updateDebugUIVisibility() { + const $debugToggle = $('#rpg-debug-toggle'); + if (extensionSettings.debugMode) { + // Show debug toggle button + $debugToggle.css('display', 'flex'); + + // Create debug panel if it doesn't exist if ($('#rpg-debug-panel').length === 0) { createDebugPanel(); } - $('#rpg-debug-toggle').show(); } else { - $('#rpg-debug-toggle').hide(); + // Hide debug toggle button + $debugToggle.css('display', 'none'); + + // Remove debug panel $('#rpg-debug-panel').remove(); } } diff --git a/src/systems/ui/mobile.js b/src/systems/ui/mobile.js index b3f7b95..d29d758 100644 --- a/src/systems/ui/mobile.js +++ b/src/systems/ui/mobile.js @@ -947,3 +947,218 @@ export function setupRefreshButtonDrag() { isDragging = false; }); } + +/** + * Sets up drag functionality for the debug toggle FAB button + * Same pattern as refresh button drag + */ +export function setupDebugButtonDrag() { + const $debugBtn = $('#rpg-debug-toggle'); + + if ($debugBtn.length === 0) { + console.warn('[RPG Mobile] Debug button not found in DOM'); + return; + } + + console.log('[RPG Mobile] setupDebugButtonDrag called'); + + // Load and apply saved position + if (extensionSettings.debugFabPosition) { + const pos = extensionSettings.debugFabPosition; + console.log('[RPG Mobile] Loading saved debug button position:', pos); + + // Apply saved position + if (pos.top) $debugBtn.css('top', pos.top); + if (pos.right) $debugBtn.css('right', pos.right); + if (pos.bottom) $debugBtn.css('bottom', pos.bottom); + if (pos.left) $debugBtn.css('left', pos.left); + + // Constrain to viewport after position is applied + requestAnimationFrame(() => constrainFabToViewport($debugBtn)); + } + + // Touch/drag state + let isDragging = false; + let touchStartTime = 0; + let touchStartX = 0; + let touchStartY = 0; + let buttonStartX = 0; + let buttonStartY = 0; + const LONG_PRESS_DURATION = 200; + const MOVE_THRESHOLD = 10; + let rafId = null; + let pendingX = null; + let pendingY = null; + + // Update position using requestAnimationFrame + function updatePosition() { + if (pendingX !== null && pendingY !== null) { + $debugBtn.css({ + left: pendingX + 'px', + top: pendingY + 'px', + right: 'auto', + bottom: 'auto' + }); + pendingX = null; + pendingY = null; + } + rafId = null; + } + + // Touch start + $debugBtn.on('touchstart', function(e) { + const touch = e.originalEvent.touches[0]; + touchStartTime = Date.now(); + touchStartX = touch.clientX; + touchStartY = touch.clientY; + + const offset = $debugBtn.offset(); + buttonStartX = offset.left; + buttonStartY = offset.top; + + isDragging = false; + }); + + // Touch move + $debugBtn.on('touchmove', function(e) { + const touch = e.originalEvent.touches[0]; + const deltaX = touch.clientX - touchStartX; + const deltaY = touch.clientY - touchStartY; + const timeSinceStart = Date.now() - touchStartTime; + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) { + isDragging = true; + $debugBtn.addClass('dragging'); + } + + if (isDragging) { + e.preventDefault(); + + let newX = buttonStartX + deltaX; + let newY = buttonStartY + deltaY; + + const buttonWidth = $debugBtn.outerWidth(); + const buttonHeight = $debugBtn.outerHeight(); + + const minX = 10; + const maxX = window.innerWidth - buttonWidth - 10; + const minY = 10; + const maxY = window.innerHeight - buttonHeight - 10; + + newX = Math.max(minX, Math.min(maxX, newX)); + newY = Math.max(minY, Math.min(maxY, newY)); + + pendingX = newX; + pendingY = newY; + if (!rafId) { + rafId = requestAnimationFrame(updatePosition); + } + } + }); + + // Touch end + $debugBtn.on('touchend', function(e) { + if (isDragging) { + // Save new position + const offset = $debugBtn.offset(); + const newPosition = { + left: offset.left + 'px', + top: offset.top + 'px' + }; + + extensionSettings.debugFabPosition = newPosition; + saveSettings(); + + setTimeout(() => { + $debugBtn.removeClass('dragging'); + }, 50); + + // Set flag to prevent click handler from firing + $debugBtn.data('just-dragged', true); + setTimeout(() => { + $debugBtn.data('just-dragged', false); + }, 100); + + isDragging = false; + } + }); + + // Mouse support for desktop + let mouseDown = false; + + $debugBtn.on('mousedown', function(e) { + e.preventDefault(); + touchStartTime = Date.now(); + touchStartX = e.clientX; + touchStartY = e.clientY; + + const offset = $debugBtn.offset(); + buttonStartX = offset.left; + buttonStartY = offset.top; + + mouseDown = true; + isDragging = false; + }); + + $(document).on('mousemove.rpgDebugDrag', function(e) { + if (!mouseDown) return; + + const deltaX = e.clientX - touchStartX; + const deltaY = e.clientY - touchStartY; + const timeSinceStart = Date.now() - touchStartTime; + const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); + + if (!isDragging && (timeSinceStart > LONG_PRESS_DURATION || distance > MOVE_THRESHOLD)) { + isDragging = true; + $debugBtn.addClass('dragging'); + } + + if (isDragging) { + let newX = buttonStartX + deltaX; + let newY = buttonStartY + deltaY; + + const buttonWidth = $debugBtn.outerWidth(); + const buttonHeight = $debugBtn.outerHeight(); + + const minX = 10; + const maxX = window.innerWidth - buttonWidth - 10; + const minY = 10; + const maxY = window.innerHeight - buttonHeight - 10; + + newX = Math.max(minX, Math.min(maxX, newX)); + newY = Math.max(minY, Math.min(maxY, newY)); + + pendingX = newX; + pendingY = newY; + if (!rafId) { + rafId = requestAnimationFrame(updatePosition); + } + } + }); + + $(document).on('mouseup.rpgDebugDrag', function(e) { + if (mouseDown && isDragging) { + const offset = $debugBtn.offset(); + const newPosition = { + left: offset.left + 'px', + top: offset.top + 'px' + }; + + extensionSettings.debugFabPosition = newPosition; + saveSettings(); + + setTimeout(() => { + $debugBtn.removeClass('dragging'); + }, 50); + + $debugBtn.data('just-dragged', true); + setTimeout(() => { + $debugBtn.data('just-dragged', false); + }, 100); + } + + mouseDown = false; + isDragging = false; + }); +} diff --git a/style.css b/style.css index 70872c5..e947926 100644 --- a/style.css +++ b/style.css @@ -4784,42 +4784,46 @@ body:has(.rpg-panel.rpg-position-left) #sheld { Debug Panel Styles - Mobile-Friendly Debug Log Viewer =================================================================== */ -/* Debug toggle button (FAB-style) */ +/* ============================================ + DEBUG TOGGLE FAB BUTTON (Same pattern as mobile FABs) + ============================================ */ .rpg-debug-toggle { display: none; /* Hidden by default, shown when debugMode is enabled */ align-items: center; justify-content: center; position: fixed; - bottom: 20px; - left: 20px; - width: 50px; - height: 50px; + /* Position set by JavaScript based on saved settings */ + width: 44px; + height: 44px; border-radius: 50%; - background: #ff6b6b; - border: 2px solid #c92a2a; - color: white; - font-size: 1.5rem; - cursor: pointer; - z-index: 10003; /* Above everything else */ - box-shadow: 0 4px 12px rgba(255, 107, 107, 0.5); - transition: all 0.3s ease; + background: var(--SmartThemeBlurTintColor); + border: 2px solid var(--SmartThemeBorderColor); + color: var(--rpg-text, #ecf0f1); + font-size: 1.85vw; + cursor: grab; + z-index: 1000; /* Below refresh (1001) and mobile toggle (10002) */ + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + transition: opacity 0.3s ease, transform 0.2s ease, top 0.3s ease, left 0.3s ease, right 0.3s ease, bottom 0.3s ease; + user-select: none; + -webkit-user-select: none; + will-change: top, left; +} + +/* Disable transitions while actively dragging */ +.rpg-debug-toggle.dragging { + transition: none; + cursor: grabbing; } .rpg-debug-toggle:hover { transform: scale(1.1); - box-shadow: 0 6px 16px rgba(255, 107, 107, 0.7); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4); } .rpg-debug-toggle:active { transform: scale(0.95); } -@media (max-width: 1000px) { - .rpg-debug-toggle { - display: flex; /* Show on mobile when debugMode is enabled */ - } -} - /* Debug panel */ .rpg-debug-panel { position: fixed; @@ -4937,6 +4941,67 @@ body:has(.rpg-panel.rpg-position-left) #sheld { word-break: break-word; } +/* Mobile view - slide from right like main panel */ +@media (max-width: 1000px) { + .rpg-debug-panel { + /* Reset bottom slide positioning */ + transform: none; + transition: none; + bottom: auto; + + /* Mobile panel - slide from right */ + position: fixed !important; + top: var(--topBarBlockSize) !important; + right: 0 !important; + left: auto !important; + + /* Mobile sizing using dynamic viewport units */ + width: 85dvw !important; + max-width: 400px !important; + height: calc(100dvh - var(--topBarBlockSize)) !important; + + /* Hidden by default */ + display: none !important; + + /* Mobile scrolling */ + overflow-y: auto !important; + -webkit-overflow-scrolling: touch; + + /* Styling */ + border-radius: 20px 0 0 0; + border-left: 1px solid var(--SmartThemeBorderColor); + border-top: 1px solid var(--SmartThemeBorderColor); + border-bottom: none; + backdrop-filter: blur(calc(var(--SmartThemeBlurStrength) * 2)); + box-shadow: -5px 0 20px rgba(0, 0, 0, 0.5); + } + + /* Show panel when opened with slide-in animation */ + .rpg-debug-panel.rpg-mobile-open { + display: flex !important; + z-index: 10002; + animation: rpgSlideInFromRight 0.3s ease-in-out; + } + + /* Closing animation - slide out to right */ + .rpg-debug-panel.rpg-mobile-closing { + display: flex !important; + z-index: 10002; + animation: rpgSlideOutToRight 0.3s ease-in-out; + } + + /* Debug logs container needs to stay scrollable */ + .rpg-debug-logs { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } + + /* Debug toggle button on mobile */ + .rpg-debug-toggle { + font-size: clamp(20px, 5.1vw, 26px) !important; + } +} + /* Desktop view - smaller panel in bottom right */ @media (min-width: 1001px) { .rpg-debug-panel { @@ -4949,8 +5014,4 @@ body:has(.rpg-panel.rpg-position-left) #sheld { border-radius: 12px; border: 2px solid var(--SmartThemeBorderColor, #333); } - - .rpg-debug-toggle { - display: flex; /* Show on desktop too when debugMode enabled */ - } }