feat: enhance drag-and-drop functionality to support multiple file paths and URLs

This commit is contained in:
claudi 2026-03-04 13:43:21 +01:00
parent 1e848e84b2
commit c612072dc8
5 changed files with 384 additions and 480 deletions

View file

@ -11,7 +11,7 @@
console.log('%c[WebDrop Intercept] Script loaded - INTERCEPT_ENABLED=' + INTERCEPT_ENABLED, 'background: #2196F3; color: white; font-weight: bold; padding: 4px 8px;');
var currentDragUrl = null;
var currentDragUrls = []; // Array to support multiple URLs
var angularDragHandlers = [];
var originalAddEventListener = EventTarget.prototype.addEventListener;
var listenerPatchActive = true;
@ -60,8 +60,14 @@
DataTransfer.prototype.setData = function(format, data) {
if (format === 'text/plain' || format === 'text/uri-list') {
currentDragUrl = data;
console.log('%c[Intercept] Captured URL:', 'color: #4CAF50; font-weight: bold;', data.substring(0, 80));
// text/uri-list contains newline-separated URLs
// text/plain may be single URL or multiple newline-separated URLs
currentDragUrls = data.trim().split('\n').filter(function(url) {
return url.trim().length > 0;
}).map(function(url) {
return url.trim();
});
console.log('%c[Intercept] Captured ' + currentDragUrls.length + ' URL(s)', 'color: #4CAF50; font-weight: bold;', currentDragUrls[0].substring(0, 60));
}
return originalSetData.call(this, format, data);
};
@ -94,7 +100,7 @@
// Register OUR handler in capture phase
originalAddEventListener.call(document, 'dragstart', function(e) {
currentDragUrl = null; // Reset
currentDragUrls = []; // Reset
// Call Angular's handlers first to let them set the data
var handled = 0;
@ -111,33 +117,41 @@
}
}
console.log('[Intercept] Called', handled, 'Angular handlers, URL:', currentDragUrl ? currentDragUrl.substring(0, 60) : 'none');
console.log('[Intercept] Called', handled, 'Angular handlers, URLs:', currentDragUrls.length, 'URL(s)', currentDragUrls.length > 0 ? currentDragUrls[0].substring(0, 60) : 'none');
// NOW check if we should intercept
// Intercept any drag with a URL that matches our configured mappings
if (currentDragUrl) {
// Intercept any drag with URLs that match our configured mappings
if (currentDragUrls.length > 0) {
var shouldIntercept = false;
// Check against configured URL mappings
// Check each URL against configured URL mappings
// Intercept if ANY URL matches
if (window.webdropConfig && window.webdropConfig.urlMappings) {
for (var j = 0; j < window.webdropConfig.urlMappings.length; j++) {
var mapping = window.webdropConfig.urlMappings[j];
if (currentDragUrl.toLowerCase().startsWith(mapping.url_prefix.toLowerCase())) {
shouldIntercept = true;
console.log('[Intercept] URL matches mapping for:', mapping.local_path);
break;
for (var k = 0; k < currentDragUrls.length; k++) {
var dragUrl = currentDragUrls[k];
for (var j = 0; j < window.webdropConfig.urlMappings.length; j++) {
var mapping = window.webdropConfig.urlMappings[j];
if (dragUrl.toLowerCase().startsWith(mapping.url_prefix.toLowerCase())) {
shouldIntercept = true;
console.log('[Intercept] URL #' + (k+1) + ' matches mapping for:', mapping.local_path);
break;
}
}
if (shouldIntercept) break;
}
} else {
// Fallback: Check for legacy Z: drive pattern if no config available
shouldIntercept = /^z:/i.test(currentDragUrl);
if (shouldIntercept) {
console.warn('[Intercept] Using fallback Z: drive pattern (no URL mappings configured)');
for (var k = 0; k < currentDragUrls.length; k++) {
if (/^z:/i.test(currentDragUrls[k])) {
shouldIntercept = true;
console.warn('[Intercept] Using fallback Z: drive pattern (no URL mappings configured)');
break;
}
}
}
if (shouldIntercept) {
console.log('%c[Intercept] PREVENTING browser drag, using Qt',
console.log('%c[Intercept] PREVENTING browser drag, using Qt for ' + currentDragUrls.length + ' file(s)',
'background: #F44336; color: white; font-weight: bold; padding: 4px 8px;');
e.preventDefault();
@ -145,14 +159,15 @@
ensureChannel(function() {
if (window.bridge && typeof window.bridge.start_file_drag === 'function') {
console.log('%c[Intercept] → Qt: start_file_drag', 'color: #9C27B0; font-weight: bold;');
window.bridge.start_file_drag(currentDragUrl);
console.log('%c[Intercept] → Qt: start_file_drag with ' + currentDragUrls.length + ' file(s)', 'color: #9C27B0; font-weight: bold;');
// Pass as JSON string to avoid Qt WebChannel array conversion issues
window.bridge.start_file_drag(JSON.stringify(currentDragUrls));
} else {
console.error('[Intercept] bridge.start_file_drag not available!');
}
});
currentDragUrl = null;
currentDragUrls = [];
return false;
}
}

View file

@ -7,7 +7,7 @@ import re
import sys
from datetime import datetime
from pathlib import Path
from typing import Optional
from typing import Optional, Union
from PySide6.QtCore import (
QEvent,
@ -312,21 +312,34 @@ class _DragBridge(QObject):
self.window = window
@Slot(str)
def start_file_drag(self, path_text: str) -> None:
"""Start a native file drag for the given path or Azure URL.
def start_file_drag(self, paths_text: str) -> None:
"""Start a native file drag for the given path(s) or Azure URL(s).
Called from JavaScript when user drags item(s).
Accepts either:
- Single file path string or Azure URL
- JSON array string of file paths or Azure URLs (multiple drag support)
Called from JavaScript when user drags an item.
Accepts either local file paths or Azure Blob Storage URLs.
Defers execution to avoid Qt drag manager state issues.
Args:
path_text: File path string or Azure URL to drag
paths_text: String (single path/URL) or JSON array string (multiple paths/URLs)
"""
logger.debug(f"Bridge: start_file_drag called for {path_text}")
logger.debug(f"Bridge: start_file_drag called with {len(paths_text)} chars")
# Defer to avoid drag manager state issues
# handle_drag() handles URL conversion and validation internally
QTimer.singleShot(0, lambda: self.window.drag_interceptor.handle_drag(path_text))
# Try to parse as JSON array first (for multiple-drag support)
paths_list: Union[str, list] = paths_text
if paths_text.startswith("["):
try:
parsed = json.loads(paths_text)
if isinstance(parsed, list):
paths_list = parsed
logger.debug(f"Parsed JSON array with {len(parsed)} item(s)")
except (json.JSONDecodeError, TypeError) as e:
logger.warning(f"Failed to parse JSON array: {e}, treating as single string")
# Handle both single string and list
QTimer.singleShot(0, lambda: self.window.drag_interceptor.handle_drag(paths_list))
@Slot(str)
def debug_log(self, message: str) -> None: