Add drag & drop script variants and enhanced debugging tools
- Introduced multiple JavaScript scripts for handling drag & drop functionality: - `bridge_script.js`: Original implementation with popup prevention. - `bridge_script_debug.js`: Debug version with extensive logging for troubleshooting. - `bridge_script_v2.js`: Enhanced version extending DataTransfer for better integration. - `bridge_script_hybrid.js`: Hybrid approach allowing parallel native file drag. - `bridge_script_drop_intercept.js`: Intercepts drop events for custom handling. - `bridge_script_intercept.js`: Prevents browser drag for ALT+drag, using Qt for file drag. - Added detailed documentation in `SCRIPT_VARIANTS.md` outlining usage, status, and recommended workflows for each script. - Implemented logging features to capture drag events, DataTransfer modifications, and network requests for better debugging. - Enhanced DataTransfer handling to support Windows-specific file formats and improve user experience during drag & drop operations.
This commit is contained in:
parent
88dc358894
commit
dee02ad600
12 changed files with 2244 additions and 65 deletions
222
QUICKSTART_DEBUGGING.md
Normal file
222
QUICKSTART_DEBUGGING.md
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
# 🚀 QUICK START - Popup-Problem lösen
|
||||||
|
|
||||||
|
## Problem Zusammenfassung
|
||||||
|
|
||||||
|
- ✅ File-Drop funktioniert (Z:\ Laufwerk)
|
||||||
|
- ❌ Web-App Popup erscheint nicht nach Drop (Auschecken-Dialog)
|
||||||
|
|
||||||
|
**Grund**: Unser JavaScript verhindert das Browser-Drag-Event mit `preventDefault()`, daher bekommt die Web-App kein Drop-Event.
|
||||||
|
|
||||||
|
## 🎯 Lösungsstrategie
|
||||||
|
|
||||||
|
**Phase 1: DEBUGGING** (ca. 15-30 Min)
|
||||||
|
→ Herausfinden WIE das Popup ausgelöst wird
|
||||||
|
|
||||||
|
**Phase 2: IMPLEMENTATION** (ca. 30-60 Min)
|
||||||
|
→ Popup-Trigger nach File-Drop manuell aufrufen
|
||||||
|
|
||||||
|
## 📋 Phase 1: Debugging - JETZT STARTEN
|
||||||
|
|
||||||
|
### Schritt 1: Debug-Script aktivieren
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Datei öffnen
|
||||||
|
code "C:\Development\VS Code Projects\webdrop_bridge\src\webdrop_bridge\ui\main_window.py"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Zeile ~433** in `_install_bridge_script()` ändern:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# VORHER:
|
||||||
|
script_path = Path(__file__).parent / "bridge_script.js"
|
||||||
|
|
||||||
|
# NACHHER (für Debugging):
|
||||||
|
script_path = Path(__file__).parent / "bridge_script_debug.js"
|
||||||
|
```
|
||||||
|
|
||||||
|
Speichern (Ctrl+S).
|
||||||
|
|
||||||
|
### Schritt 2: Anwendung starten
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd "C:\Development\VS Code Projects\webdrop_bridge"
|
||||||
|
python -m webdrop_bridge.main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schritt 3: Browser DevTools öffnen
|
||||||
|
|
||||||
|
1. Wenn WebDrop Bridge lädt
|
||||||
|
2. **F12** drücken → DevTools öffnen
|
||||||
|
3. **Console-Tab** auswählen
|
||||||
|
4. Sie sollten sehen:
|
||||||
|
`[WebDrop DEBUG] Ready! Perform ALT-drag+drop and watch the logs.`
|
||||||
|
|
||||||
|
### Schritt 4: ALT-Drag+Drop durchführen
|
||||||
|
|
||||||
|
1. In der GlobalDAM Anwendung:
|
||||||
|
- **ALT-Taste** gedrückt halten
|
||||||
|
- Asset **draggen**
|
||||||
|
- Irgendwo **droppen** (z.B. auf dem Desktop)
|
||||||
|
|
||||||
|
2. **Popup sollte erscheinen!** (Auschecken-Ja/Nein)
|
||||||
|
|
||||||
|
3. **Sofort** zur Browser-Console wechseln!
|
||||||
|
|
||||||
|
### Schritt 5: Logs analysieren
|
||||||
|
|
||||||
|
Suchen Sie in der Console nach:
|
||||||
|
|
||||||
|
#### ✅ **A) Modal/Popup Detektion**
|
||||||
|
```
|
||||||
|
[MODAL OPENED] <<<< DAS WOLLEN WIR SEHEN!
|
||||||
|
Modal element: <div class="...">
|
||||||
|
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!
|
||||||
268
docs/ANGULAR_CDK_ANALYSIS.md
Normal file
268
docs/ANGULAR_CDK_ANALYSIS.md
Normal file
|
|
@ -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
|
||||||
|
<!-- Drag Group (oberste Ebene) -->
|
||||||
|
<div cdkdroplistgroup="" aydnd="" class="flex h-full flex-col">
|
||||||
|
|
||||||
|
<!-- Drop Zone (Collections) -->
|
||||||
|
<div cdkdroplist="" class="cdk-drop-list" id="collectioncsuaaDVNokl0...">
|
||||||
|
|
||||||
|
<!-- Draggable Element (Asset Card) -->
|
||||||
|
<li cdkdrag="" class="cdk-drag asset-list-item" draggable="false">
|
||||||
|
<img src="./GlobalDAM JRI_files/anPGZszKzgKaSz1SIx2HFgduy"
|
||||||
|
alt="weiss_ORIGINAL">
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
<!-- Asset ID im Element-ID -->
|
||||||
|
<div id="anPGZszKzgKaSz1SIx2HFgduy">
|
||||||
|
|
||||||
|
<!-- Asset ID in der Bild-URL -->
|
||||||
|
<img src="./GlobalDAM JRI_files/anPGZszKzgKaSz1SIx2HFgduy">
|
||||||
|
|
||||||
|
<!-- Asset Name im alt-Attribut -->
|
||||||
|
<img alt="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
|
||||||
277
docs/DRAG_DROP_PROBLEM_ANALYSIS.md
Normal file
277
docs/DRAG_DROP_PROBLEM_ANALYSIS.md
Normal file
|
|
@ -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)
|
||||||
171
docs/SCRIPT_VARIANTS.md
Normal file
171
docs/SCRIPT_VARIANTS.md
Normal file
|
|
@ -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
|
||||||
|
|
@ -141,26 +141,71 @@
|
||||||
console.log('[WebDrop Bridge] isZDrive:', isZDrive, 'isAzureUrl:', isAzureUrl);
|
console.log('[WebDrop Bridge] isZDrive:', isZDrive, 'isAzureUrl:', isAzureUrl);
|
||||||
|
|
||||||
if (isZDrive || 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') {
|
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
|
// DON'T prevent immediately - let Angular process dragstart for ~200ms
|
||||||
e.preventDefault();
|
// This allows web app to register the drag and prepare popup
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
// Start native file drag via Qt
|
// Store URL and element for later
|
||||||
ensureChannel(function() {
|
window.__lastDraggedUrl = path;
|
||||||
if (window.bridge && typeof window.bridge.start_file_drag === 'function') {
|
var originalTarget = e.target;
|
||||||
console.log('[WebDrop Bridge] Calling start_file_drag:', path.substring(0, 60));
|
|
||||||
window.bridge.start_file_drag(path);
|
// After 200ms: Cancel browser drag and start Qt drag
|
||||||
currentDragData = null;
|
setTimeout(function() {
|
||||||
} else {
|
console.log('[WebDrop Bridge] Starting Qt drag now, browser drag will be cancelled');
|
||||||
console.error('[WebDrop Bridge] bridge.start_file_drag not available!');
|
|
||||||
|
// 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;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -203,6 +248,72 @@
|
||||||
}, 2000); // Wait 2 seconds after DOM ready
|
}, 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
|
// Install after DOM is ready
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
console.log('[WebDrop Bridge] Waiting for DOMContentLoaded...');
|
console.log('[WebDrop Bridge] Waiting for DOMContentLoaded...');
|
||||||
|
|
|
||||||
333
src/webdrop_bridge/ui/bridge_script_debug.js
Normal file
333
src/webdrop_bridge/ui/bridge_script_debug.js
Normal file
|
|
@ -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;');
|
||||||
|
})();
|
||||||
211
src/webdrop_bridge/ui/bridge_script_drop_intercept.js
Normal file
211
src/webdrop_bridge/ui/bridge_script_drop_intercept.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
})();
|
||||||
109
src/webdrop_bridge/ui/bridge_script_hybrid.js
Normal file
109
src/webdrop_bridge/ui/bridge_script_hybrid.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
})();
|
||||||
165
src/webdrop_bridge/ui/bridge_script_intercept.js
Normal file
165
src/webdrop_bridge/ui/bridge_script_intercept.js
Normal file
|
|
@ -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;');
|
||||||
|
})();
|
||||||
214
src/webdrop_bridge/ui/bridge_script_v2.js
Normal file
214
src/webdrop_bridge/ui/bridge_script_v2.js
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
"""Main application window with web engine integration."""
|
"""Main application window with web engine integration."""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
@ -426,7 +428,8 @@ class MainWindow(QMainWindow):
|
||||||
logger.warning("Failed to load qwebchannel.js from resources")
|
logger.warning("Failed to load qwebchannel.js from resources")
|
||||||
|
|
||||||
# Load bridge script from file
|
# 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:
|
try:
|
||||||
with open(script_path, 'r', encoding='utf-8') as f:
|
with open(script_path, 'r', encoding='utf-8') as f:
|
||||||
bridge_code = f.read()
|
bridge_code = f.read()
|
||||||
|
|
@ -492,7 +495,119 @@ class MainWindow(QMainWindow):
|
||||||
local_path: Local file path that is being dragged
|
local_path: Local file path that is being dragged
|
||||||
"""
|
"""
|
||||||
logger.info(f"Drag started: {source} -> {local_path}")
|
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:
|
def _on_drag_failed(self, source: str, error: str) -> None:
|
||||||
"""Handle drag operation failure.
|
"""Handle drag operation failure.
|
||||||
|
|
@ -605,33 +720,13 @@ class MainWindow(QMainWindow):
|
||||||
def dragEnterEvent(self, event):
|
def dragEnterEvent(self, event):
|
||||||
"""Handle drag entering the main window (from WebView or external).
|
"""Handle drag entering the main window (from WebView or external).
|
||||||
|
|
||||||
When a drag from the WebView enters the MainWindow area, we can read
|
Note: With intercept script, ALT-drags are prevented in JavaScript
|
||||||
the drag data and potentially convert Azure URLs to file drags.
|
and handled via bridge.start_file_drag(). This just handles any
|
||||||
|
remaining drag events.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event: QDragEnterEvent
|
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()
|
event.ignore()
|
||||||
|
|
||||||
def dragMoveEvent(self, event):
|
def dragMoveEvent(self, event):
|
||||||
|
|
@ -640,10 +735,7 @@ class MainWindow(QMainWindow):
|
||||||
Args:
|
Args:
|
||||||
event: QDragMoveEvent
|
event: QDragMoveEvent
|
||||||
"""
|
"""
|
||||||
if self._current_drag_url:
|
event.ignore()
|
||||||
event.acceptProposedAction()
|
|
||||||
else:
|
|
||||||
event.ignore()
|
|
||||||
|
|
||||||
def dragLeaveEvent(self, event):
|
def dragLeaveEvent(self, event):
|
||||||
"""Handle drag leaving the main window.
|
"""Handle drag leaving the main window.
|
||||||
|
|
@ -651,33 +743,15 @@ class MainWindow(QMainWindow):
|
||||||
Args:
|
Args:
|
||||||
event: QDragLeaveEvent
|
event: QDragLeaveEvent
|
||||||
"""
|
"""
|
||||||
logger.debug("Drag left main window")
|
event.ignore()
|
||||||
# Reset tracking
|
|
||||||
self._current_drag_url = None
|
|
||||||
|
|
||||||
def dropEvent(self, event):
|
def dropEvent(self, event):
|
||||||
"""Handle drop on the main window.
|
"""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:
|
Args:
|
||||||
event: QDropEvent
|
event: QDropEvent
|
||||||
"""
|
"""
|
||||||
if self._current_drag_url:
|
event.ignore()
|
||||||
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()
|
|
||||||
|
|
||||||
def _on_js_console_message(self, level, message, line_number, source_id):
|
def _on_js_console_message(self, level, message, line_number, source_id):
|
||||||
"""Redirect JavaScript console messages to Python logger.
|
"""Redirect JavaScript console messages to Python logger.
|
||||||
|
|
@ -863,10 +937,33 @@ class MainWindow(QMainWindow):
|
||||||
def closeEvent(self, event) -> None:
|
def closeEvent(self, event) -> None:
|
||||||
"""Handle window close event.
|
"""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:
|
Args:
|
||||||
event: Close event
|
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()
|
event.accept()
|
||||||
|
|
||||||
def check_for_updates_startup(self) -> None:
|
def check_for_updates_startup(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,8 @@ class RestrictedWebEngineView(QWebEngineView):
|
||||||
|
|
||||||
# Create persistent profile with custom storage location
|
# Create persistent profile with custom storage location
|
||||||
# Using "WebDropBridge" as the profile name
|
# 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))
|
profile.setPersistentStoragePath(str(profile_path))
|
||||||
|
|
||||||
# Configure persistent cookies (critical for authentication)
|
# Configure persistent cookies (critical for authentication)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue