diff --git a/index.js b/index.js index c606f20..037592d 100644 --- a/index.js +++ b/index.js @@ -210,6 +210,14 @@ async function initUI() { `; $('body').append(mobileToggleHtml); + // Add mobile refresh button (same pattern as toggle button) + const mobileRefreshHtml = ` + + `; + $('body').append(mobileRefreshHtml); + // Cache UI elements using state setters setPanelContainer($('#rpg-companion-panel')); setUserStatsContainer($('#rpg-user-stats')); diff --git a/src/systems/ui/layout.js b/src/systems/ui/layout.js index d3e9b0f..b88b5f0 100644 --- a/src/systems/ui/layout.js +++ b/src/systems/ui/layout.js @@ -273,12 +273,12 @@ export function applyPanelPosition() { */ export function updateGenerationModeUI() { if (extensionSettings.generationMode === 'together') { - // In "together" mode, both update buttons are hidden + // In "together" mode, hide both update buttons $('#rpg-manual-update').hide(); $('#rpg-manual-update-mobile').hide(); } else { - // In "separate" mode, both update buttons are visible - // (CSS media queries will control which one is displayed) + // In "separate" mode, show both buttons + // (CSS media queries control which one is visible based on viewport) $('#rpg-manual-update').show(); $('#rpg-manual-update-mobile').show(); } diff --git a/src/systems/ui/mobile.js b/src/systems/ui/mobile.js index aed33b7..b3f7b95 100644 --- a/src/systems/ui/mobile.js +++ b/src/systems/ui/mobile.js @@ -440,32 +440,41 @@ export function setupMobileToggle() { * Constrains the mobile FAB button to viewport bounds with top-bar awareness. * Only runs when button is in user-controlled state (mobileFabPosition exists). * Ensures button never goes behind the top bar or outside viewport edges. + * @param {jQuery} $button - Optional button element (defaults to mobile toggle) */ -export function constrainFabToViewport() { +export function constrainFabToViewport($button = null) { + // Default to mobile toggle if no button specified + if (!$button) { + $button = $('#rpg-mobile-toggle'); + } + + if ($button.length === 0) return; + + // Determine which position setting to check based on button ID + const isRefreshButton = $button.attr('id') === 'rpg-manual-update-mobile'; + const positionSetting = isRefreshButton ? 'mobileRefreshPosition' : 'mobileFabPosition'; + // Only constrain if user has set a custom position - if (!extensionSettings.mobileFabPosition) { + if (!extensionSettings[positionSetting]) { console.log('[RPG Mobile] Skipping viewport constraint - using CSS defaults'); return; } - const $mobileToggle = $('#rpg-mobile-toggle'); - if ($mobileToggle.length === 0) return; - // Skip if button is not visible - if (!$mobileToggle.is(':visible')) { + if (!$button.is(':visible')) { console.log('[RPG Mobile] Skipping viewport constraint - button not visible'); return; } // Get current position - const offset = $mobileToggle.offset(); + const offset = $button.offset(); if (!offset) return; let currentX = offset.left; let currentY = offset.top; - const buttonWidth = $mobileToggle.outerWidth(); - const buttonHeight = $mobileToggle.outerHeight(); + const buttonWidth = $button.outerWidth(); + const buttonHeight = $button.outerHeight(); // Get top bar height from CSS variable (fallback to 50px if not set) const topBarHeight = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--topBarBlockSize')) || 50; @@ -491,15 +500,15 @@ export function constrainFabToViewport() { }); // Apply new position - $mobileToggle.css({ + $button.css({ left: newX + 'px', top: newY + 'px', right: 'auto', bottom: 'auto' }); - // Save corrected position - extensionSettings.mobileFabPosition = { + // Save corrected position to appropriate setting + extensionSettings[positionSetting] = { left: newX + 'px', top: newY + 'px' }; @@ -725,43 +734,34 @@ export function setupContentEditableScrolling() { /** * Sets up the mobile refresh button with drag functionality. - * Button is only visible when panel is open, and can be dragged to reposition. + * Same pattern as mobile toggle button. * Tap = refresh, drag = reposition */ export function setupRefreshButtonDrag() { const $refreshBtn = $('#rpg-manual-update-mobile'); - const $panel = $('#rpg-companion-panel'); if ($refreshBtn.length === 0) { console.warn('[RPG Mobile] Refresh button not found in DOM'); return; } + console.log('[RPG Mobile] setupRefreshButtonDrag called'); + // Load and apply saved position if (extensionSettings.mobileRefreshPosition) { const pos = extensionSettings.mobileRefreshPosition; - if (pos.left) $refreshBtn.css('left', pos.left); + console.log('[RPG Mobile] Loading saved refresh button position:', pos); + + // Apply saved position if (pos.top) $refreshBtn.css('top', pos.top); if (pos.right) $refreshBtn.css('right', pos.right); if (pos.bottom) $refreshBtn.css('bottom', pos.bottom); + if (pos.left) $refreshBtn.css('left', pos.left); + + // Constrain to viewport after position is applied + requestAnimationFrame(() => constrainFabToViewport($refreshBtn)); } - // Show/hide button based on panel state - const updateButtonVisibility = () => { - if ($panel.hasClass('rpg-mobile-open')) { - $refreshBtn.show(); - } else { - $refreshBtn.hide(); - } - }; - - // Initial visibility check - updateButtonVisibility(); - - // Listen for panel state changes (attach to panel toggle events) - // This will be triggered by setupMobileToggle - $(document).on('rpg-panel-toggled', updateButtonVisibility); - // Touch/drag state let isDragging = false; let touchStartTime = 0; diff --git a/style.css b/style.css index 6a3ed8c..f64b342 100644 --- a/style.css +++ b/style.css @@ -2708,62 +2708,51 @@ body:has(.rpg-panel.rpg-position-left) #sheld { } /* ============================================ - REFRESH ICON BUTTON (Mobile - Floating & Draggable) + MOBILE REFRESH BUTTON (FAB - Same pattern as toggle) ============================================ */ -.rpg-refresh-icon-btn { - display: none; /* Hidden by default, shown on mobile when panel open */ - position: fixed; /* Fixed for dragging anywhere on viewport */ - bottom: 80px; /* Above FAB toggle, below screen edge */ - right: 20px; - width: 44px; /* Touch-friendly size */ - height: 44px; - padding: 0; - background: var(--rpg-highlight); - border: 2px solid var(--rpg-highlight); - border-radius: 50%; - color: var(--rpg-text); - font-size: 1rem; - cursor: grab; - transition: transform 0.3s ease, box-shadow 0.3s ease, opacity 0.3s ease; +.rpg-mobile-refresh { + display: none; align-items: center; justify-content: center; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); - z-index: 1001; /* Above panel (1000) but below mobile FAB (10002) */ - - /* Fix sticky tap highlight */ - -webkit-tap-highlight-color: transparent; - -webkit-touch-callout: none; + position: fixed; + /* Position set by JavaScript based on saved settings */ + width: 44px; + height: 44px; + border-radius: 50%; + background: var(--SmartThemeBlurTintColor); + border: 2px solid var(--SmartThemeBorderColor); + color: var(--rpg-text, #ecf0f1); + font-size: 1.85vw; + cursor: grab; + z-index: 1001; /* Above panel (1000) but below 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; - touch-action: none; -} - -/* Remove focus outline (prevents black state) */ -.rpg-refresh-icon-btn:focus { - outline: none; + will-change: top, left; } /* Disable transitions while actively dragging */ -.rpg-refresh-icon-btn.dragging { +.rpg-mobile-refresh.dragging { transition: none; cursor: grabbing; } -.rpg-refresh-icon-btn:hover { +.rpg-mobile-refresh:hover { transform: scale(1.1); - box-shadow: 0 4px 12px var(--rpg-highlight); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4); } -.rpg-refresh-icon-btn:active { +.rpg-mobile-refresh:active { transform: scale(0.95); } /* Spinning animation when refreshing */ -.rpg-refresh-icon-btn.spinning i { +.rpg-mobile-refresh.spinning i { animation: rpg-spin 0.8s linear infinite; } -.rpg-refresh-icon-btn i { +.rpg-mobile-refresh i { pointer-events: none; } @@ -3361,14 +3350,28 @@ body:has(.rpg-panel.rpg-position-left) #sheld { /* Mobile-specific panel behavior - matches SillyTavern's 1000px breakpoint */ /* CACHE BUST v2025-01-16 */ -/* Mobile: Show icon button, hide desktop button */ +/* Mobile refresh button visibility - opposite of toggle */ @media (max-width: 1000px) { - .rpg-refresh-icon-btn { - display: flex !important; /* Show mobile icon button */ + /* Show mobile refresh button */ + .rpg-mobile-refresh { + display: flex; } + /* Hide desktop refresh button */ .rpg-manual-update-btn { - display: none !important; /* Hide desktop button */ + display: none !important; + } + + /* Show refresh button when panel is open (opposite of toggle) */ + body:has(.rpg-panel.rpg-mobile-open) .rpg-mobile-refresh { + opacity: 1; + pointer-events: auto; + } + + /* Hide refresh button when panel is closed */ + .rpg-mobile-refresh { + opacity: 0; + pointer-events: none; } } @@ -4040,6 +4043,11 @@ body:has(.rpg-panel.rpg-position-left) #sheld { font-size: clamp(20px, 5.1vw, 26px) !important; } + /* Larger mobile refresh icon (same as toggle) */ + .rpg-mobile-refresh { + font-size: clamp(20px, 5.1vw, 26px) !important; + } + /* ======================================== MOBILE SETTINGS POPUP ======================================== */ diff --git a/template.html b/template.html index 1e3d842..8d24685 100644 --- a/template.html +++ b/template.html @@ -54,11 +54,6 @@ - - -