fix: add debug toggle as draggable mobile FAB button

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.
This commit is contained in:
Lucas 'Paperboy' Rose-Winters
2025-10-22 08:29:58 +11:00
parent 44240e6840
commit d4491a4705
6 changed files with 402 additions and 46 deletions
+11 -1
View File
@@ -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 = `
<button id="rpg-debug-toggle" class="rpg-debug-toggle" title="Toggle Debug Logs">
<i class="fa-solid fa-bug"></i>
</button>
`;
$('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
+4
View File
@@ -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,
+6 -1
View File
@@ -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)
};
/**
+81 -20
View File
@@ -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() {
</div>
`;
// Create debug toggle button (FAB-style)
const debugToggleHtml = `
<button id="rpg-debug-toggle" class="rpg-debug-toggle" title="Toggle Debug Logs">
<i class="fa-solid fa-bug"></i>
</button>
`;
// 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 = $('<div class="rpg-mobile-overlay"></div>');
$('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();
}
}
+215
View File
@@ -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;
});
}
+85 -24
View File
@@ -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 */
}
}