+Classes: modal-dialog checkout-dialog
+```
+
+**Wenn gefunden →** Popup wird durch DOM-Manipulation ausgelöst
+**Merkblatt:** Notieren Sie die `className` und `textContent`
+
+#### ✅ **B) API-Call Detection**
+```
+[FETCH] https://dev.agravity.io/api/assets/abc123/checkout
+```
+oder
+```
+[XHR] POST https://dev.agravity.io/api/assets/abc123/checkout
+```
+
+**Wenn gefunden →** Popup wird durch API-Response ausgelöst
+**Merkblatt:** Notieren Sie die vollständige URL und Methode (POST/GET)
+
+#### ✅ **C) Event Detection**
+```
+[EVENT] DROP
+Target: ...
+DataTransfer: ...
+```
+
+**Merkblatt:** Notieren Sie welche Events feuern NACHDEM Drop passiert
+
+### Schritt 6: Detailanalyse
+
+In der Browser Console, führen Sie aus:
+
+```javascript
+// Event-Statistik
+webdrop_debug.getEventCounts()
+
+// Asset-Card Component untersuchen (wenn Angular DevTools installiert)
+var card = document.querySelector('ay-asset-card');
+var component = webdrop_debug.getComponent(card);
+console.log('Component methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(component)));
+```
+
+**Merkblatt:** Notieren Sie:
+- Welche Methoden hat das Component? (z.B. `onCheckout`, `showDialog`, etc.)
+- Gibt es einen `click`-Handler auf dem Asset?
+
+## 📝 Was Sie herausfinden müssen
+
+Nach dem Debugging sollten Sie wissen:
+
+1. **WIE wird Popup ausgelöst?**
+ - [ ] API-Call nach Drop (URL: ________________________)
+ - [ ] DOM-Element wird erstellt (Class: ________________)
+ - [ ] Angular Component Methode (Name: ________________)
+ - [ ] Click-Event auf Button (Selector: _______________)
+
+2. **WANN wird Popup ausgelöst?**
+ - [ ] Sofort nach Drop
+ - [ ] Nach Verzögerung (_____ Sekunden)
+ - [ ] Nach API-Response
+ - [ ] Nach User-Interaction
+
+3. **WELCHE Daten werden benötigt?**
+ - [ ] Asset-ID (Beispiel: _________________________)
+ - [ ] Collection-ID (Beispiel: ______________________)
+ - [ ] User-ID (Beispiel: ___________________________)
+ - [ ] Weitere: ____________________________________
+
+## 🔧 Phase 2: Implementation (NACHDEM Debugging abgeschlossen)
+
+Basierend auf Ihren Erkenntnissen:
+
+### Fall A: Popup durch API-Call
+
+**In `bridge_script.js` hinzufügen** (nach erfolgreichem File-Drop):
+
+```javascript
+// Nach Qt File-Drop Erfolg
+function triggerCheckoutPopup(assetId) {
+ fetch('https://dev.agravity.io/api/assets/' + assetId + '/checkout', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ // ggf. Authorization Header
+ },
+ body: JSON.stringify({
+ // Parameter basierend auf Debug-Logs
+ })
+ })
+ .then(response => response.json())
+ .then(data => {
+ console.log('Checkout popup triggered via API');
+ });
+}
+```
+
+### Fall B: Popup durch Component-Methode
+
+```javascript
+function triggerCheckoutPopup(assetId) {
+ var card = document.getElementById(assetId);
+ if (card && window.ng) {
+ var component = ng.getComponent(card);
+ if (component && component.onCheckout) {
+ component.onCheckout();
+ }
+ }
+}
+```
+
+### Fall C: Popup durch Click-Simulation
+
+```javascript
+function triggerCheckoutPopup(assetId) {
+ var button = document.querySelector('[data-action="checkout"]');
+ if (button) {
+ button.click();
+ }
+}
+```
+
+## ⚡ Troubleshooting
+
+**Problem:** Kein `[MODAL OPENED]` Log
+**Lösung:** Popup wird möglicherweise anders erstellt. Suchen Sie in Network-Tab nach API-Calls
+
+**Problem:** Zu viele Logs
+**Lösung:** Console Filter nutzen: `[MODAL]` oder `[FETCH]`
+
+**Problem:** `webdrop_debug` not defined
+**Lösung:** Debug-Script wurde nicht richtig geladen. main_window.py prüfen.
+
+**Problem:** Angular DevTools fehlt
+**Lösung:** Chrome Extension installieren: "Angular DevTools"
+
+## 📞 Nächste Schritte
+
+1. ✅ Debug-Script aktivieren (oben Schritt 1)
+2. ✅ Logs sammeln (Schritt 3-6)
+3. ✅ Ergebnisse notieren (Merkblatt)
+4. → **Ergebnisse mitteilen** → Ich helfe bei Implementation!
+
+## 📄 Zusätzliche Dokumentation
+
+- [DRAG_DROP_PROBLEM_ANALYSIS.md](DRAG_DROP_PROBLEM_ANALYSIS.md) - Detaillierte Problem-Analyse
+- [SCRIPT_VARIANTS.md](SCRIPT_VARIANTS.md) - Alle verfügbaren Scripts
+- [ANGULAR_CDK_ANALYSIS.md](ANGULAR_CDK_ANALYSIS.md) - Angular Framework Details
+
+---
+
+**Hinweis:** Das Debug-Script hat Performance-Overhead. Nach dem Debugging zurück zu `bridge_script.js` wechseln!
diff --git a/docs/ANGULAR_CDK_ANALYSIS.md b/docs/ANGULAR_CDK_ANALYSIS.md
new file mode 100644
index 0000000..5e434ad
--- /dev/null
+++ b/docs/ANGULAR_CDK_ANALYSIS.md
@@ -0,0 +1,268 @@
+# Angular CDK Drag & Drop Analysis - GlobalDAM
+
+## Framework Detection
+
+**Web Application:** Agravity GlobalDAM
+**Framework:** Angular 19.2.14
+**Drag & Drop:** Angular CDK (Component Dev Kit)
+**Styling:** TailwindCSS
+
+## Technical Findings
+
+### 1. Angular CDK Implementation
+
+```html
+
+
+```
+
+### 2. Key Observations
+
+#### Native HTML5 Drag ist DEAKTIVIERT
+```html
+draggable="false"
+```
+
+**Bedeutung:**
+- Kein Zugriff auf native `dragstart`, `drag`, `dragend` Events
+- Kein `event.dataTransfer` API verfügbar
+- Angular CDK simuliert Drag & Drop komplett in JavaScript
+- Daten werden NICHT über natives Clipboard/DataTransfer übertragen
+
+#### Angular CDK Direktiven
+- `cdkdroplistgroup` - Gruppiert mehrere Drop-Zonen
+- `cdkdroplist` - Markiert Drop-Bereiche (Collections, Clipboard)
+- `cdkdrag` - Markiert draggbare Elemente (Assets)
+- `cdkdroplistsortingdisabled` - Sortierung deaktiviert
+
+#### Asset Identifikation
+```html
+
+
+
+
+

+
+
+
![weiss_ORIGINAL]()
+```
+
+## Impact on WebDrop Bridge
+
+### ❌ Bisheriger Ansatz funktioniert NICHT
+
+Unser aktueller Ansatz basiert auf:
+1. Interception von nativen Drag-Events
+2. Manipulation von `event.dataTransfer.effectAllowed` und `.dropEffect`
+3. Setzen von URLs im DataTransfer
+
+**Das funktioniert NICHT mit Angular CDK**, da:
+- Angular CDK das native Drag & Drop komplett umgeht
+- Keine nativen Events gefeuert werden
+- DataTransfer API nicht verwendet wird
+
+### ✅ Mögliche Lösungsansätze
+
+#### Ansatz 1: JavaScript Injection zur Laufzeit
+Injiziere JavaScript-Code, der Angular CDK Events abfängt:
+
+```javascript
+// Überwache Angular CDK Event-Handler
+document.addEventListener('cdkDragStarted', (event) => {
+ const assetId = event.source.element.nativeElement.id;
+ const assetName = event.source.element.nativeElement.querySelector('img')?.alt;
+
+ // Sende an Qt WebChannel
+ bridge.handleDragStart(assetId, assetName);
+});
+
+document.addEventListener('cdkDragDropped', (event) => {
+ // Verhindere das Standard-Verhalten
+ event.preventDefault();
+
+ // Starte nativen Drag von Qt aus
+ bridge.initNativeDrag();
+});
+```
+
+**Vorteile:**
+- ✅ Direkter Zugriff auf Angular CDK Events
+- ✅ Kann Asset-Informationen extrahieren
+- ✅ Kann Drag-Operationen abfangen
+
+**Nachteile:**
+- ⚠️ Erfordert genaue Kenntnis der Angular CDK Internals
+- ⚠️ Könnte bei Angular CDK Updates brechen
+- ⚠️ Komplexer zu implementieren
+
+#### Ansatz 2: DOM Mutation Observer
+Überwache DOM-Änderungen während des Drags:
+
+```javascript
+const observer = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ // Suche nach CDK Drag-Elementen mit bestimmten Klassen
+ const dragElement = document.querySelector('.cdk-drag-preview');
+ if (dragElement) {
+ const assetId = dragElement.querySelector('[id^="a"]')?.id;
+ bridge.handleDrag(assetId);
+ }
+ });
+});
+
+observer.observe(document.body, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ attributeFilter: ['class']
+});
+```
+
+**Vorteile:**
+- ✅ Robuster gegenüber Framework-Updates
+- ✅ Funktioniert mit beliebigen Frameworks
+
+**Nachteile:**
+- ⚠️ Performance-Overhead
+- ⚠️ Kann falsche Positive erzeugen
+
+#### Ansatz 3: Qt WebChannel Bridge mit Custom Events
+Nutze Qt WebChannel, um mit der Angular-Anwendung zu kommunizieren:
+
+```python
+# Python-Seite (Qt)
+class DragBridge(QObject):
+ @Slot(str, str)
+ def onAssetDragStart(self, asset_id: str, asset_name: str):
+ """Called from JavaScript when Angular CDK drag starts."""
+ logger.info(f"Asset drag started: {asset_id} ({asset_name})")
+ self.convert_and_drag(asset_id, asset_name)
+```
+
+```javascript
+// JavaScript-Seite (injiziert via QWebEngineScript)
+new QWebChannel(qt.webChannelTransport, (channel) => {
+ const dragBridge = channel.objects.dragBridge;
+
+ // Monkey-patch Angular CDK's DragRef
+ const originalStartDraggingSequence = CdkDrag.prototype._startDraggingSequence;
+ CdkDrag.prototype._startDraggingSequence = function(event) {
+ const assetElement = this.element.nativeElement;
+ const assetId = assetElement.id;
+ const assetName = assetElement.querySelector('img')?.alt;
+
+ // Benachrichtige Qt
+ dragBridge.onAssetDragStart(assetId, assetName);
+
+ // Rufe original Angular CDK Methode auf
+ return originalStartDraggingSequence.call(this, event);
+ };
+});
+```
+
+**Vorteile:**
+- ✅ Saubere Kommunikation zwischen Qt und Web
+- ✅ Kann Asset-Informationen zuverlässig extrahieren
+- ✅ Typensicher (Qt Signals/Slots)
+
+**Nachteile:**
+- ⚠️ Erfordert Monkey-Patching von Angular CDK
+- ⚠️ Kann bei CDK Updates brechen
+
+#### Ansatz 4: Browser DevTools Protocol (Chrome DevTools)
+Nutze Chrome DevTools Protocol für tiefere Integration:
+
+```python
+from PySide6.QtWebEngineCore import QWebEngineProfile
+
+profile = QWebEngineProfile.defaultProfile()
+profile.setRequestInterceptor(...)
+
+# Intercepte Netzwerk-Requests und injiziere Header
+# Überwache JavaScript-Execution via CDP
+```
+
+**Vorteile:**
+- ✅ Sehr mächtig, kann JavaScript-Execution überwachen
+- ✅ Kann Events auf niedrigerer Ebene abfangen
+
+**Nachteile:**
+- ⚠️ Sehr komplex
+- ⚠️ Erfordert Chrome DevTools Protocol Kenntnisse
+- ⚠️ Performance-Overhead
+
+## Empfohlener Ansatz
+
+### **Ansatz 3: Qt WebChannel Bridge** (BEVORZUGT)
+
+**Begründung:**
+1. ✅ Saubere Architektur mit klarer Trennung
+2. ✅ Typsicher durch Qt Signals/Slots
+3. ✅ Kann Asset-IDs und -Namen zuverlässig extrahieren
+4. ✅ Funktioniert auch wenn Angular CDK interne Änderungen hat
+5. ✅ Ermöglicht bidirektionale Kommunikation
+
+**Implementierungsschritte:**
+
+### Phase 1: Asset-Informationen extrahieren
+1. JavaScript via QWebEngineScript injizieren
+2. Qt WebChannel setuppen
+3. Angular CDK Events überwachen (ohne Monkey-Patching als Test)
+4. Asset-IDs und Namen an Qt senden
+
+### Phase 2: Native Drag initiieren
+1. Bei CDK Drag-Start: Extrahiere Asset-Informationen
+2. Sende Asset-ID an Backend/API
+3. Erhalte lokalen Dateipfad oder Azure Blob URL
+4. Konvertiere zu lokalem Pfad (wie aktuell)
+5. Initiiere nativen Drag mit QDrag
+
+### Phase 3: Drag-Feedback
+1. Zeige Drag-Preview in Qt (optional)
+2. Update Cursor während Drag
+3. Cleanup nach Drag-Ende
+
+## Asset-ID zu Dateipfad Mapping
+
+Die Anwendung verwendet Asset-IDs in mehreren Formaten:
+
+```javascript
+// Asset-ID: anPGZszKzgKaSz1SIx2HFgduy
+
+// Mögliche URL-Konstruktion:
+const assetUrl = `https://dev.agravity.io/api/assets/${assetId}`;
+const downloadUrl = `https://dev.agravity.io/api/assets/${assetId}/download`;
+const blobUrl = `https://static.agravity.io/${workspaceId}/${assetId}/${filename}`;
+```
+
+**Für WebDrop Bridge:**
+- Asset-ID aus DOM extrahieren
+- Asset-Metadaten via API abrufen (falls verfügbar)
+- Blob-URL konstruieren
+- URL Converter nutzen (bereits implementiert!)
+
+## Next Steps
+
+1. **Proof of Concept**: Qt WebChannel mit einfachem Event-Logger
+2. **Asset-ID Extraction**: JavaScript Injection testen
+3. **API Research**: GlobalDAM API untersuchen (Asset-Metadaten)
+4. **Integration**: Mit bestehendem URLConverter verbinden
+5. **Testing**: Mit echten Assets testen
+
+## Hinweise
+
+- Angular CDK Version kann sich unterscheiden - Code muss robust sein
+- Asset-IDs scheinen eindeutig zu sein (Base64-ähnlich)
+- Die Anwendung nutzt Azure Blob Storage (basierend auf bisherigen URLs)
+- Custom Components (`ay-*`) deuten auf eine eigene Component Library hin
diff --git a/docs/DRAG_DROP_PROBLEM_ANALYSIS.md b/docs/DRAG_DROP_PROBLEM_ANALYSIS.md
new file mode 100644
index 0000000..7e6906d
--- /dev/null
+++ b/docs/DRAG_DROP_PROBLEM_ANALYSIS.md
@@ -0,0 +1,277 @@
+# Drag & Drop Problem Analysis - File Drop + Web App Popup
+
+## Das Kernproblem
+
+**Ziel**: Bei ALT-Drag soll:
+1. ✅ File gedroppt werden (Z:\ Laufwerk) → Native File-Drop
+2. ✅ Web-App Popup erscheinen (Auschecken-Dialog) → Web-App Drop-Event
+
+**Problem**: Diese beiden schließen sich gegenseitig aus:
+- Native File-Drag (von Qt) → Web-App bekommt kein Drop-Event → Kein Popup
+- Browser Text-Drag (von Web-App) → Kein File-Drop → Kein File
+
+## Browser-Sicherheitsbeschränkungen
+
+1. **DataTransfer.files ist read-only**
+ Wir können keine Files zu einem DataTransfer hinzufügen
+
+2. **Nur EIN Drag zur Zeit möglich**
+ Wir können keinen parallelen Drag starten
+
+3. **DataTransfer kann nicht erstellt werden**
+ Wir können kein synthetisches Drop-Event mit Files erzeugen
+
+4. **Cross-Domain Sicherheit**
+ File-Zugriff ist stark eingeschränkt
+
+## Getestete Ansätze
+
+### ❌ Ansatz 1: DataTransfer erweitern
+**Idee**: Web-App setzt URL, wir fügen Files hinzu
+**Problem**: DataTransfer.files ist read-only
+
+### ❌ Ansatz 2: Parallele Drags
+**Idee**: Browser-Drag + Qt-Drag gleichzeitig
+**Problem**: Nur ein Drag zur Zeit möglich
+
+### ❌ Ansatz 3: Synthetisches Drop-Event
+**Idee**: Original Drop abfangen, neues Event mit Files erzeugen
+**Problem**: DataTransfer.files kann nicht gesetzt werden
+
+### ⚠️ Ansatz 4: Native Drag + Event-Simulation
+**Idee**: Qt Native Drag, dann Web-App Event manuell auslösen
+**Problem**: Erfordert Kenntnis der exakten Web-App Event-Struktur (Angular CDK)
+
+## 🎯 Praktikable Lösungen
+
+### Lösung A: **Zwei-Phasen Ansatz** (EMPFOHLEN für Testing)
+
+**Phase 1: File-Drop**
+1. ALT-Drag startet
+2. JavaScript erkennt convertible URL
+3. Ruft Qt's `start_file_drag(url)` auf
+4. `preventDefault()` verhindert Browser-Drag
+5. Qt Native Drag läuft
+6. File wird gedroppt ✅
+
+**Phase 2: Popup manuell triggern**
+7. Nach erfolgreichem Drop (via Qt Signal)
+8. JavaScript simuliert den Event/API-Call der das Popup auslöst
+9. Popup erscheint ✅
+
+**Vorteile:**
+- ✅ Funktioniert garantiert für File-Drop
+- ✅ Kontrolle über beide Phasen
+- ✅ Kann debugged werden
+
+**Nachteile:**
+- ⚠️ Erfordert Reverse-Engineering des Popup-Triggers
+- ⚠️ Könnte bei Web-App Updates brechen
+
+**Implementierung:**
+```javascript
+// Phase 1: Standard - File Drag
+document.addEventListener('dragstart', function(e) {
+ if (!e.altKey) return;
+
+ // Extract URL from DataTransfer
+ var url = e.dataTransfer.getData('text/plain');
+ if (isConvertible(url)) {
+ e.preventDefault();
+ window.bridge.start_file_drag(url);
+ }
+}, true);
+
+// Phase 2: Popup Trigger (nach Drop-Erfolg)
+// Qt ruft auf: window.trigger_checkout_popup(assetId)
+window.trigger_checkout_popup = function(assetId) {
+ // Option 1: Klick auf Checkout-Button simulieren
+ // Option 2: API-Call direkt aufrufen
+ // Option 3: Angular-Event dispatchen
+
+ // Beispiel: Suche nach Angular-Component und rufe Methode auf
+ var angularComponent = findAngularComponent('ay-asset-card');
+ if (angularComponent) {
+ angularComponent.onCheckout(assetId);
+ }
+};
+```
+
+**TODO für diese Lösung:**
+1. ✅ File-Drag funktioniert bereits
+2. ⏸️ Herausfinden wie Popup ausgelöst wird:
+ - Browser DevTools öffnen
+ - Network-Tab beobachten (API-Call?)
+ - Elements-Tab nutzen (Angular Component?)
+ - Event-Listeners ansehen
+
+### Lösung B: **Gar nichts ändern beim Drag** (FALLBACK)
+
+**Ansatz:**
+1. ALT-Drag läuft normal durch (URL-Text wird gedroppt)
+2. Popup erscheint ✅
+3. Nach Popup-Bestätigung (Auschecken)
+4. **DANN** konvertieren wir die URL und kopieren das File
+
+**Vorteile:**
+- ✅ Web-App funktioniert normal
+- ✅ Kein Popup-Problem
+- ✅ Einfach zu implementieren
+
+**Nachteile:**
+- ⚠️ Kein echter Drag & Drop (nur Copy)
+- ⚠️ User muss zweimal handeln
+
+### Lösung C: **File-Drop via Qt Window Overlay** (EXPERIMENTELL)
+
+**Ansatz:**
+1. Beim ALT-Drag: Start Normal Web-App Drag
+2. Qt erstellt ein transparentes Overlay-Window über dem Browser
+3. Overlay fängt das Drop-Event ab
+4. Qt macht File-Drop
+5. Qt leitet Drop-Koordinaten an Browser weiter
+6. Browser bekommt synthetisches Event (ohne Files, nur Koordinaten)
+7. Popup erscheint
+
+**Vorteile:**
+- ✅ Beide Funktionen potentiell möglich
+- ✅ Keine DataTransfer-Manipulation nötig
+
+**Nachteile:**
+- ⚠️ Sehr komplex
+- ⚠️ Plattform-spezifisch (Windows)
+- ⚠️ Performance-Overhead
+
+## 🔬 Nächste Schritte - Reverse Engineering
+
+Um Lösung A zu implementieren, müssen wir herausfinden:
+
+### 1. Wie wird das Popup ausgelöst?
+
+**Debug-Schritte:**
+```javascript
+// In Browser Console ausführen während ALT-Drag+Drop
+
+// Methode 1: Event-Listener finden
+getEventListeners(document)
+
+// Methode 2: Angular Component finden
+var cards = document.querySelectorAll('ay-asset-card');
+var component = ng.getComponent(cards[0]); // Angular DevTools
+console.log(component);
+
+// Methode 3: Network-Tab
+// Schauen ob API-Call gemacht wird nach Drop
+```
+
+**Erwartete Möglichkeiten:**
+- API-Call zu `/api/assets/{id}/checkout`
+- Angular Event: `cdkDropListDropped`
+- Component-Methode: `onAssetDropped()` oder ähnlich
+- Modal-Service: `ModalService.show('checkout-dialog')`
+
+### 2. Asset-ID extrahieren
+
+Die Asset-ID wird benötigt für den Popup-Trigger:
+
+```javascript
+// Asset-ID ist wahrscheinlich in:
+// - Element-ID: "anPGZszKzgKaSz1SIx2HFgduy"
+// - Image-URL: "./GlobalDAM JRI_files/anPGZszKzgKaSz1SIx2HFgduy"
+// - Data-Attribut: data-asset-id
+
+function extractAssetId(element) {
+ // Aus Element-ID
+ if (element.id && element.id.match(/^a[A-Za-z0-9_-]+$/)) {
+ return element.id;
+ }
+
+ // Aus Bild-URL
+ var img = element.querySelector('img');
+ if (img && img.src) {
+ var match = img.src.match(/([A-Za-z0-9_-]{20,})/);
+ if (match) return match[1];
+ }
+
+ return null;
+}
+```
+
+### 3. Popup programmatisch öffnen
+
+Sobald wir wissen wie, implementieren wir:
+
+```python
+# Python (Qt)
+class DragBridge(QObject):
+ @Slot(str)
+ def on_file_dropped_success(self, local_path: str):
+ """Called after successful file drop."""
+ # Extrahiere Asset-ID aus Pfad oder URL-Mapping
+ asset_id = self.extract_asset_id(local_path)
+
+ # Trigger Popup via JavaScript
+ js_code = f"window.trigger_checkout_popup('{asset_id}')"
+ self.web_view.page().runJavaScript(js_code)
+```
+
+## ⚡ Quick Win: Debugging aktivieren
+
+Fügen Sie zu **allen** JavaScript-Varianten umfangreiches Logging hinzu:
+
+```javascript
+// In bridge_script.js
+
+// Log ALLE Events
+['dragstart', 'drag', 'dragenter', 'dragover', 'drop', 'dragend'].forEach(function(eventName) {
+ document.addEventListener(eventName, function(e) {
+ console.log('[DEBUG]', eventName, {
+ target: e.target.tagName,
+ dataTransfer: {
+ types: e.dataTransfer.types,
+ effectAllowed: e.dataTransfer.effectAllowed,
+ dropEffect: e.dataTransfer.dropEffect
+ },
+ altKey: e.altKey,
+ coordinates: {x: e.clientX, y: e.clientY}
+ });
+ }, true);
+});
+
+// Log DataTransfer Zugriffe
+Object.defineProperty(DataTransfer.prototype, 'types', {
+ get: function() {
+ var types = this._types || [];
+ console.log('[DEBUG] DataTransfer.types accessed:', types);
+ return types;
+ }
+});
+```
+
+## 📝 Empfehlung
+
+**Sofortige Maßnahmen:**
+
+1. ✅ **Lösung A Phase 1 ist bereits implementiert** (File-Drop funktioniert)
+
+2. 🔍 **Reverse-Engineering durchführen:**
+ - GlobalDAM JRI im Browser öffnen
+ - DevTools öffnen (F12)
+ - ALT-Drag+Drop durchführen
+ - Beobachten:
+ - Network-Tab → API-Calls?
+ - Console → Fehler/Logs?
+ - Angular DevTools → Component-Events?
+
+3. 🛠️ **Popup-Trigger implementieren:**
+ - Sobald bekannt WIE Popup ausgelöst wird
+ - JavaScript-Funktion `trigger_checkout_popup()` erstellen
+ - Von Qt aus nach erfolgreichem Drop aufrufen
+
+4. 🧪 **Testen:**
+ - ALT-Drag eines Assets
+ - File-Drop sollte funktionieren
+ - Popup sollte erscheinen
+
+**Fallback:**
+Falls Reverse-Engineering zu komplex ist → **Lösung B** verwenden (Kein Drag, nur Copy nach Popup-Bestätigung)
diff --git a/docs/SCRIPT_VARIANTS.md b/docs/SCRIPT_VARIANTS.md
new file mode 100644
index 0000000..cd21eed
--- /dev/null
+++ b/docs/SCRIPT_VARIANTS.md
@@ -0,0 +1,171 @@
+# WebDrop Bridge - Drag & Drop Script Varianten
+
+## Verfügbare JavaScript-Scripte
+
+### 1. `bridge_script.js` (CURRENT - Original)
+**Status:** Versucht Drag zu ersetzen, verhindert Popup
+**Verwendung:** Testing, zeigt dass File-Drop prinzipiell funktioniert
+**Problem:** Web-App Popup erscheint nicht
+
+### 2. `bridge_script_debug.js` (DEBUG - **EMPFOHLEN ZUM START**)
+**Status:** Umfangreiches Logging, keine Manipulation
+**Verwendung:** Herausfinden wie Popup ausgelöst wird
+**Funktionen:**
+- Loggt ALLE Drag/Drop Events
+- Überwacht DataTransfer.setData
+- Überwacht Network-Requests (API-Calls)
+- Erkennt Angular Components
+- Erkennt Modal/Popup Öffnungen
+- Bietet Helper-Funktionen in Console
+
+**So verwenden:**
+```python
+# In main_window.py Zeile ~433 ändern:
+script_path = Path(__file__).parent / "bridge_script_debug.js" # <-- DEBUG aktivieren
+```
+
+**Im Browser:**
+1. F12 → Console öffnen
+2. ALT-Drag+Drop durchführen
+3. Logs analysieren:
+ - `[MODAL OPENED]` → Popup-Trigger gefunden!
+ - `[FETCH]` oder `[XHR]` → API-Call gefunden!
+ - `[EVENT]` → Drag-Flow verstehen
+
+### 3. `bridge_script_v2.js` (EXTEND - Experimentell)
+**Status:** Versuch DataTransfer zu erweitern
+**Problem:** Browser-Sicherheit verhindert Files hinzufügen
+
+### 4. `bridge_script_hybrid.js` (HYBRID - Experimentell)
+**Status:** Versuch parallele Drags
+**Problem:** Nur ein Drag zur Zeit möglich
+
+### 5. `bridge_script_drop_intercept.js` (DROP - Experimentell)
+**Status:** Drop-Event abfangen und manipulieren
+**Problem:** DataTransfer kann nicht mit Files erstellt werden
+
+## 🎯 Empfohlener Workflow
+
+### Phase 1: Debugging (JETZT)
+
+1. **Debug-Script aktivieren:**
+ ```python
+ # main_window.py, _install_bridge_script()
+ script_path = Path(__file__).parent / "bridge_script_debug.js"
+ ```
+
+2. **Anwendung starten und testen:**
+ ```powershell
+ python -m webdrop_bridge.main
+ ```
+
+3. **In Browser (F12 Console):**
+ - ALT-Drag+Drop durchführen
+ - Logs kopieren und analysieren
+ - Nach `[MODAL OPENED]` oder API-Calls suchen
+
+4. **Herausfinden:**
+ - ✅ Wie wird Popup ausgelöst? (API-Call, Event, Component-Methode)
+ - ✅ Welche Asset-ID wird verwendet?
+ - ✅ Wo im DOM befindet sich die Popup-Logik?
+
+### Phase 2: Implementation (DANACH)
+
+Basierend auf Debug-Ergebnissen:
+
+**Fall A: Popup wird durch API-Call ausgelöst**
+```javascript
+// In bridge_script.js nach erfolgreichem Drop:
+fetch('/api/assets/' + assetId + '/checkout', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'}
+}).then(response => {
+ console.log('Checkout popup triggered');
+});
+```
+
+**Fall B: Popup wird durch Angular-Event ausgelöst**
+```javascript
+// Trigger Angular CDK Event
+var dropList = document.querySelector('[cdkdroplist]');
+if (dropList && window.ng) {
+ var component = ng.getComponent(dropList);
+ component.onDrop({assetId: assetId});
+}
+```
+
+**Fall C: Popup wird durch Component-Methode ausgelöst**
+```javascript
+// Direkter Methoden-Aufruf
+var assetCard = document.getElementById(assetId);
+if (assetCard && window.ng) {
+ var component = ng.getComponent(assetCard);
+ component.showCheckoutDialog();
+}
+```
+
+### Phase 3: Testing (FINAL)
+
+1. Implementation in `bridge_script.js` integrieren
+2. Beide Funktionen testen:
+ - ✅ File wird gedroppt (Z:\ Laufwerk)
+ - ✅ Popup erscheint (Auschecken-Dialog)
+
+## 🔧 Script-Wechsel in Code
+
+```python
+# src/webdrop_bridge/ui/main_window.py
+# Zeile ~433 in _install_bridge_script()
+
+# ORIGINAL (funktioniert, aber kein Popup):
+script_path = Path(__file__).parent / "bridge_script.js"
+
+# DEBUG (für Analyse):
+script_path = Path(__file__).parent / "bridge_script_debug.js"
+
+# Oder via Konfiguration:
+script_name = self.config.bridge_script or "bridge_script.js"
+script_path = Path(__file__).parent / script_name
+```
+
+## 📝 Debug-Checkliste
+
+Beim Testen mit Debug-Script, notieren Sie:
+
+- [ ] Wann erscheint das Popup? (Nach Drop, nach Verzögerung, sofort?)
+- [ ] Gibt es API-Calls? (Welche URL, Parameter, Zeitpunkt?)
+- [ ] Welche Angular-Events feuern? (CDK Events, Custom Events?)
+- [ ] Wo wird Modal/Dialog erstellt? (DOM-Position, Klassen, Component)
+- [ ] Welche Asset-Informationen werden benötigt? (ID, Name, URL?)
+- [ ] Stack-Trace beim Modal-Öffnen? (console.trace Ausgabe)
+
+## 🚀 Quick Commands
+
+```powershell
+# App mit Debug-Script starten
+python -m webdrop_bridge.main
+
+# In Browser Console (F12):
+webdrop_debug.getEventCounts() # Event-Statistiken
+webdrop_debug.resetCounters() # Zähler zurücksetzen
+webdrop_debug.getListeners(element) # Event-Listener auflisten
+webdrop_debug.getComponent(element) # Angular Component anzeigen
+
+# Logs filtern:
+# Console Filter: "[MODAL]" → Nur Popup-Logs
+# Console Filter: "[EVENT]" → Nur Event-Logs
+# Console Filter: "[FETCH]|[XHR]" → Nur Network-Logs
+```
+
+## ⚠️ Bekannte Limitationen
+
+1. **Angular DevTools benötigt:** Für `ng.getComponent()` Installation nötig
+2. **Chrome/Edge:** Einige Features funktionieren nur in Chromium-Browsern
+3. **CSP:** Bei strengen Content-Security-Policies können Logs blockiert werden
+4. **Performance:** Debug-Script hat deutlichen Performance-Overhead
+
+## 📚 Weiterführende Dokumentation
+
+- [ANGULAR_CDK_ANALYSIS.md](ANGULAR_CDK_ANALYSIS.md) - Angular Framework Details
+- [DRAG_DROP_PROBLEM_ANALYSIS.md](DRAG_DROP_PROBLEM_ANALYSIS.md) - Problem-Analyse + Lösungen
+- [ARCHITECTURE.md](ARCHITECTURE.md) - Gesamtarchitektur
diff --git a/src/webdrop_bridge/ui/bridge_script.js b/src/webdrop_bridge/ui/bridge_script.js
index 622a0e4..ad8d4ce 100644
--- a/src/webdrop_bridge/ui/bridge_script.js
+++ b/src/webdrop_bridge/ui/bridge_script.js
@@ -141,26 +141,71 @@
console.log('[WebDrop Bridge] isZDrive:', isZDrive, 'isAzureUrl:', isAzureUrl);
if (isZDrive || isAzureUrl) {
- console.log('[WebDrop Bridge] >>> CONVERTING URL TO NATIVE DRAG');
+ console.log('[WebDrop Bridge] >>> CONVERTING URL TO NATIVE DRAG (DELAYED)');
if (window.bridge && typeof window.bridge.debug_log === 'function') {
- window.bridge.debug_log('Convertible URL detected - preventing browser drag');
+ window.bridge.debug_log('Convertible URL detected - delaying Qt drag for Angular events');
}
- // Prevent the browser's drag operation
- e.preventDefault();
- e.stopPropagation();
+ // DON'T prevent immediately - let Angular process dragstart for ~200ms
+ // This allows web app to register the drag and prepare popup
- // Start native file drag via Qt
- ensureChannel(function() {
- if (window.bridge && typeof window.bridge.start_file_drag === 'function') {
- console.log('[WebDrop Bridge] Calling start_file_drag:', path.substring(0, 60));
- window.bridge.start_file_drag(path);
- currentDragData = null;
- } else {
- console.error('[WebDrop Bridge] bridge.start_file_drag not available!');
+ // Store URL and element for later
+ window.__lastDraggedUrl = path;
+ var originalTarget = e.target;
+
+ // After 200ms: Cancel browser drag and start Qt drag
+ setTimeout(function() {
+ console.log('[WebDrop Bridge] Starting Qt drag now, browser drag will be cancelled');
+
+ // Try to cancel browser drag by creating a fake drop on same element
+ try {
+ var dropEvent = new DragEvent('drop', {
+ bubbles: true,
+ cancelable: true,
+ view: window
+ });
+ originalTarget.dispatchEvent(dropEvent);
+ } catch(err) {
+ console.log('[WebDrop Bridge] Could not dispatch drop event:', err);
}
- });
+
+ // Hide Angular CDK overlays
+ var style = document.createElement('style');
+ style.id = 'webdrop-bridge-hide-overlay';
+ style.textContent = `
+ .cdk-drag-animating,
+ .cdk-drag-preview,
+ .cdk-drag-placeholder,
+ [cdkdroplist].cdk-drop-list-dragging,
+ #root-collection-drop-area,
+ [id*="drop-area"] {
+ opacity: 0 !important;
+ pointer-events: none !important;
+ display: none !important;
+ }
+ `;
+ document.head.appendChild(style);
+
+ // Start Qt drag
+ ensureChannel(function() {
+ if (window.bridge && typeof window.bridge.start_file_drag === 'function') {
+ console.log('[WebDrop Bridge] Calling start_file_drag:', path.substring(0, 60));
+ window.bridge.start_file_drag(path);
+ currentDragData = null;
+
+ // Cleanup after 5 seconds
+ setTimeout(function() {
+ var hideStyle = document.getElementById('webdrop-bridge-hide-overlay');
+ if (hideStyle) hideStyle.remove();
+ }, 5000);
+ } else {
+ console.error('[WebDrop Bridge] bridge.start_file_drag not available!');
+ }
+ });
+ }, 200); // 200ms delay
+
+ // Let the browser drag start naturally (no preventDefault yet)
return false;
} else {
@@ -203,6 +248,72 @@
}, 2000); // Wait 2 seconds after DOM ready
}
+ // Global function to trigger checkout after successful file drop
+ window.trigger_checkout_for_asset = function(azure_url) {
+ console.log('[WebDrop Bridge] trigger_checkout_for_asset called for:', azure_url);
+
+ // Extract asset ID from Azure URL
+ // Format: https://devagravitystg.file.core.windows.net/devagravitysync/{assetId}/{filename}
+ var match = azure_url.match(/\/devagravitysync\/([^\/]+)\//);
+ if (!match) {
+ console.error('[WebDrop Bridge] Could not extract asset ID from URL:', azure_url);
+ return;
+ }
+
+ var assetId = match[1];
+ console.log('[WebDrop Bridge] Extracted asset ID:', assetId);
+ console.log('[WebDrop Bridge] Calling checkout API directly...');
+
+ // Direct API call to checkout asset (skip popup, auto-checkout)
+ var apiUrl = 'https://devagravityprivate.azurewebsites.net/api/assets/' + assetId + '/checkout';
+
+ fetch(apiUrl, {
+ method: 'POST',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ // Empty body or add checkout parameters if needed
+ })
+ }).then(function(response) {
+ if (response.ok) {
+ console.log('[WebDrop Bridge] ✅ Asset checked out successfully:', assetId);
+ return response.json();
+ } else {
+ console.warn('[WebDrop Bridge] ⚠️ Checkout API returned status:', response.status);
+ // Try alternative: Mark as checked out without confirmation
+ return tryAlternativeCheckout(assetId);
+ }
+ }).then(function(data) {
+ console.log('[WebDrop Bridge] Checkout response:', data);
+ }).catch(function(err) {
+ console.error('[WebDrop Bridge] ❌ Checkout API error:', err);
+ // Fallback: Try alternative checkout method
+ tryAlternativeCheckout(assetId);
+ });
+ };
+
+ // Alternative checkout method if direct API fails
+ function tryAlternativeCheckout(assetId) {
+ console.log('[WebDrop Bridge] Trying alternative checkout for:', assetId);
+
+ // Option 1: Try GET to fetch asset status, might trigger checkout tracking
+ var statusUrl = 'https://devagravityprivate.azurewebsites.net/api/assets/' + assetId;
+
+ return fetch(statusUrl, {
+ method: 'GET',
+ credentials: 'include'
+ }).then(function(response) {
+ if (response.ok) {
+ console.log('[WebDrop Bridge] Asset status fetched, might have logged usage');
+ }
+ return response.json();
+ }).catch(function(err) {
+ console.error('[WebDrop Bridge] Alternative checkout also failed:', err);
+ });
+ }
+
// Install after DOM is ready
if (document.readyState === 'loading') {
console.log('[WebDrop Bridge] Waiting for DOMContentLoaded...');
diff --git a/src/webdrop_bridge/ui/bridge_script_debug.js b/src/webdrop_bridge/ui/bridge_script_debug.js
new file mode 100644
index 0000000..3fe16a1
--- /dev/null
+++ b/src/webdrop_bridge/ui/bridge_script_debug.js
@@ -0,0 +1,333 @@
+// WebDrop Bridge - DEBUG Version
+// Heavy logging to understand web app's drag&drop behavior
+//
+// Usage:
+// 1. Load this script
+// 2. Perform ALT-drag+drop in web app
+// 3. Check console for detailed logs
+// 4. Look for: API calls, events names, component methods
+
+(function() {
+ if (window.__webdrop_debug_injected) return;
+ window.__webdrop_debug_injected = true;
+
+ console.log('%c[WebDrop DEBUG] Script loaded', 'background: #222; color: #bada55; font-size: 14px; font-weight: bold;');
+
+ // ============================================================================
+ // PART 1: Event Monitoring - see ALL drag/drop related events
+ // ============================================================================
+
+ var allEvents = ['dragstart', 'drag', 'dragenter', 'dragover', 'dragleave', 'drop', 'dragend'];
+ var eventCounts = {};
+
+ allEvents.forEach(function(eventName) {
+ eventCounts[eventName] = 0;
+
+ document.addEventListener(eventName, function(e) {
+ eventCounts[eventName]++;
+
+ // Only log dragstart, drop, dragend fully (others are noisy)
+ if (eventName === 'dragstart' || eventName === 'drop' || eventName === 'dragend') {
+ console.group('%c[EVENT] ' + eventName.toUpperCase(), 'color: #FF6B6B; font-weight: bold;');
+ console.log('Target:', e.target.tagName, e.target.className, e.target.id);
+ console.log('DataTransfer:', {
+ types: Array.from(e.dataTransfer.types),
+ effectAllowed: e.dataTransfer.effectAllowed,
+ dropEffect: e.dataTransfer.dropEffect,
+ files: e.dataTransfer.files.length
+ });
+ console.log('Keys:', {
+ alt: e.altKey,
+ ctrl: e.ctrlKey,
+ shift: e.shiftKey
+ });
+ console.log('Position:', {x: e.clientX, y: e.clientY});
+
+ // Try to read data (only works in drop/dragstart)
+ if (eventName === 'drop' || eventName === 'dragstart') {
+ try {
+ var plainText = e.dataTransfer.getData('text/plain');
+ var uriList = e.dataTransfer.getData('text/uri-list');
+ console.log('Data:', {
+ 'text/plain': plainText ? plainText.substring(0, 100) : null,
+ 'text/uri-list': uriList ? uriList.substring(0, 100) : null
+ });
+ } catch(err) {
+ console.warn('Could not read DataTransfer data:', err.message);
+ }
+ }
+
+ console.groupEnd();
+ }
+ }, true); // Capture phase
+ });
+
+ // Log event summary every 5 seconds
+ setInterval(function() {
+ var hasEvents = Object.keys(eventCounts).some(function(k) { return eventCounts[k] > 0; });
+ if (hasEvents) {
+ console.log('%c[EVENT SUMMARY]', 'color: #4ECDC4; font-weight: bold;', eventCounts);
+ }
+ }, 5000);
+
+ // ============================================================================
+ // PART 2: DataTransfer.setData Interception
+ // ============================================================================
+
+ try {
+ var originalSetData = DataTransfer.prototype.setData;
+
+ DataTransfer.prototype.setData = function(format, data) {
+ console.log('%c[DataTransfer.setData]', 'color: #FFE66D; font-weight: bold;', format, '=',
+ typeof data === 'string' ? data.substring(0, 100) : data);
+ return originalSetData.call(this, format, data);
+ };
+
+ console.log('[WebDrop DEBUG] DataTransfer.setData patched ✓');
+ } catch(e) {
+ console.error('[WebDrop DEBUG] Failed to patch DataTransfer:', e);
+ }
+
+ // ============================================================================
+ // PART 3: Network Monitor - detect API calls (with request bodies)
+ // ============================================================================
+
+ var originalFetch = window.fetch;
+ window.fetch = function() {
+ var url = arguments[0];
+ var options = arguments[1] || {};
+
+ console.log('%c🌐 Fetch called:', 'color: #95E1D3; font-weight: bold;', url);
+
+ // Log headers if present
+ if (options.headers) {
+ console.log('%c[FETCH HEADERS]', 'color: #FFB6C1; font-weight: bold;');
+ console.log(JSON.stringify(options.headers, null, 2));
+ }
+
+ // Log request body if present
+ if (options.body) {
+ try {
+ var bodyPreview = typeof options.body === 'string' ? options.body : JSON.stringify(options.body);
+ if (bodyPreview.length > 200) {
+ bodyPreview = bodyPreview.substring(0, 200) + '... (truncated)';
+ }
+ console.log('%c[FETCH BODY]', 'color: #FFE66D; font-weight: bold;', bodyPreview);
+ } catch(e) {
+ console.log('%c[FETCH BODY]', 'color: #FFE66D; font-weight: bold;', '[Could not stringify]');
+ }
+ }
+
+ return originalFetch.apply(this, arguments);
+ };
+
+ var originalXHROpen = XMLHttpRequest.prototype.open;
+ var originalXHRSend = XMLHttpRequest.prototype.send;
+ var originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
+
+ XMLHttpRequest.prototype.open = function(method, url) {
+ this._webdrop_method = method;
+ this._webdrop_url = url;
+ this._webdrop_headers = {};
+ console.log('%c[XHR]', 'color: #95E1D3; font-weight: bold;', method, url);
+ return originalXHROpen.apply(this, arguments);
+ };
+
+ XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
+ this._webdrop_headers = this._webdrop_headers || {};
+ this._webdrop_headers[header] = value;
+ return originalXHRSetRequestHeader.apply(this, arguments);
+ };
+
+ XMLHttpRequest.prototype.send = function(body) {
+ // Log headers if present
+ if (this._webdrop_headers && Object.keys(this._webdrop_headers).length > 0) {
+ if (this._webdrop_url && this._webdrop_url.includes('checkout')) {
+ console.log('%c[XHR HEADERS - CHECKOUT]', 'background: #FF6B6B; color: white; font-weight: bold; padding: 2px 6px;');
+ console.log(JSON.stringify(this._webdrop_headers, null, 2));
+ } else {
+ console.log('%c[XHR HEADERS]', 'color: #FFB6C1; font-weight: bold;');
+ console.log(JSON.stringify(this._webdrop_headers, null, 2));
+ }
+ }
+
+ // Log request body if present
+ if (body) {
+ try {
+ var bodyPreview = typeof body === 'string' ? body : JSON.stringify(body);
+ if (bodyPreview.length > 200) {
+ bodyPreview = bodyPreview.substring(0, 200) + '... (truncated)';
+ }
+
+ // Highlight checkout API calls
+ if (this._webdrop_url && this._webdrop_url.includes('checkout')) {
+ console.log('%c[XHR BODY - CHECKOUT]', 'background: #FF6B6B; color: white; font-weight: bold; padding: 2px 6px;',
+ this._webdrop_method, this._webdrop_url);
+ console.log('%c[CHECKOUT PAYLOAD]', 'color: #00FF00; font-weight: bold;', bodyPreview);
+ } else {
+ console.log('%c[XHR BODY]', 'color: #FFE66D; font-weight: bold;', bodyPreview);
+ }
+ } catch(e) {
+ console.log('%c[XHR BODY]', 'color: #FFE66D; font-weight: bold;', '[Could not stringify]');
+ }
+ }
+
+ return originalXHRSend.apply(this, arguments);
+ };
+
+ console.log('[WebDrop DEBUG] Network interceptors installed ✓ (with request body logging)');
+
+ // ============================================================================
+ // PART 4: Angular Event Detection (if Angular is used)
+ // ============================================================================
+
+ setTimeout(function() {
+ // Try to detect Angular
+ if (window.ng) {
+ console.log('%c[ANGULAR DETECTED]', 'color: #DD2C00; font-weight: bold;', 'Version:', window.ng.version?.full);
+
+ // Try to find Angular components
+ var cards = document.querySelectorAll('ay-asset-card');
+ console.log('[ANGULAR] Found', cards.length, 'asset cards');
+
+ if (cards.length > 0 && window.ng.getComponent) {
+ try {
+ var component = window.ng.getComponent(cards[0]);
+ console.log('%c[ANGULAR COMPONENT]', 'color: #DD2C00; font-weight: bold;', component);
+ console.log('Methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(component)));
+ } catch(e) {
+ console.warn('[ANGULAR] Could not get component:', e);
+ }
+ }
+ } else {
+ console.log('[WebDrop DEBUG] Angular not detected or DevTools required');
+ }
+
+ // Try to detect CDK
+ if (document.querySelector('[cdkdrag]')) {
+ console.log('%c[ANGULAR CDK] Detected', 'color: #FF6F00; font-weight: bold;');
+
+ // Monitor CDK specific events (if we can access them)
+ // Note: CDK events are often internal, we might need to monkey-patch
+ console.log('[CDK] Drag elements found:', document.querySelectorAll('[cdkdrag]').length);
+ console.log('[CDK] Drop lists found:', document.querySelectorAll('[cdkdroplist]').length);
+ }
+ }, 2000);
+
+ // ============================================================================
+ // PART 5: Modal/Dialog Detection
+ // ============================================================================
+
+ var modalObserver = new MutationObserver(function(mutations) {
+ mutations.forEach(function(mutation) {
+ mutation.addedNodes.forEach(function(node) {
+ if (node.nodeType === 1) { // Element node
+ // Check for common modal/dialog patterns
+ // Safely get className as string (handles SVG elements and undefined)
+ var className = typeof node.className === 'string'
+ ? node.className
+ : (node.className && node.className.baseVal) ? node.className.baseVal : '';
+
+ var isModal = className && (
+ className.includes('modal') ||
+ className.includes('dialog') ||
+ className.includes('popup') ||
+ className.includes('overlay')
+ );
+
+ if (isModal) {
+ console.log('%c[MODAL OPENED]', 'color: #FF6B6B; font-size: 16px; font-weight: bold;');
+ console.log('Modal element:', node);
+ console.log('Classes:', className);
+ console.log('Content:', node.textContent.substring(0, 200));
+
+ // Log the stack trace to see what triggered it
+ console.trace('Modal opened from:');
+ }
+ }
+ });
+ });
+ });
+
+ // Start observer when body is ready
+ function startModalObserver() {
+ if (document.body) {
+ modalObserver.observe(document.body, {
+ childList: true,
+ subtree: true
+ });
+ console.log('[WebDrop DEBUG] Modal observer installed ✓');
+ } else {
+ console.warn('[WebDrop DEBUG] document.body not ready, modal observer skipped');
+ }
+ }
+
+ // Wait for DOM to be ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', startModalObserver);
+ } else {
+ startModalObserver();
+ }
+
+ // ============================================================================
+ // PART 6: Helper Functions for Manual Testing
+ // ============================================================================
+
+ window.webdrop_debug = {
+ // Get all event listeners on an element
+ getListeners: function(element) {
+ if (typeof getEventListeners === 'function') {
+ return getEventListeners(element || document);
+ } else {
+ console.warn('getEventListeners not available. Open Chrome DevTools Console.');
+ return null;
+ }
+ },
+
+ // Find Angular component for an element
+ getComponent: function(element) {
+ if (window.ng && window.ng.getComponent) {
+ return window.ng.getComponent(element);
+ } else {
+ console.warn('Angular DevTools not available. Install Angular DevTools extension.');
+ return null;
+ }
+ },
+
+ // Simulate a drop at coordinates
+ simulateDrop: function(x, y, data) {
+ var dropEvent = new DragEvent('drop', {
+ bubbles: true,
+ cancelable: true,
+ clientX: x,
+ clientY: y
+ });
+
+ // Can't set dataTransfer, but element can be used for testing
+ var target = document.elementFromPoint(x, y);
+ target.dispatchEvent(dropEvent);
+ console.log('Simulated drop at', x, y, 'on', target);
+ },
+
+ // Get event counts
+ getEventCounts: function() {
+ return eventCounts;
+ },
+
+ // Reset counters
+ resetCounters: function() {
+ Object.keys(eventCounts).forEach(function(k) { eventCounts[k] = 0; });
+ console.log('Counters reset');
+ }
+ };
+
+ console.log('%c[WebDrop DEBUG] Helper functions available as window.webdrop_debug', 'color: #95E1D3; font-weight: bold;');
+ console.log('Try: webdrop_debug.getListeners(), webdrop_debug.getComponent(element)');
+
+ // ============================================================================
+ // READY
+ // ============================================================================
+
+ console.log('%c[WebDrop DEBUG] Ready! Perform ALT-drag+drop and watch the logs.',
+ 'background: #222; color: #00FF00; font-size: 14px; font-weight: bold; padding: 4px;');
+})();
diff --git a/src/webdrop_bridge/ui/bridge_script_drop_intercept.js b/src/webdrop_bridge/ui/bridge_script_drop_intercept.js
new file mode 100644
index 0000000..8b49201
--- /dev/null
+++ b/src/webdrop_bridge/ui/bridge_script_drop_intercept.js
@@ -0,0 +1,211 @@
+// WebDrop Bridge - Drop Event Interception Strategy
+// Strategy:
+// 1. Let browser drag proceed normally (web app sets URL in DataTransfer)
+// 2. Intercept DROP event in capture phase
+// 3. Convert URL to local file path
+// 4. Synthesize new DROP event with file data
+// 5. Dispatch synthetic event (web app receives it and shows popup)
+// 6. File gets dropped correctly
+
+(function() {
+ if (window.__webdrop_bridge_drop_intercept) return;
+ window.__webdrop_bridge_drop_intercept = true;
+
+ console.log('[WebDrop Bridge DROP] Script loaded - Drop interception strategy');
+
+ var dragState = {
+ url: null,
+ localPath: null,
+ isConvertible: false,
+ altKeyPressed: false,
+ dragElement: null
+ };
+
+ // Patch DataTransfer.setData to capture URLs during drag
+ try {
+ var originalSetData = DataTransfer.prototype.setData;
+
+ DataTransfer.prototype.setData = function(format, data) {
+ if (format === 'text/plain' || format === 'text/uri-list') {
+ console.log('[WebDrop Bridge DROP] Captured data:', format, '=', data.substring(0, 80));
+
+ if (dragState.altKeyPressed) {
+ dragState.url = data;
+
+ // Check if convertible
+ dragState.isConvertible = /^z:/i.test(data) ||
+ /^https?:\/\/.+\.file\.core\.windows\.net\//i.test(data);
+
+ if (dragState.isConvertible) {
+ console.log('[WebDrop Bridge DROP] >>> CONVERTIBLE URL - will intercept drop');
+
+ // Request conversion NOW (synchronously if possible)
+ ensureChannel(function() {
+ if (window.bridge && typeof window.bridge.convert_url_sync === 'function') {
+ // Synchronous conversion
+ dragState.localPath = window.bridge.convert_url_sync(data);
+ console.log('[WebDrop Bridge DROP] Converted to:', dragState.localPath);
+ } else if (window.bridge && typeof window.bridge.convert_url_to_path === 'function') {
+ // Async conversion (fallback)
+ window.bridge.convert_url_to_path(data);
+ console.log('[WebDrop Bridge DROP] Async conversion requested');
+ }
+ });
+ }
+ }
+ }
+
+ // Always call original
+ return originalSetData.call(this, format, data);
+ };
+
+ console.log('[WebDrop Bridge DROP] DataTransfer patched');
+ } catch(e) {
+ console.error('[WebDrop Bridge DROP] Patch failed:', e);
+ }
+
+ // Callback for async conversion
+ window.webdrop_set_local_path = function(path) {
+ dragState.localPath = path;
+ console.log('[WebDrop Bridge DROP] Local path received:', path);
+ };
+
+ function ensureChannel(cb) {
+ if (window.bridge) { cb(); return; }
+
+ if (window.QWebChannel && window.qt && window.qt.webChannelTransport) {
+ new QWebChannel(window.qt.webChannelTransport, function(channel) {
+ window.bridge = channel.objects.bridge;
+ console.log('[WebDrop Bridge DROP] QWebChannel connected');
+ cb();
+ });
+ }
+ }
+
+ function createSyntheticDropEvent(originalEvent, localPath) {
+ console.log('[WebDrop Bridge DROP] Creating synthetic drop event with file:', localPath);
+
+ try {
+ // Create a new DataTransfer with file
+ // NOTE: This is complex because DataTransfer can't be created directly
+ // We'll create a new DragEvent and try to set files
+
+ var newEvent = new DragEvent('drop', {
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ view: window,
+ detail: originalEvent.detail,
+ screenX: originalEvent.screenX,
+ screenY: originalEvent.screenY,
+ clientX: originalEvent.clientX,
+ clientY: originalEvent.clientY,
+ ctrlKey: originalEvent.ctrlKey,
+ altKey: originalEvent.altKey,
+ shiftKey: originalEvent.shiftKey,
+ metaKey: originalEvent.metaKey,
+ button: originalEvent.button,
+ buttons: originalEvent.buttons,
+ relatedTarget: originalEvent.relatedTarget,
+ // We can't directly set dataTransfer, it's read-only
+ });
+
+ // This is a limitation: We can't create a DataTransfer with files from JavaScript
+ // The only way is to use a real file input or drag a real file
+
+ console.warn('[WebDrop Bridge DROP] Cannot create DataTransfer with files from JS');
+ console.log('[WebDrop Bridge DROP] Will use workaround: modify original DataTransfer');
+
+ return null; // Cannot create synthetic event with files
+
+ } catch(error) {
+ console.error('[WebDrop Bridge DROP] Synthetic event creation failed:', error);
+ return null;
+ }
+ }
+
+ function installHooks() {
+ console.log('[WebDrop Bridge DROP] Installing hooks');
+
+ // Monitor dragstart
+ document.addEventListener('dragstart', function(e) {
+ dragState.altKeyPressed = e.altKey;
+ dragState.url = null;
+ dragState.localPath = null;
+ dragState.isConvertible = false;
+ dragState.dragElement = e.target;
+
+ console.log('[WebDrop Bridge DROP] dragstart, altKey:', e.altKey);
+
+ // Let it proceed
+ }, true);
+
+ // Intercept DROP event
+ document.addEventListener('drop', function(e) {
+ console.log('[WebDrop Bridge DROP] drop event, isConvertible:', dragState.isConvertible);
+
+ if (!dragState.isConvertible || !dragState.localPath) {
+ console.log('[WebDrop Bridge DROP] Not convertible or no path, letting through');
+ return; // Let normal drop proceed
+ }
+
+ console.log('[WebDrop Bridge DROP] >>> INTERCEPTING DROP for conversion');
+
+ // This is the problem: We can't modify the DataTransfer at this point
+ // And we can't create a new one with files from JavaScript
+
+ // WORKAROUND: Tell Qt to handle the drop natively
+ e.preventDefault(); // Prevent browser handling
+ e.stopPropagation();
+
+ // Get drop coordinates
+ var dropX = e.clientX;
+ var dropY = e.clientY;
+
+ console.log('[WebDrop Bridge DROP] Drop at:', dropX, dropY);
+
+ // Tell Qt to perform native file drop at these coordinates
+ ensureChannel(function() {
+ if (window.bridge && typeof window.bridge.handle_native_drop === 'function') {
+ window.bridge.handle_native_drop(dragState.localPath, dropX, dropY);
+ }
+ });
+
+ // THEN manually trigger the web app's drop handler
+ // This is tricky and app-specific
+ // For Angular CDK, we might need to trigger cdkDropListDropped
+
+ console.warn('[WebDrop Bridge DROP] Web app popup might not appear - investigating...');
+
+ return false;
+
+ }, true); // Capture phase
+
+ // Clean up
+ document.addEventListener('dragend', function(e) {
+ console.log('[WebDrop Bridge DROP] dragend, cleaning up');
+ dragState = {
+ url: null,
+ localPath: null,
+ isConvertible: false,
+ altKeyPressed: false,
+ dragElement: null
+ };
+ }, false);
+
+ console.log('[WebDrop Bridge DROP] Hooks installed');
+ }
+
+ // Initialize
+ ensureChannel(function() {
+ installHooks();
+ });
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function() {
+ if (!window.bridge) ensureChannel(installHooks);
+ });
+ } else if (!window.bridge) {
+ ensureChannel(installHooks);
+ }
+})();
diff --git a/src/webdrop_bridge/ui/bridge_script_hybrid.js b/src/webdrop_bridge/ui/bridge_script_hybrid.js
new file mode 100644
index 0000000..9727648
--- /dev/null
+++ b/src/webdrop_bridge/ui/bridge_script_hybrid.js
@@ -0,0 +1,109 @@
+// WebDrop Bridge - Hybrid Strategy v3
+// Allow web-app drag to proceed normally (for popups)
+// BUT notify Qt to start a PARALLEL native file drag
+// Windows supports concurrent drag sources - drop target chooses which to use
+
+(function() {
+ if (window.__webdrop_bridge_hybrid_injected) return;
+ window.__webdrop_bridge_hybrid_injected = true;
+
+ console.log('[WebDrop Bridge HYBRID] Script loaded');
+
+ var dragState = {
+ url: null,
+ inProgress: false,
+ altKeyPressed: false
+ };
+
+ // Patch DataTransfer.setData to capture URLs
+ try {
+ var originalSetData = DataTransfer.prototype.setData;
+
+ DataTransfer.prototype.setData = function(format, data) {
+ if ((format === 'text/plain' || format === 'text/uri-list') && dragState.inProgress) {
+ dragState.url = data;
+ console.log('[WebDrop Bridge HYBRID] Captured URL:', data.substring(0, 80));
+
+ // Check if convertible
+ var isConvertible = /^z:/i.test(data) ||
+ /^https?:\/\/.+\.file\.core\.windows\.net\//i.test(data);
+
+ if (isConvertible && dragState.altKeyPressed) {
+ console.log('[WebDrop Bridge HYBRID] >>> CONVERTIBLE - Triggering Qt native drag');
+
+ // Notify Qt to start PARALLEL native drag
+ ensureChannel(function() {
+ if (window.bridge && typeof window.bridge.start_parallel_drag === 'function') {
+ console.log('[WebDrop Bridge HYBRID] Calling start_parallel_drag');
+ window.bridge.start_parallel_drag(data);
+ } else if (window.bridge && typeof window.bridge.start_file_drag === 'function') {
+ // Fallback to old method
+ console.log('[WebDrop Bridge HYBRID] Using start_file_drag (fallback)');
+ window.bridge.start_file_drag(data);
+ }
+ });
+ }
+ }
+
+ // ALWAYS call original - web app functionality must work
+ return originalSetData.call(this, format, data);
+ };
+
+ console.log('[WebDrop Bridge HYBRID] DataTransfer patched');
+ } catch(e) {
+ console.error('[WebDrop Bridge HYBRID] Patch failed:', e);
+ }
+
+ function ensureChannel(cb) {
+ if (window.bridge) { cb(); return; }
+
+ if (window.QWebChannel && window.qt && window.qt.webChannelTransport) {
+ new QWebChannel(window.qt.webChannelTransport, function(channel) {
+ window.bridge = channel.objects.bridge;
+ console.log('[WebDrop Bridge HYBRID] QWebChannel connected');
+ cb();
+ });
+ } else {
+ console.error('[WebDrop Bridge HYBRID] QWebChannel not available');
+ }
+ }
+
+ function installHook() {
+ console.log('[WebDrop Bridge HYBRID] Installing hooks');
+
+ // Monitor dragstart
+ document.addEventListener('dragstart', function(e) {
+ dragState.inProgress = true;
+ dragState.altKeyPressed = e.altKey;
+ dragState.url = null;
+
+ console.log('[WebDrop Bridge HYBRID] dragstart, altKey:', e.altKey);
+
+ // NO preventDefault() - let web app proceed normally!
+
+ }, true); // Capture phase
+
+ // Clean up on dragend
+ document.addEventListener('dragend', function(e) {
+ console.log('[WebDrop Bridge HYBRID] dragend');
+ dragState.inProgress = false;
+ dragState.url = null;
+ dragState.altKeyPressed = false;
+ }, false);
+
+ console.log('[WebDrop Bridge HYBRID] Installed');
+ }
+
+ // Initialize
+ ensureChannel(function() {
+ installHook();
+ });
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function() {
+ if (!window.bridge) ensureChannel(installHook);
+ });
+ } else if (!window.bridge) {
+ ensureChannel(installHook);
+ }
+})();
diff --git a/src/webdrop_bridge/ui/bridge_script_intercept.js b/src/webdrop_bridge/ui/bridge_script_intercept.js
new file mode 100644
index 0000000..d249a83
--- /dev/null
+++ b/src/webdrop_bridge/ui/bridge_script_intercept.js
@@ -0,0 +1,165 @@
+// WebDrop Bridge - Intercept Version
+// Prevents browser drag for ALT+drag, hands off to Qt for file drag
+
+(function() {
+ if (window.__webdrop_intercept_injected) return;
+ window.__webdrop_intercept_injected = true;
+
+ // Intercept mode enabled
+ var INTERCEPT_ENABLED = true;
+
+ 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 angularDragHandlers = [];
+ var originalAddEventListener = EventTarget.prototype.addEventListener;
+ var listenerPatchActive = true;
+
+ // Capture Authorization token from XHR requests
+ window.capturedAuthToken = null;
+ var originalXHRSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
+ XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
+ if (header === 'Authorization' && value.startsWith('Bearer ')) {
+ window.capturedAuthToken = value;
+ console.log('[Intercept] Captured auth token');
+ }
+ return originalXHRSetRequestHeader.apply(this, arguments);
+ };
+
+ // ============================================================================
+ // PART 1: Intercept Angular's dragstart listener registration
+ // ============================================================================
+
+ EventTarget.prototype.addEventListener = function(type, listener, options) {
+ if (listenerPatchActive && type === 'dragstart' && listener) {
+ // Store Angular's dragstart handler instead of registering it
+ console.log('[Intercept] Storing Angular dragstart listener for', this.tagName || this.constructor.name);
+ angularDragHandlers.push({
+ target: this,
+ listener: listener,
+ options: options
+ });
+ return; // Don't actually register it yet
+ }
+ // All other events: use original
+ return originalAddEventListener.call(this, type, listener, options);
+ };
+
+ // ============================================================================
+ // PART 2: Intercept DataTransfer.setData to capture URL
+ // ============================================================================
+
+ var originalSetData = DataTransfer.prototype.setData;
+
+ 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));
+ }
+ return originalSetData.call(this, format, data);
+ };
+
+ console.log('[Intercept] DataTransfer.setData patched ✓');
+
+ // ============================================================================
+ // PART 3: Install OUR dragstart handler in capture phase
+ // ============================================================================
+
+ setTimeout(function() {
+ console.log('[Intercept] Installing dragstart handler, have', angularDragHandlers.length, 'Angular handlers');
+
+ // Stop intercepting addEventListener
+ listenerPatchActive = false;
+
+ // Register OUR handler in capture phase
+ originalAddEventListener.call(document, 'dragstart', function(e) {
+ currentDragUrl = null; // Reset
+
+ console.log('%c[Intercept] dragstart', 'background: #FF9800; color: white; padding: 2px 6px;', 'ALT:', e.altKey);
+
+ // Call Angular's handlers first to let them set the data
+ var handled = 0;
+ for (var i = 0; i < angularDragHandlers.length; i++) {
+ var h = angularDragHandlers[i];
+ if (h.target === document || h.target === e.target ||
+ (h.target.contains && h.target.contains(e.target))) {https://devagravitystg.file.core.windows.net/devagravitysync/anPGZszKzgKaSz1SIx2HFgduy/weiss_ORIGINAL.jpg
+ try {
+ h.listener.call(e.target, e);
+ handled++;
+ } catch(err) {
+ console.error('[Intercept] Error calling Angular handler:', err);
+ }
+ }
+ }
+
+ console.log('[Intercept] Called', handled, 'Angular handlers, URL:', currentDragUrl ? currentDragUrl.substring(0, 60) : 'none');
+
+ // NOW check if we should intercept
+ if (e.altKey && currentDragUrl) {
+ var isAzure = /^https?:\/\/.+\.file\.core\.windows\.net\//i.test(currentDragUrl);
+ var isZDrive = /^z:/i.test(currentDragUrl);
+
+ if (isAzure || isZDrive) {
+ console.log('%c[Intercept] PREVENTING browser drag, using Qt',
+ 'background: #F44336; color: white; font-weight: bold; padding: 4px 8px;');
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ 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);
+ } else {
+ console.error('[Intercept] bridge.start_file_drag not available!');
+ }
+ });
+
+ currentDragUrl = null;
+ return false;
+ }
+ }
+
+ console.log('[Intercept] Normal drag, allowing browser');
+ }, true); // Capture phase
+
+ console.log('[Intercept] dragstart handler installed ✓');
+ }, 1500); // Wait for Angular to register its listeners
+
+ // ============================================================================
+ // PART 3: QWebChannel connection
+ // ============================================================================
+
+ function ensureChannel(callback) {
+ if (window.bridge) {
+ callback();
+ return;
+ }
+
+ if (window.QWebChannel && window.qt && window.qt.webChannelTransport) {
+ new QWebChannel(window.qt.webChannelTransport, function(channel) {
+ window.bridge = channel.objects.bridge;
+ console.log('[WebDrop Intercept] QWebChannel connected ✓');
+ callback();
+ });
+ } else {
+ console.error('[WebDrop Intercept] QWebChannel not available!');
+ }
+ }
+
+ // Initialize channel on load
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function() {
+ ensureChannel(function() {
+ console.log('[WebDrop Intercept] Bridge ready ✓');
+ });
+ });
+ } else {
+ ensureChannel(function() {
+ console.log('[WebDrop Intercept] Bridge ready ✓');
+ });
+ }
+
+ console.log('%c[WebDrop Intercept] Ready! ALT-drag will use Qt file drag.',
+ 'background: #4CAF50; color: white; font-weight: bold; padding: 4px 8px;');
+})();
diff --git a/src/webdrop_bridge/ui/bridge_script_v2.js b/src/webdrop_bridge/ui/bridge_script_v2.js
new file mode 100644
index 0000000..33ceccd
--- /dev/null
+++ b/src/webdrop_bridge/ui/bridge_script_v2.js
@@ -0,0 +1,214 @@
+// WebDrop Bridge - Enhanced Script v2
+// Strategy: EXTEND DataTransfer instead of REPLACING the drag
+// This allows both file-drop AND web-app functionality (popups)
+
+(function() {
+ if (window.__webdrop_bridge_v2_injected) return;
+ window.__webdrop_bridge_v2_injected = true;
+
+ console.log('[WebDrop Bridge v2] Script loaded - EXTEND strategy');
+
+ var currentDragUrl = null;
+ var currentLocalPath = null;
+ var dragInProgress = false;
+
+ // Patch DataTransfer.setData to capture URLs set by web app
+ var originalSetData = null;
+
+ try {
+ if (DataTransfer.prototype.setData) {
+ originalSetData = DataTransfer.prototype.setData;
+
+ DataTransfer.prototype.setData = function(format, data) {
+ // Capture text/plain or text/uri-list for our conversion
+ if ((format === 'text/plain' || format === 'text/uri-list') && dragInProgress) {
+ currentDragUrl = data;
+ console.log('[WebDrop Bridge v2] Captured drag URL:', data.substring(0, 80));
+
+ // Check if this is convertible (Z:\ or Azure)
+ var isZDrive = /^z:/i.test(data);
+ var isAzureUrl = /^https?:\/\/.+\.file\.core\.windows\.net\//i.test(data);
+
+ if (isZDrive || isAzureUrl) {
+ console.log('[WebDrop Bridge v2] >>> CONVERTIBLE URL DETECTED - Will add file data');
+
+ // Request conversion from Qt backend
+ if (window.bridge && typeof window.bridge.convert_url_to_path === 'function') {
+ console.log('[WebDrop Bridge v2] Requesting path conversion...');
+ window.bridge.convert_url_to_path(data);
+ // Note: Conversion happens async, local path will be set via callback
+ }
+ }
+ }
+
+ // ALWAYS call original - don't break web app
+ return originalSetData.call(this, format, data);
+ };
+
+ console.log('[WebDrop Bridge v2] DataTransfer.setData patched');
+ }
+ } catch(e) {
+ console.error('[WebDrop Bridge v2] Failed to patch DataTransfer:', e);
+ }
+
+ // Enhanced DataTransfer with file support
+ // This is called AFTER web app sets its data
+ function enhanceDataTransfer(e) {
+ if (!currentLocalPath) {
+ console.log('[WebDrop Bridge v2] No local path available, cannot enhance');
+ return;
+ }
+
+ console.log('[WebDrop Bridge v2] Enhancing DataTransfer with file:', currentLocalPath);
+
+ try {
+ var dt = e.dataTransfer;
+
+ // Strategy 1: Add Windows-specific file drop formats
+ // These are recognized by Windows Explorer and other drop targets
+ if (originalSetData) {
+ // Add FileNameW (Unicode file path for Windows)
+ var fileNameW = currentLocalPath;
+
+ // Try to add custom Windows file drop formats
+ try {
+ originalSetData.call(dt, 'application/x-qt-windows-mime;value="FileNameW"', fileNameW);
+ console.log('[WebDrop Bridge v2] Added FileNameW format');
+ } catch (e1) {
+ console.warn('[WebDrop Bridge v2] FileNameW format failed:', e1);
+ }
+
+ // Add FileName (ANSI)
+ try {
+ originalSetData.call(dt, 'application/x-qt-windows-mime;value="FileName"', fileNameW);
+ console.log('[WebDrop Bridge v2] Added FileName format');
+ } catch (e2) {
+ console.warn('[WebDrop Bridge v2] FileName format failed:', e2);
+ }
+
+ // Add FileDrop format
+ try {
+ originalSetData.call(dt, 'application/x-qt-windows-mime;value="FileDrop"', fileNameW);
+ console.log('[WebDrop Bridge v2] Added FileDrop format');
+ } catch (e3) {
+ console.warn('[WebDrop Bridge v2] FileDrop format failed:', e3);
+ }
+ }
+
+ // Set effect to allow copy/link
+ if (dt.effectAllowed === 'uninitialized' || dt.effectAllowed === 'none') {
+ dt.effectAllowed = 'copyLink';
+ console.log('[WebDrop Bridge v2] Set effectAllowed to copyLink');
+ }
+
+ } catch (error) {
+ console.error('[WebDrop Bridge v2] Error enhancing DataTransfer:', error);
+ }
+ }
+
+ function ensureChannel(cb) {
+ if (window.bridge) { cb(); return; }
+
+ function init() {
+ if (window.QWebChannel && window.qt && window.qt.webChannelTransport) {
+ new QWebChannel(window.qt.webChannelTransport, function(channel) {
+ window.bridge = channel.objects.bridge;
+ console.log('[WebDrop Bridge v2] QWebChannel connected');
+
+ // Expose callback for Qt to set the converted path
+ window.setLocalPath = function(path) {
+ currentLocalPath = path;
+ console.log('[WebDrop Bridge v2] Local path set from Qt:', path);
+ };
+
+ cb();
+ });
+ } else {
+ console.error('[WebDrop Bridge v2] QWebChannel not available!');
+ }
+ }
+
+ if (window.QWebChannel) {
+ init();
+ } else {
+ console.error('[WebDrop Bridge v2] QWebChannel not found!');
+ }
+ }
+
+ function installHook() {
+ console.log('[WebDrop Bridge v2] Installing drag interceptor (EXTEND mode)');
+
+ // Use CAPTURE PHASE to intercept early
+ document.addEventListener('dragstart', function(e) {
+ try {
+ dragInProgress = true;
+ currentDragUrl = null;
+ currentLocalPath = null;
+
+ console.log('[WebDrop Bridge v2] dragstart on:', e.target.tagName, 'altKey:', e.altKey);
+
+ // Only process ALT-drags (web app's URL drag mode)
+ if (!e.altKey) {
+ console.log('[WebDrop Bridge v2] No ALT key, ignoring');
+ dragInProgress = false;
+ return;
+ }
+
+ console.log('[WebDrop Bridge v2] ALT-drag detected, will monitor for convertible URL');
+
+ // NOTE: We DON'T call preventDefault() here!
+ // This allows web app's drag to proceed normally
+
+ // Web app will call setData() which we've patched
+ // After a short delay, we check if we got a convertible URL
+ setTimeout(function() {
+ if (currentDragUrl) {
+ console.log('[WebDrop Bridge v2] Drag URL set:', currentDragUrl.substring(0, 60));
+ // enhanceDataTransfer will be called when we have the path
+ // For now, we just wait - the drag is already in progress
+ }
+ }, 10);
+
+ } catch (error) {
+ console.error('[WebDrop Bridge v2] Error in dragstart:', error);
+ dragInProgress = false;
+ }
+ }, true); // CAPTURE phase
+
+ // Clean up on dragend
+ document.addEventListener('dragend', function(e) {
+ console.log('[WebDrop Bridge v2] dragend, cleaning up state');
+ dragInProgress = false;
+ currentDragUrl = null;
+ currentLocalPath = null;
+ }, false);
+
+ // Alternative strategy: Use 'drag' event to continuously update DataTransfer
+ // This fires many times during the drag
+ document.addEventListener('drag', function(e) {
+ if (!dragInProgress || !currentLocalPath) return;
+
+ // Try to enhance on every drag event
+ enhanceDataTransfer(e);
+ }, false);
+
+ console.log('[WebDrop Bridge v2] Hooks installed');
+ }
+
+ // Initialize
+ ensureChannel(function() {
+ console.log('[WebDrop Bridge v2] Channel ready, installing hooks');
+ installHook();
+ });
+
+ // Also install on DOM ready as fallback
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', function() {
+ if (!window.bridge) {
+ ensureChannel(installHook);
+ }
+ });
+ } else if (!window.bridge) {
+ ensureChannel(installHook);
+ }
+})();
diff --git a/src/webdrop_bridge/ui/main_window.py b/src/webdrop_bridge/ui/main_window.py
index 252e27b..34bb438 100644
--- a/src/webdrop_bridge/ui/main_window.py
+++ b/src/webdrop_bridge/ui/main_window.py
@@ -1,7 +1,9 @@
"""Main application window with web engine integration."""
import asyncio
+import json
import logging
+import re
from datetime import datetime
from pathlib import Path
from typing import Optional
@@ -426,7 +428,8 @@ class MainWindow(QMainWindow):
logger.warning("Failed to load qwebchannel.js from resources")
# Load bridge script from file
- script_path = Path(__file__).parent / "bridge_script.js"
+ # Using intercept script - prevents browser drag, hands off to Qt
+ script_path = Path(__file__).parent / "bridge_script_intercept.js"
try:
with open(script_path, 'r', encoding='utf-8') as f:
bridge_code = f.read()
@@ -492,7 +495,119 @@ class MainWindow(QMainWindow):
local_path: Local file path that is being dragged
"""
logger.info(f"Drag started: {source} -> {local_path}")
- # Can be extended with status bar updates or user feedback
+
+ # Ask user if they want to check out the asset
+ if source.startswith('http'):
+ self._prompt_checkout(source, local_path)
+
+ def _prompt_checkout(self, azure_url: str, local_path: str) -> None:
+ """Prompt user to check out the asset.
+
+ Args:
+ azure_url: Azure Blob Storage URL
+ local_path: Local file path
+ """
+ from PySide6.QtWidgets import QMessageBox
+
+ # Extract filename for display
+ filename = Path(local_path).name
+
+ # Show confirmation dialog
+ reply = QMessageBox.question(
+ self,
+ "Checkout Asset",
+ f"Do you want to check out this asset?\n\n{filename}",
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
+ QMessageBox.StandardButton.Yes
+ )
+
+ if reply == QMessageBox.StandardButton.Yes:
+ logger.info(f"User confirmed checkout for {filename}")
+ self._trigger_checkout_api(azure_url)
+ else:
+ logger.info(f"User declined checkout for {filename}")
+
+ def _trigger_checkout_api(self, azure_url: str) -> None:
+ """Trigger checkout via API call using JavaScript.
+
+ Calls the checkout API from JavaScript so HttpOnly cookies are automatically included.
+ Example URL: https://devagravitystg.file.core.windows.net/devagravitysync/anPGZszKzgKaSz1SIx2HFgduy/filename
+ Asset ID: anPGZszKzgKaSz1SIx2HFgduy
+
+ Args:
+ azure_url: Azure Blob Storage URL containing asset ID
+ """
+ try:
+ # Extract asset ID from URL (middle segment between domain and filename)
+ # Format: https://domain/container/ASSET_ID/filename
+ match = re.search(r'/([^/]+)/[^/]+$', azure_url)
+ if not match:
+ logger.warning(f"Could not extract asset ID from URL: {azure_url}")
+ return
+
+ asset_id = match.group(1)
+ logger.info(f"Extracted asset ID: {asset_id}")
+
+ # Call API from JavaScript with Authorization header
+ js_code = f"""
+ (async function() {{
+ try {{
+ // Get captured auth token (from intercepted XHR)
+ const authToken = window.capturedAuthToken;
+
+ if (!authToken) {{
+ console.error('No authorization token available');
+ return {{ success: false, error: 'No auth token' }};
+ }}
+
+ const headers = {{
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Accept-Language': 'de',
+ 'Authorization': authToken
+ }};
+
+ const response = await fetch(
+ 'https://devagravityprivate.azurewebsites.net/api/assets/checkout/bulk?checkout=true',
+ {{
+ method: 'PUT',
+ headers: headers,
+ body: JSON.stringify({{asset_ids: ['{asset_id}']}})
+ }}
+ );
+
+ if (response.ok) {{
+ console.log('✅ Checkout API successful for asset {asset_id}');
+ return {{ success: true, status: response.status }};
+ }} else {{
+ const text = await response.text();
+ console.warn('Checkout API returned status ' + response.status + ': ' + text.substring(0, 200));
+ return {{ success: false, status: response.status, error: text }};
+ }}
+ }} catch (error) {{
+ console.error('Checkout API call failed:', error);
+ return {{ success: false, error: error.toString() }};
+ }}
+ }})();
+ """
+
+ def on_result(result):
+ """Callback when JavaScript completes."""
+ if result and isinstance(result, dict):
+ if result.get('success'):
+ logger.info(f"✅ Checkout successful for asset {asset_id}")
+ else:
+ status = result.get('status', 'unknown')
+ error = result.get('error', 'unknown error')
+ logger.warning(f"Checkout API returned status {status}: {error}")
+ else:
+ logger.debug(f"Checkout API call completed (result: {result})")
+
+ # Execute JavaScript (async, non-blocking)
+ self.web_view.page().runJavaScript(js_code, on_result)
+
+ except Exception as e:
+ logger.exception(f"Error triggering checkout API: {e}")
def _on_drag_failed(self, source: str, error: str) -> None:
"""Handle drag operation failure.
@@ -605,33 +720,13 @@ class MainWindow(QMainWindow):
def dragEnterEvent(self, event):
"""Handle drag entering the main window (from WebView or external).
- When a drag from the WebView enters the MainWindow area, we can read
- the drag data and potentially convert Azure URLs to file drags.
+ Note: With intercept script, ALT-drags are prevented in JavaScript
+ and handled via bridge.start_file_drag(). This just handles any
+ remaining drag events.
Args:
event: QDragEnterEvent
"""
- from PySide6.QtCore import QMimeData
-
- mime_data = event.mimeData()
-
- # Check if we have text data (URL from web app)
- if mime_data.hasText():
- url_text = mime_data.text()
- logger.debug(f"Drag entered main window with text: {url_text[:100]}")
-
- # Store for potential conversion
- self._current_drag_url = url_text
-
- # Check if it's convertible
- is_azure = url_text.startswith('https://') and 'file.core.windows.net' in url_text
- is_z_drive = url_text.lower().startswith('z:')
-
- if is_azure or is_z_drive:
- logger.info(f"Convertible URL detected in drag: {url_text[:60]}")
- event.acceptProposedAction()
- return
-
event.ignore()
def dragMoveEvent(self, event):
@@ -640,10 +735,7 @@ class MainWindow(QMainWindow):
Args:
event: QDragMoveEvent
"""
- if self._current_drag_url:
- event.acceptProposedAction()
- else:
- event.ignore()
+ event.ignore()
def dragLeaveEvent(self, event):
"""Handle drag leaving the main window.
@@ -651,33 +743,15 @@ class MainWindow(QMainWindow):
Args:
event: QDragLeaveEvent
"""
- logger.debug("Drag left main window")
- # Reset tracking
- self._current_drag_url = None
+ event.ignore()
def dropEvent(self, event):
"""Handle drop on the main window.
- This captures drops on the MainWindow area (outside WebView).
- If the user drops an Azure URL here, we convert it to a file operation.
-
Args:
event: QDropEvent
"""
- if self._current_drag_url:
- logger.info(f"Drop on main window with URL: {self._current_drag_url[:60]}")
-
- # Handle via drag interceptor (converts Azure URL to local path)
- success = self.drag_interceptor.handle_drag(self._current_drag_url)
-
- if success:
- event.acceptProposedAction()
- else:
- event.ignore()
-
- self._current_drag_url = None
- else:
- event.ignore()
+ event.ignore()
def _on_js_console_message(self, level, message, line_number, source_id):
"""Redirect JavaScript console messages to Python logger.
@@ -863,10 +937,33 @@ class MainWindow(QMainWindow):
def closeEvent(self, event) -> None:
"""Handle window close event.
+ Properly cleanup WebEnginePage before closing to avoid
+ "Release of profile requested but WebEnginePage still not deleted" warning.
+ This ensures session data (cookies, login state) is properly saved.
+
Args:
event: Close event
"""
- # Can be extended with save operations or cleanup
+ logger.debug("Closing application - cleaning up web engine resources")
+
+ # Properly delete WebEnginePage before the profile is released
+ # This ensures cookies and session data are saved correctly
+ if hasattr(self, 'web_view') and self.web_view:
+ page = self.web_view.page()
+ if page:
+ # Disconnect signals to prevent callbacks during shutdown
+ try:
+ page.loadFinished.disconnect()
+ except RuntimeError:
+ pass # Already disconnected or never connected
+
+ # Delete the page explicitly
+ page.deleteLater()
+ logger.debug("WebEnginePage scheduled for deletion")
+
+ # Clear the page from the view
+ self.web_view.setPage(None)
+
event.accept()
def check_for_updates_startup(self) -> None:
diff --git a/src/webdrop_bridge/ui/restricted_web_view.py b/src/webdrop_bridge/ui/restricted_web_view.py
index 489b473..7f20ad2 100644
--- a/src/webdrop_bridge/ui/restricted_web_view.py
+++ b/src/webdrop_bridge/ui/restricted_web_view.py
@@ -155,7 +155,8 @@ class RestrictedWebEngineView(QWebEngineView):
# Create persistent profile with custom storage location
# Using "WebDropBridge" as the profile name
- profile = QWebEngineProfile("WebDropBridge", self)
+ # Note: No parent specified so we control the lifecycle
+ profile = QWebEngineProfile("WebDropBridge")
profile.setPersistentStoragePath(str(profile_path))
# Configure persistent cookies (critical for authentication)