feat: Enhance URL conversion and bridge script handling for improved drag-and-drop functionality
Some checks are pending
Tests & Quality Checks / Test on Python 3.11 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.10 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-2 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-2 (push) Waiting to run
Tests & Quality Checks / Build Artifacts (push) Blocked by required conditions
Tests & Quality Checks / Build Artifacts-1 (push) Blocked by required conditions
Some checks are pending
Tests & Quality Checks / Test on Python 3.11 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-1 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.10 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.11-2 (push) Waiting to run
Tests & Quality Checks / Test on Python 3.12-2 (push) Waiting to run
Tests & Quality Checks / Build Artifacts (push) Blocked by required conditions
Tests & Quality Checks / Build Artifacts-1 (push) Blocked by required conditions
This commit is contained in:
parent
9edadc2c16
commit
cbd8ed0186
4 changed files with 275 additions and 37 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
"""URL to local path conversion for Azure Blob Storage URLs."""
|
"""URL to local path conversion for Azure Blob Storage URLs."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
@ -51,13 +52,14 @@ class URLConverter:
|
||||||
# Combine with local path
|
# Combine with local path
|
||||||
local_path = Path(mapping.local_path) / relative_path
|
local_path = Path(mapping.local_path) / relative_path
|
||||||
|
|
||||||
# Normalize path (resolve .. and .) but don't follow symlinks yet
|
# Keep legacy Windows separator normalization to preserve
|
||||||
try:
|
# existing Windows drag/drop behavior.
|
||||||
# On Windows, normalize separators
|
if os.name == "nt":
|
||||||
local_path = Path(str(local_path).replace("/", "\\"))
|
try:
|
||||||
except (OSError, RuntimeError) as e:
|
local_path = Path(str(local_path).replace("/", "\\"))
|
||||||
logger.warning(f"Failed to normalize path {local_path}: {e}")
|
except (OSError, RuntimeError) as e:
|
||||||
return None
|
logger.warning(f"Failed to normalize path {local_path}: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
logger.debug(f"Converted URL to path: {url} -> {local_path}")
|
logger.debug(f"Converted URL to path: {url} -> {local_path}")
|
||||||
return local_path
|
return local_path
|
||||||
|
|
|
||||||
|
|
@ -79,13 +79,13 @@
|
||||||
|
|
||||||
function installDragHandler() {
|
function installDragHandler() {
|
||||||
console.log('[Intercept] Installing dragstart handler, have', angularDragHandlers.length, 'Angular handlers');
|
console.log('[Intercept] Installing dragstart handler, have', angularDragHandlers.length, 'Angular handlers');
|
||||||
|
|
||||||
if (angularDragHandlers.length === 0) {
|
if (angularDragHandlers.length === 0) {
|
||||||
console.warn('[Intercept] No Angular handlers found yet, will retry in 1s...');
|
console.warn('[Intercept] No Angular handlers found yet, will retry in 1s...');
|
||||||
setTimeout(installDragHandler, 1000);
|
setTimeout(installDragHandler, 1000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only install once, even if called multiple times
|
// Only install once, even if called multiple times
|
||||||
if (dragHandlerInstalled) {
|
if (dragHandlerInstalled) {
|
||||||
console.log('[Intercept] Handler already installed, skipping');
|
console.log('[Intercept] Handler already installed, skipping');
|
||||||
|
|
@ -122,7 +122,7 @@
|
||||||
// Intercept any drag with URLs that match our configured mappings
|
// Intercept any drag with URLs that match our configured mappings
|
||||||
if (currentDragUrls.length > 0) {
|
if (currentDragUrls.length > 0) {
|
||||||
var shouldIntercept = false;
|
var shouldIntercept = false;
|
||||||
|
|
||||||
// Check each URL against configured URL mappings
|
// Check each URL against configured URL mappings
|
||||||
// Intercept if ANY URL matches
|
// Intercept if ANY URL matches
|
||||||
if (window.webdropConfig && window.webdropConfig.urlMappings) {
|
if (window.webdropConfig && window.webdropConfig.urlMappings) {
|
||||||
|
|
@ -148,14 +148,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldIntercept) {
|
if (shouldIntercept) {
|
||||||
console.log('%c[Intercept] PREVENTING browser drag, using Qt for ' + currentDragUrls.length + ' file(s)',
|
console.log('%c[Intercept] PREVENTING browser drag, using Qt for ' + currentDragUrls.length + ' file(s)',
|
||||||
'background: #F44336; color: white; font-weight: bold; padding: 4px 8px;');
|
'background: #F44336; color: white; font-weight: bold; padding: 4px 8px;');
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
ensureChannel(function() {
|
ensureChannel(function() {
|
||||||
if (window.bridge && typeof window.bridge.start_file_drag === 'function') {
|
if (window.bridge && typeof window.bridge.start_file_drag === 'function') {
|
||||||
console.log('%c[Intercept] → Qt: start_file_drag with ' + currentDragUrls.length + ' file(s)', 'color: #9C27B0; font-weight: bold;');
|
console.log('%c[Intercept] → Qt: start_file_drag with ' + currentDragUrls.length + ' file(s)', 'color: #9C27B0; font-weight: bold;');
|
||||||
|
|
@ -165,7 +165,7 @@
|
||||||
console.error('[Intercept] bridge.start_file_drag not available!');
|
console.error('[Intercept] bridge.start_file_drag not available!');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
currentDragUrls = [];
|
currentDragUrls = [];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -176,12 +176,12 @@
|
||||||
|
|
||||||
console.log('[Intercept] dragstart handler installed ✓');
|
console.log('[Intercept] dragstart handler installed ✓');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Angular to register its listeners, then install our handler
|
// Wait for Angular to register its listeners, then install our handler
|
||||||
// Start checking after 3 seconds (give Angular time to load), then retry for up to 30 seconds
|
// Start checking after 3 seconds (give Angular time to load), then retry for up to 30 seconds
|
||||||
var installRetries = 0;
|
var installRetries = 0;
|
||||||
var maxRetries = 27; // 3 initial + 27 retries * 1s = 30s total
|
var maxRetries = 27; // 3 initial + 27 retries * 1s = 30s total
|
||||||
|
|
||||||
function scheduleInstall() {
|
function scheduleInstall() {
|
||||||
if (dragHandlerInstalled) return; // Already done
|
if (dragHandlerInstalled) return; // Already done
|
||||||
installRetries++;
|
installRetries++;
|
||||||
|
|
@ -193,7 +193,7 @@
|
||||||
console.warn('[Intercept] Gave up waiting for Angular handlers after 30s');
|
console.warn('[Intercept] Gave up waiting for Angular handlers after 30s');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(scheduleInstall, 3000);
|
setTimeout(scheduleInstall, 3000);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
|
||||||
|
|
@ -426,6 +426,11 @@ class MainWindow(QMainWindow):
|
||||||
self.config = config
|
self.config = config
|
||||||
self._background_threads = [] # Keep references to background threads
|
self._background_threads = [] # Keep references to background threads
|
||||||
self._background_workers = {} # Keep references to background workers
|
self._background_workers = {} # Keep references to background workers
|
||||||
|
self._bridge_script_source = "" # Cache combined bridge source for recovery injection
|
||||||
|
self._bridge_script_re_registered = False # Flag to prevent duplicate re-registration on same load
|
||||||
|
self._is_page_loading = False # Track if a page load is currently in progress
|
||||||
|
self._pending_reload = False # Coalesce multiple rapid reload requests into one
|
||||||
|
self._load_sequence = 0 # Monotonic counter to ignore stale async recovery callbacks
|
||||||
self.checking_dialog = None # Track the checking dialog
|
self.checking_dialog = None # Track the checking dialog
|
||||||
self.downloading_dialog = None # Track the download dialog
|
self.downloading_dialog = None # Track the download dialog
|
||||||
self._is_manual_check = False # Track if this is a manual check (for UI feedback)
|
self._is_manual_check = False # Track if this is a manual check (for UI feedback)
|
||||||
|
|
@ -498,6 +503,10 @@ class MainWindow(QMainWindow):
|
||||||
# Install the drag bridge script
|
# Install the drag bridge script
|
||||||
self._install_bridge_script()
|
self._install_bridge_script()
|
||||||
|
|
||||||
|
# Connect to loadStarted to re-register script before page loads
|
||||||
|
# This ensures the script is injected even on toolbar Reload button clicks
|
||||||
|
self.web_view.loadStarted.connect(self._on_page_load_started)
|
||||||
|
|
||||||
# Connect to loadFinished to verify script injection
|
# Connect to loadFinished to verify script injection
|
||||||
self.web_view.loadFinished.connect(self._on_page_loaded)
|
self.web_view.loadFinished.connect(self._on_page_loaded)
|
||||||
|
|
||||||
|
|
@ -598,9 +607,12 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
script = QWebEngineScript()
|
script = QWebEngineScript()
|
||||||
script.setName("webdrop-bridge")
|
script.setName("webdrop-bridge")
|
||||||
# Use Deferred instead of DocumentCreation to allow DOM to be ready first
|
# Preserve existing Windows behavior and use earlier injection on macOS
|
||||||
# This prevents race conditions with JavaScript event listeners
|
# so dragstart handlers are captured before Angular registers them.
|
||||||
script.setInjectionPoint(QWebEngineScript.InjectionPoint.Deferred)
|
if sys.platform == "darwin":
|
||||||
|
script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentCreation)
|
||||||
|
else:
|
||||||
|
script.setInjectionPoint(QWebEngineScript.InjectionPoint.Deferred)
|
||||||
script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
|
script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
|
||||||
script.setRunsOnSubFrames(False)
|
script.setRunsOnSubFrames(False)
|
||||||
|
|
||||||
|
|
@ -735,6 +747,9 @@ class MainWindow(QMainWindow):
|
||||||
for i, mapping in enumerate(self.config.url_mappings):
|
for i, mapping in enumerate(self.config.url_mappings):
|
||||||
logger.debug(f" Mapping {i+1}: {mapping.url_prefix} → {mapping.local_path}")
|
logger.debug(f" Mapping {i+1}: {mapping.url_prefix} → {mapping.local_path}")
|
||||||
|
|
||||||
|
# Keep script source for runtime recovery if a navigation drops injected scripts.
|
||||||
|
self._bridge_script_source = combined_code
|
||||||
|
|
||||||
script.setSourceCode(combined_code)
|
script.setSourceCode(combined_code)
|
||||||
self.web_view.page().scripts().insert(script)
|
self.web_view.page().scripts().insert(script)
|
||||||
logger.debug(f"✅ Successfully installed bridge script")
|
logger.debug(f"✅ Successfully installed bridge script")
|
||||||
|
|
@ -1338,32 +1353,243 @@ class MainWindow(QMainWindow):
|
||||||
logger.error(f"JS Console: {message}")
|
logger.error(f"JS Console: {message}")
|
||||||
logger.debug(f" at {source_id}:{line_number}")
|
logger.debug(f" at {source_id}:{line_number}")
|
||||||
|
|
||||||
|
def _on_page_load_started(self) -> None:
|
||||||
|
"""Called when a page starts loading (before loadFinished).
|
||||||
|
|
||||||
|
Re-registers the bridge script to ensure it will be injected on reload,
|
||||||
|
page navigation, or any load event.
|
||||||
|
|
||||||
|
Uses a flag to prevent duplicate re-registrations if loadStarted fires multiple times.
|
||||||
|
"""
|
||||||
|
self._is_page_loading = True
|
||||||
|
self._load_sequence += 1
|
||||||
|
self._bridge_script_re_registered = False
|
||||||
|
|
||||||
|
logger.debug("Page load started - ensuring bridge script is registered")
|
||||||
|
if self._bridge_script_source:
|
||||||
|
self._ensure_bridge_script_exists(verbose=False)
|
||||||
|
self._bridge_script_re_registered = True
|
||||||
|
else:
|
||||||
|
logger.debug("Bridge script source not cached; skipping re-registration")
|
||||||
|
|
||||||
def _on_page_loaded(self, success: bool) -> None:
|
def _on_page_loaded(self, success: bool) -> None:
|
||||||
"""Called when a page finishes loading.
|
"""Called when a page finishes loading.
|
||||||
|
|
||||||
Checks if the bridge script was successfully injected.
|
Checks if the bridge script was successfully injected, with automatic recovery
|
||||||
|
for page reloads and redirects.
|
||||||
|
|
||||||
|
Resets the re-registration flag for the next load cycle.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
success: True if page loaded successfully
|
success: True if page loaded successfully
|
||||||
"""
|
"""
|
||||||
|
# Mark load finished and reset flag for next load cycle
|
||||||
|
finished_sequence = self._load_sequence
|
||||||
|
self._is_page_loading = False
|
||||||
|
self._bridge_script_re_registered = False
|
||||||
|
|
||||||
|
# If user pressed reload multiple times during loading, perform exactly one
|
||||||
|
# more reload after a short settle delay.
|
||||||
|
if self._pending_reload:
|
||||||
|
self._pending_reload = False
|
||||||
|
QTimer.singleShot(150, self._trigger_reload_now)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
logger.warning("Page failed to load")
|
logger.warning("Page failed to load")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check if bridge script is loaded
|
def _verify_bridge_loaded(stage: str, attempt: int = 1, sequence: int = finished_sequence) -> None:
|
||||||
def check_script(result):
|
"""Check if bridge marker exists and optionally recover script injection.
|
||||||
if result:
|
|
||||||
logger.debug("WebDrop Bridge script is active")
|
Implements multi-attempt recovery strategy:
|
||||||
logger.debug("QWebChannel bridge is ready")
|
- initial: First check after page load (50ms delay)
|
||||||
else:
|
- recovery_N: Recovery attempts with progressive delays
|
||||||
logger.error("WebDrop Bridge script NOT loaded!")
|
- Each recovery reattempt after a delay to handle late injections
|
||||||
logger.error("Drag-and-drop conversion will not work")
|
"""
|
||||||
|
if sequence != self._load_sequence:
|
||||||
|
logger.debug("Skipping stale bridge verification run for an older page load")
|
||||||
|
return
|
||||||
|
|
||||||
# Execute JS to check if our script is loaded
|
def check_script(result):
|
||||||
self.web_view.page().runJavaScript(
|
if sequence != self._load_sequence:
|
||||||
"typeof window.__webdrop_intercept_injected !== 'undefined' && window.__webdrop_intercept_injected === true",
|
logger.debug(
|
||||||
check_script,
|
"Skipping stale bridge verification callback for an older page load"
|
||||||
)
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if result:
|
||||||
|
logger.debug("WebDrop Bridge script is active")
|
||||||
|
logger.debug("QWebChannel bridge is ready")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Multi-stage recovery for page reloads and redirects
|
||||||
|
if stage == "initial" and self._bridge_script_source:
|
||||||
|
logger.warning(
|
||||||
|
"Bridge marker missing after page load; attempting recovery injection via runJavaScript"
|
||||||
|
)
|
||||||
|
|
||||||
|
def after_recovery(_: object) -> None:
|
||||||
|
# Schedule a recheck with delay
|
||||||
|
QTimer.singleShot(
|
||||||
|
100, lambda: _verify_bridge_loaded("recovery", 1, sequence)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.web_view.page().runJavaScript(self._bridge_script_source, after_recovery)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Multiple recovery attempts with increasing delays
|
||||||
|
if stage.startswith("recovery") and self._bridge_script_source:
|
||||||
|
if attempt < 3: # Allow up to 3 recovery attempts (100ms, 250ms, 500ms)
|
||||||
|
logger.warning(
|
||||||
|
f"Bridge marker still missing (recovery attempt {attempt}); "
|
||||||
|
f"retrying with progressive delay"
|
||||||
|
)
|
||||||
|
|
||||||
|
def after_retry(_: object) -> None:
|
||||||
|
# Exponential backoff: 100ms, 250ms, 500ms
|
||||||
|
delay = int(100 * (1.5 ** (attempt - 1)))
|
||||||
|
QTimer.singleShot(
|
||||||
|
delay,
|
||||||
|
lambda: _verify_bridge_loaded(
|
||||||
|
"recovery", attempt + 1, sequence
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.web_view.page().runJavaScript(self._bridge_script_source, after_retry)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Final recovery attempt: re-register script in page().scripts() after 800ms
|
||||||
|
if stage.startswith("recovery") and attempt == 3:
|
||||||
|
logger.error(
|
||||||
|
"Bridge marker missing after 3 recovery attempts; "
|
||||||
|
"attempting QWebEngineScript re-registration"
|
||||||
|
)
|
||||||
|
|
||||||
|
def after_re_register(_: object) -> None:
|
||||||
|
# Final check after re-registration
|
||||||
|
QTimer.singleShot(
|
||||||
|
100, lambda: _verify_bridge_loaded("final_check", 1, sequence)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._re_register_bridge_script()
|
||||||
|
self.web_view.page().runJavaScript(self._bridge_script_source, after_re_register)
|
||||||
|
return
|
||||||
|
|
||||||
|
# All recovery attempts exhausted
|
||||||
|
logger.error("❌ WebDrop Bridge script failed to inject after all recovery attempts!")
|
||||||
|
logger.error(" Drag-and-drop functionality is DISABLED")
|
||||||
|
logger.debug(f" Stage: {stage}, Attempt: {attempt}")
|
||||||
|
|
||||||
|
self.web_view.page().runJavaScript(
|
||||||
|
"typeof window.__webdrop_intercept_injected !== 'undefined' && window.__webdrop_intercept_injected === true",
|
||||||
|
check_script,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Run verification slightly deferred to avoid races with redirect-heavy loads.
|
||||||
|
QTimer.singleShot(50, lambda: _verify_bridge_loaded("initial", 1, finished_sequence))
|
||||||
|
|
||||||
|
def _request_reload(self) -> None:
|
||||||
|
"""Handle toolbar reload clicks with coalescing during active page loads."""
|
||||||
|
if self._is_page_loading:
|
||||||
|
self._pending_reload = True
|
||||||
|
logger.debug("Reload requested while loading; queued one pending reload")
|
||||||
|
return
|
||||||
|
|
||||||
|
self._trigger_reload_now()
|
||||||
|
|
||||||
|
def _trigger_reload_now(self) -> None:
|
||||||
|
"""Execute a single reload with bridge script availability check."""
|
||||||
|
self._pending_reload = False
|
||||||
|
# Lock immediately so rapid clicks between reload() and loadStarted don't queue
|
||||||
|
# additional concurrent reloads.
|
||||||
|
self._is_page_loading = True
|
||||||
|
self._ensure_bridge_script_exists(verbose=False)
|
||||||
|
self.web_view.reload()
|
||||||
|
|
||||||
|
def _ensure_bridge_script_exists(self, verbose: bool = False) -> None:
|
||||||
|
"""Ensure bridge script exists in QWebEngineScript collection (idempotent).
|
||||||
|
|
||||||
|
Checks if the script already exists. If not, adds it.
|
||||||
|
Never removes/re-adds to avoid race conditions with Qt's injection mechanism.
|
||||||
|
|
||||||
|
This is safer than removing+re-adding because:
|
||||||
|
- Avoids concurrent access conflicts with Qt's internal injection
|
||||||
|
- Prevents missing injections during rapid reloads
|
||||||
|
- Guarantees script is available without timing gaps
|
||||||
|
|
||||||
|
Args:
|
||||||
|
verbose: If True, use debug logging; otherwise use minimal logging
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
scripts = self.web_view.page().scripts()
|
||||||
|
|
||||||
|
# Check if script already exists
|
||||||
|
already_exists = False
|
||||||
|
for script in scripts.toList(): # type: ignore
|
||||||
|
if script.name() == "webdrop-bridge":
|
||||||
|
already_exists = True
|
||||||
|
if verbose:
|
||||||
|
logger.debug("Bridge script already exists in page().scripts()")
|
||||||
|
break
|
||||||
|
|
||||||
|
# If script doesn't exist, add it
|
||||||
|
if not already_exists and self._bridge_script_source:
|
||||||
|
new_script = QWebEngineScript()
|
||||||
|
new_script.setName("webdrop-bridge")
|
||||||
|
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
new_script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentCreation)
|
||||||
|
else:
|
||||||
|
new_script.setInjectionPoint(QWebEngineScript.InjectionPoint.Deferred)
|
||||||
|
|
||||||
|
new_script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
|
||||||
|
new_script.setRunsOnSubFrames(False)
|
||||||
|
new_script.setSourceCode(self._bridge_script_source)
|
||||||
|
|
||||||
|
scripts.insert(new_script)
|
||||||
|
logger.debug(f"✓ Added bridge script to collection ({len(self._bridge_script_source)} chars)")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to ensure bridge script exists: {e}")
|
||||||
|
|
||||||
|
def _re_register_bridge_script(self, verbose: bool = False) -> None:
|
||||||
|
"""Force re-registration of bridge script in QWebEngineScript collection.
|
||||||
|
|
||||||
|
Removes old script and re-adds it to ensure it's injected on next page load.
|
||||||
|
This is a fallback for recovery mechanics when normal injection fails.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
verbose: If True, use debug logging; otherwise use minimal logging
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Remove old script with same name if it exists
|
||||||
|
scripts = self.web_view.page().scripts()
|
||||||
|
removed = False
|
||||||
|
for script in scripts.toList(): # type: ignore
|
||||||
|
if script.name() == "webdrop-bridge":
|
||||||
|
if verbose:
|
||||||
|
logger.debug("Removing old webdrop-bridge script from page().scripts()")
|
||||||
|
scripts.remove(script)
|
||||||
|
removed = True
|
||||||
|
|
||||||
|
# Re-register the script
|
||||||
|
if self._bridge_script_source:
|
||||||
|
new_script = QWebEngineScript()
|
||||||
|
new_script.setName("webdrop-bridge")
|
||||||
|
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
new_script.setInjectionPoint(QWebEngineScript.InjectionPoint.DocumentCreation)
|
||||||
|
else:
|
||||||
|
new_script.setInjectionPoint(QWebEngineScript.InjectionPoint.Deferred)
|
||||||
|
|
||||||
|
new_script.setWorldId(QWebEngineScript.ScriptWorldId.MainWorld)
|
||||||
|
new_script.setRunsOnSubFrames(False)
|
||||||
|
new_script.setSourceCode(self._bridge_script_source)
|
||||||
|
|
||||||
|
scripts.insert(new_script)
|
||||||
|
if verbose or removed:
|
||||||
|
logger.debug(f"✓ Re-registered webdrop-bridge script ({len(self._bridge_script_source)} chars)")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to re-register bridge script: {e}")
|
||||||
|
|
||||||
def _create_navigation_toolbar(self) -> None:
|
def _create_navigation_toolbar(self) -> None:
|
||||||
"""Create navigation toolbar with Home, Back, Forward, Refresh buttons.
|
"""Create navigation toolbar with Home, Back, Forward, Refresh buttons.
|
||||||
|
|
@ -1401,13 +1627,18 @@ class MainWindow(QMainWindow):
|
||||||
home_action.triggered.connect(self._navigate_home)
|
home_action.triggered.connect(self._navigate_home)
|
||||||
|
|
||||||
# Refresh button
|
# Refresh button
|
||||||
refresh_action = self.web_view.pageAction(self.web_view.page().WebAction.Reload)
|
refresh_action = toolbar.addAction("")
|
||||||
reload_icon_path = self._resolve_toolbar_icon_path(
|
reload_icon_path = self._resolve_toolbar_icon_path(
|
||||||
os.getenv("TOOLBAR_ICON_RELOAD", "resources/icons/reload.ico")
|
os.getenv("TOOLBAR_ICON_RELOAD", "resources/icons/reload.ico")
|
||||||
)
|
)
|
||||||
if reload_icon_path is not None:
|
if reload_icon_path is not None:
|
||||||
refresh_action.setIcon(QIcon(str(reload_icon_path)))
|
refresh_action.setIcon(QIcon(str(reload_icon_path)))
|
||||||
toolbar.addAction(refresh_action)
|
else:
|
||||||
|
refresh_action.setIcon(
|
||||||
|
self.style().standardIcon(self.style().StandardPixmap.SP_BrowserReload)
|
||||||
|
)
|
||||||
|
refresh_action.setToolTip("Reload")
|
||||||
|
refresh_action.triggered.connect(self._request_reload)
|
||||||
|
|
||||||
# Open-with-default-app drop zone (right of Reload)
|
# Open-with-default-app drop zone (right of Reload)
|
||||||
self._open_drop_zone = OpenDropZone()
|
self._open_drop_zone = OpenDropZone()
|
||||||
|
|
@ -1849,6 +2080,7 @@ class MainWindow(QMainWindow):
|
||||||
|
|
||||||
def _navigate_home(self) -> None:
|
def _navigate_home(self) -> None:
|
||||||
"""Navigate to the home (start) URL."""
|
"""Navigate to the home (start) URL."""
|
||||||
|
self._pending_reload = False
|
||||||
home_url = self.config.webapp_url
|
home_url = self.config.webapp_url
|
||||||
if home_url.startswith("http://") or home_url.startswith("https://"):
|
if home_url.startswith("http://") or home_url.startswith("https://"):
|
||||||
self.web_view.load(QUrl(home_url))
|
self.web_view.load(QUrl(home_url))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Unit tests for URL converter."""
|
"""Unit tests for URL converter."""
|
||||||
|
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
@ -44,7 +45,10 @@ def test_convert_simple_url(converter):
|
||||||
result = converter.convert_url_to_path(url)
|
result = converter.convert_url_to_path(url)
|
||||||
|
|
||||||
assert result is not None
|
assert result is not None
|
||||||
assert str(result).endswith("test\\file.png") # Windows path separator
|
if os.name == "nt":
|
||||||
|
assert str(result).endswith("test\\file.png")
|
||||||
|
else:
|
||||||
|
assert str(result).endswith("test/file.png")
|
||||||
|
|
||||||
|
|
||||||
def test_convert_url_with_special_characters(converter):
|
def test_convert_url_with_special_characters(converter):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue