bump
This commit is contained in:
462
daemon/daemon.go
462
daemon/daemon.go
@@ -6480,6 +6480,16 @@ func (d *Daemon) dragAndDropByOffset(tabID, sourceSelector string, offsetX, offs
|
|||||||
|
|
||||||
// performDragAndDrop performs the actual drag and drop operation between two elements
|
// performDragAndDrop performs the actual drag and drop operation between two elements
|
||||||
func (d *Daemon) performDragAndDrop(page *rod.Page, sourceSelector, targetSelector string) error {
|
func (d *Daemon) performDragAndDrop(page *rod.Page, sourceSelector, targetSelector string) error {
|
||||||
|
// First, try the enhanced HTML5 drag and drop approach
|
||||||
|
err := d.performHTML5DragAndDrop(page, sourceSelector, targetSelector)
|
||||||
|
if err == nil {
|
||||||
|
d.debugLog("HTML5 drag and drop completed successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.debugLog("HTML5 drag and drop failed (%v), falling back to mouse events", err)
|
||||||
|
|
||||||
|
// Fallback to the original mouse-based approach
|
||||||
// Find source element
|
// Find source element
|
||||||
sourceElement, err := page.Element(sourceSelector)
|
sourceElement, err := page.Element(sourceSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -6524,8 +6534,390 @@ func (d *Daemon) performDragAndDrop(page *rod.Page, sourceSelector, targetSelect
|
|||||||
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, targetX, targetY)
|
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, targetX, targetY)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// injectDragDropHelpers injects the JavaScript drag and drop helper functions into the page
|
||||||
|
func (d *Daemon) injectDragDropHelpers(page *rod.Page) error {
|
||||||
|
// Read the JavaScript helper file
|
||||||
|
jsHelpers := `
|
||||||
|
// HTML5 Drag and Drop Helper Functions for Cremote
|
||||||
|
// These functions are injected into web pages to provide reliable drag and drop functionality
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Create a namespace to avoid conflicts
|
||||||
|
window.cremoteDragDrop = window.cremoteDragDrop || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates HTML5 drag and drop between two elements
|
||||||
|
* @param {string} sourceSelector - CSS selector for source element
|
||||||
|
* @param {string} targetSelector - CSS selector for target element
|
||||||
|
* @returns {Promise<boolean>} - Success status
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.dragElementToElement = async function(sourceSelector, targetSelector) {
|
||||||
|
const sourceElement = document.querySelector(sourceSelector);
|
||||||
|
const targetElement = document.querySelector(targetSelector);
|
||||||
|
|
||||||
|
if (!sourceElement) {
|
||||||
|
throw new Error('Source element not found: ' + sourceSelector);
|
||||||
|
}
|
||||||
|
if (!targetElement) {
|
||||||
|
throw new Error('Target element not found: ' + targetSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make source draggable if not already
|
||||||
|
if (!sourceElement.draggable) {
|
||||||
|
sourceElement.draggable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and dispatch dragstart event
|
||||||
|
const dragStartEvent = new DragEvent('dragstart', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: new DataTransfer()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set drag data
|
||||||
|
dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector);
|
||||||
|
dragStartEvent.dataTransfer.effectAllowed = 'all';
|
||||||
|
|
||||||
|
const dragStartResult = sourceElement.dispatchEvent(dragStartEvent);
|
||||||
|
if (!dragStartResult) {
|
||||||
|
console.log('Dragstart was cancelled');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to simulate realistic drag timing
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
// Create and dispatch dragover event on target
|
||||||
|
const dragOverEvent = new DragEvent('dragover', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
const dragOverResult = targetElement.dispatchEvent(dragOverEvent);
|
||||||
|
|
||||||
|
// Create and dispatch drop event on target
|
||||||
|
const dropEvent = new DragEvent('drop', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropResult = targetElement.dispatchEvent(dropEvent);
|
||||||
|
|
||||||
|
// Create and dispatch dragend event on source
|
||||||
|
const dragEndEvent = new DragEvent('dragend', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
sourceElement.dispatchEvent(dragEndEvent);
|
||||||
|
|
||||||
|
return dropResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Cremote drag and drop helpers loaded successfully');
|
||||||
|
})();
|
||||||
|
`
|
||||||
|
|
||||||
|
// Inject the JavaScript helpers
|
||||||
|
_, err := page.Eval(jsHelpers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to inject drag and drop helpers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// performHTML5DragAndDrop performs drag and drop using HTML5 drag events
|
||||||
|
func (d *Daemon) performHTML5DragAndDrop(page *rod.Page, sourceSelector, targetSelector string) error {
|
||||||
|
// Inject the helper functions
|
||||||
|
err := d.injectDragDropHelpers(page)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to inject helpers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the HTML5 drag and drop
|
||||||
|
jsCode := fmt.Sprintf(`
|
||||||
|
(async function() {
|
||||||
|
try {
|
||||||
|
const result = await window.cremoteDragDrop.dragElementToElement('%s', '%s');
|
||||||
|
return { success: result, error: null };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
`, sourceSelector, targetSelector)
|
||||||
|
|
||||||
|
result, err := page.Eval(jsCode)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute HTML5 drag and drop: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the result
|
||||||
|
resultMap := result.Value.Map()
|
||||||
|
if resultMap == nil {
|
||||||
|
return fmt.Errorf("invalid result from HTML5 drag and drop")
|
||||||
|
}
|
||||||
|
|
||||||
|
success, exists := resultMap["success"]
|
||||||
|
if !exists || !success.Bool() {
|
||||||
|
errorMsg := "unknown error"
|
||||||
|
if errorVal, exists := resultMap["error"]; exists && errorVal.Str() != "" {
|
||||||
|
errorMsg = errorVal.Str()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("HTML5 drag and drop failed: %s", errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// injectEnhancedDragDropHelpers injects the complete JavaScript drag and drop helper functions
|
||||||
|
func (d *Daemon) injectEnhancedDragDropHelpers(page *rod.Page) error {
|
||||||
|
// Read the complete JavaScript helper file content
|
||||||
|
jsHelpers := `
|
||||||
|
// Enhanced HTML5 Drag and Drop Helper Functions for Cremote
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Create a namespace to avoid conflicts
|
||||||
|
window.cremoteDragDrop = window.cremoteDragDrop || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates HTML5 drag and drop between two elements
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.dragElementToElement = async function(sourceSelector, targetSelector) {
|
||||||
|
const sourceElement = document.querySelector(sourceSelector);
|
||||||
|
const targetElement = document.querySelector(targetSelector);
|
||||||
|
|
||||||
|
if (!sourceElement) {
|
||||||
|
throw new Error('Source element not found: ' + sourceSelector);
|
||||||
|
}
|
||||||
|
if (!targetElement) {
|
||||||
|
throw new Error('Target element not found: ' + targetSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make source draggable if not already
|
||||||
|
if (!sourceElement.draggable) {
|
||||||
|
sourceElement.draggable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and dispatch dragstart event
|
||||||
|
const dragStartEvent = new DragEvent('dragstart', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: new DataTransfer()
|
||||||
|
});
|
||||||
|
|
||||||
|
dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector);
|
||||||
|
dragStartEvent.dataTransfer.effectAllowed = 'all';
|
||||||
|
|
||||||
|
const dragStartResult = sourceElement.dispatchEvent(dragStartEvent);
|
||||||
|
if (!dragStartResult) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
// Create and dispatch dragover event on target
|
||||||
|
const dragOverEvent = new DragEvent('dragover', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
targetElement.dispatchEvent(dragOverEvent);
|
||||||
|
|
||||||
|
// Create and dispatch drop event on target
|
||||||
|
const dropEvent = new DragEvent('drop', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropResult = targetElement.dispatchEvent(dropEvent);
|
||||||
|
|
||||||
|
// Create and dispatch dragend event on source
|
||||||
|
const dragEndEvent = new DragEvent('dragend', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
sourceElement.dispatchEvent(dragEndEvent);
|
||||||
|
|
||||||
|
return dropResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an element can receive drop events
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.hasDropEventListener = function(element) {
|
||||||
|
if (element.ondrop) return true;
|
||||||
|
if (element.getAttribute('ondrop')) return true;
|
||||||
|
if (element.ondragover || element.getAttribute('ondragover')) return true;
|
||||||
|
|
||||||
|
const dropIndicators = ['drop-zone', 'dropzone', 'droppable', 'drop-target'];
|
||||||
|
const className = element.className.toLowerCase();
|
||||||
|
return dropIndicators.some(indicator => className.includes(indicator));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the best drop target at given coordinates
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.findDropTargetAtCoordinates = function(x, y) {
|
||||||
|
const elements = document.elementsFromPoint(x, y);
|
||||||
|
|
||||||
|
for (const element of elements) {
|
||||||
|
if (this.hasDropEventListener(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements[0] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced drag to coordinates that finds the best drop target
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.smartDragToCoordinates = async function(sourceSelector, x, y) {
|
||||||
|
const sourceElement = document.querySelector(sourceSelector);
|
||||||
|
|
||||||
|
if (!sourceElement) {
|
||||||
|
throw new Error('Source element not found: ' + sourceSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetElement = this.findDropTargetAtCoordinates(x, y);
|
||||||
|
if (!targetElement) {
|
||||||
|
throw new Error('No suitable drop target found at coordinates (' + x + ', ' + y + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
const canReceiveDrops = this.hasDropEventListener(targetElement);
|
||||||
|
|
||||||
|
if (canReceiveDrops) {
|
||||||
|
const success = await this.dragElementToElement(sourceSelector,
|
||||||
|
targetElement.id ? '#' + targetElement.id : targetElement.tagName.toLowerCase());
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: success,
|
||||||
|
method: 'element-to-element',
|
||||||
|
targetElement: {
|
||||||
|
tagName: targetElement.tagName,
|
||||||
|
id: targetElement.id,
|
||||||
|
className: targetElement.className,
|
||||||
|
hasDropListener: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Fall back to coordinate-based drag with manual event dispatch
|
||||||
|
const result = await this.dragElementToCoordinates(sourceSelector, x, y);
|
||||||
|
result.method = 'coordinate-based';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates HTML5 drag and drop from element to coordinates
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.dragElementToCoordinates = async function(sourceSelector, x, y) {
|
||||||
|
const sourceElement = document.querySelector(sourceSelector);
|
||||||
|
|
||||||
|
if (!sourceElement) {
|
||||||
|
throw new Error('Source element not found: ' + sourceSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetElement = document.elementFromPoint(x, y);
|
||||||
|
if (!targetElement) {
|
||||||
|
throw new Error('No element found at coordinates (' + x + ', ' + y + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sourceElement.draggable) {
|
||||||
|
sourceElement.draggable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dragStartEvent = new DragEvent('dragstart', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: new DataTransfer()
|
||||||
|
});
|
||||||
|
|
||||||
|
dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector);
|
||||||
|
dragStartEvent.dataTransfer.effectAllowed = 'all';
|
||||||
|
|
||||||
|
const dragStartResult = sourceElement.dispatchEvent(dragStartEvent);
|
||||||
|
if (!dragStartResult) {
|
||||||
|
return { success: false, reason: 'Dragstart was cancelled', targetElement: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
const dragOverEvent = new DragEvent('dragover', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
clientX: x,
|
||||||
|
clientY: y,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
targetElement.dispatchEvent(dragOverEvent);
|
||||||
|
|
||||||
|
const dropEvent = new DragEvent('drop', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
clientX: x,
|
||||||
|
clientY: y,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropResult = targetElement.dispatchEvent(dropEvent);
|
||||||
|
|
||||||
|
const dragEndEvent = new DragEvent('dragend', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
sourceElement.dispatchEvent(dragEndEvent);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: dropResult,
|
||||||
|
targetElement: {
|
||||||
|
tagName: targetElement.tagName,
|
||||||
|
id: targetElement.id,
|
||||||
|
className: targetElement.className,
|
||||||
|
hasDropListener: this.hasDropEventListener(targetElement)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Enhanced Cremote drag and drop helpers loaded successfully');
|
||||||
|
})();
|
||||||
|
`
|
||||||
|
|
||||||
|
// Inject the JavaScript helpers
|
||||||
|
_, err := page.Eval(jsHelpers)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to inject enhanced drag and drop helpers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// performDragAndDropToCoordinates performs drag and drop from element to specific coordinates
|
// performDragAndDropToCoordinates performs drag and drop from element to specific coordinates
|
||||||
func (d *Daemon) performDragAndDropToCoordinates(page *rod.Page, sourceSelector string, targetX, targetY int) error {
|
func (d *Daemon) performDragAndDropToCoordinates(page *rod.Page, sourceSelector string, targetX, targetY int) error {
|
||||||
|
// First, try the enhanced HTML5 approach with smart target detection
|
||||||
|
err := d.performHTML5DragToCoordinates(page, sourceSelector, targetX, targetY)
|
||||||
|
if err == nil {
|
||||||
|
d.debugLog("HTML5 coordinate drag completed successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.debugLog("HTML5 coordinate drag failed (%v), falling back to mouse events", err)
|
||||||
|
|
||||||
|
// Fallback to the original mouse-based approach
|
||||||
// Find source element
|
// Find source element
|
||||||
sourceElement, err := page.Element(sourceSelector)
|
sourceElement, err := page.Element(sourceSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -6549,21 +6941,67 @@ func (d *Daemon) performDragAndDropToCoordinates(page *rod.Page, sourceSelector
|
|||||||
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, float64(targetX), float64(targetY))
|
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, float64(targetX), float64(targetY))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// performHTML5DragToCoordinates performs HTML5 drag to coordinates with smart target detection
|
||||||
|
func (d *Daemon) performHTML5DragToCoordinates(page *rod.Page, sourceSelector string, targetX, targetY int) error {
|
||||||
|
// First, inject the enhanced helper functions that include coordinate support
|
||||||
|
err := d.injectEnhancedDragDropHelpers(page)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to inject enhanced helpers: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the smart coordinate drag
|
||||||
|
jsCode := fmt.Sprintf(`
|
||||||
|
(async function() {
|
||||||
|
try {
|
||||||
|
const result = await window.cremoteDragDrop.smartDragToCoordinates('%s', %d, %d);
|
||||||
|
return { success: result.success, method: result.method, error: null, targetInfo: result.targetElement };
|
||||||
|
} catch (error) {
|
||||||
|
return { success: false, error: error.message, method: 'failed', targetInfo: null };
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
`, sourceSelector, targetX, targetY)
|
||||||
|
|
||||||
|
result, err := page.Eval(jsCode)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to execute HTML5 coordinate drag: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the result
|
||||||
|
resultMap := result.Value.Map()
|
||||||
|
if resultMap == nil {
|
||||||
|
return fmt.Errorf("invalid result from HTML5 coordinate drag")
|
||||||
|
}
|
||||||
|
|
||||||
|
success, exists := resultMap["success"]
|
||||||
|
if !exists || !success.Bool() {
|
||||||
|
errorMsg := "unknown error"
|
||||||
|
if errorVal, exists := resultMap["error"]; exists && errorVal.Str() != "" {
|
||||||
|
errorMsg = errorVal.Str()
|
||||||
|
}
|
||||||
|
return fmt.Errorf("HTML5 coordinate drag failed: %s", errorMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log the method used for debugging
|
||||||
|
if method, exists := resultMap["method"]; exists && method.Str() != "" {
|
||||||
|
d.debugLog("HTML5 coordinate drag used method: %s", method.Str())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// performDragAndDropByOffset performs drag and drop from element by relative offset
|
// performDragAndDropByOffset performs drag and drop from element by relative offset
|
||||||
func (d *Daemon) performDragAndDropByOffset(page *rod.Page, sourceSelector string, offsetX, offsetY int) error {
|
func (d *Daemon) performDragAndDropByOffset(page *rod.Page, sourceSelector string, offsetX, offsetY int) error {
|
||||||
// Find source element
|
// First, calculate the target coordinates
|
||||||
sourceElement, err := page.Element(sourceSelector)
|
sourceElement, err := page.Element(sourceSelector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to find source element %s: %v", sourceSelector, err)
|
return fmt.Errorf("failed to find source element %s: %v", sourceSelector, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get source element position and size
|
|
||||||
sourceBox, err := sourceElement.Shape()
|
sourceBox, err := sourceElement.Shape()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get source element shape: %v", err)
|
return fmt.Errorf("failed to get source element shape: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate source center point from the first quad
|
|
||||||
if len(sourceBox.Quads) == 0 {
|
if len(sourceBox.Quads) == 0 {
|
||||||
return fmt.Errorf("source element has no quads")
|
return fmt.Errorf("source element has no quads")
|
||||||
}
|
}
|
||||||
@@ -6571,11 +7009,21 @@ func (d *Daemon) performDragAndDropByOffset(page *rod.Page, sourceSelector strin
|
|||||||
sourceX := (sourceQuad[0] + sourceQuad[2] + sourceQuad[4] + sourceQuad[6]) / 4
|
sourceX := (sourceQuad[0] + sourceQuad[2] + sourceQuad[4] + sourceQuad[6]) / 4
|
||||||
sourceY := (sourceQuad[1] + sourceQuad[3] + sourceQuad[5] + sourceQuad[7]) / 4
|
sourceY := (sourceQuad[1] + sourceQuad[3] + sourceQuad[5] + sourceQuad[7]) / 4
|
||||||
|
|
||||||
// Calculate target point by adding offset
|
// Calculate target coordinates
|
||||||
targetX := sourceX + float64(offsetX)
|
targetX := int(sourceX + float64(offsetX))
|
||||||
targetY := sourceY + float64(offsetY)
|
targetY := int(sourceY + float64(offsetY))
|
||||||
|
|
||||||
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, targetX, targetY)
|
// Try the enhanced HTML5 approach first (reuse coordinate logic)
|
||||||
|
err = d.performHTML5DragToCoordinates(page, sourceSelector, targetX, targetY)
|
||||||
|
if err == nil {
|
||||||
|
d.debugLog("HTML5 offset drag completed successfully")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
d.debugLog("HTML5 offset drag failed (%v), falling back to mouse events", err)
|
||||||
|
|
||||||
|
// Fallback to the original mouse-based approach
|
||||||
|
return d.performDragAndDropBetweenPoints(page, sourceX, sourceY, float64(targetX), float64(targetY))
|
||||||
}
|
}
|
||||||
|
|
||||||
// performDragAndDropBetweenPoints performs the actual drag and drop using Chrome DevTools Protocol mouse events
|
// performDragAndDropBetweenPoints performs the actual drag and drop using Chrome DevTools Protocol mouse events
|
||||||
|
|||||||
255
daemon/drag_drop_helpers.js
Normal file
255
daemon/drag_drop_helpers.js
Normal file
@@ -0,0 +1,255 @@
|
|||||||
|
// HTML5 Drag and Drop Helper Functions for Cremote
|
||||||
|
// These functions are injected into web pages to provide reliable drag and drop functionality
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Create a namespace to avoid conflicts
|
||||||
|
window.cremoteDragDrop = window.cremoteDragDrop || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates HTML5 drag and drop between two elements
|
||||||
|
* @param {string} sourceSelector - CSS selector for source element
|
||||||
|
* @param {string} targetSelector - CSS selector for target element
|
||||||
|
* @returns {Promise<boolean>} - Success status
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.dragElementToElement = async function(sourceSelector, targetSelector) {
|
||||||
|
const sourceElement = document.querySelector(sourceSelector);
|
||||||
|
const targetElement = document.querySelector(targetSelector);
|
||||||
|
|
||||||
|
if (!sourceElement) {
|
||||||
|
throw new Error(`Source element not found: ${sourceSelector}`);
|
||||||
|
}
|
||||||
|
if (!targetElement) {
|
||||||
|
throw new Error(`Target element not found: ${targetSelector}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make source draggable if not already
|
||||||
|
if (!sourceElement.draggable) {
|
||||||
|
sourceElement.draggable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and dispatch dragstart event
|
||||||
|
const dragStartEvent = new DragEvent('dragstart', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: new DataTransfer()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set drag data
|
||||||
|
dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector);
|
||||||
|
dragStartEvent.dataTransfer.effectAllowed = 'all';
|
||||||
|
|
||||||
|
const dragStartResult = sourceElement.dispatchEvent(dragStartEvent);
|
||||||
|
if (!dragStartResult) {
|
||||||
|
console.log('Dragstart was cancelled');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to simulate realistic drag timing
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
// Create and dispatch dragover event on target
|
||||||
|
const dragOverEvent = new DragEvent('dragover', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
const dragOverResult = targetElement.dispatchEvent(dragOverEvent);
|
||||||
|
|
||||||
|
// Create and dispatch drop event on target
|
||||||
|
const dropEvent = new DragEvent('drop', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropResult = targetElement.dispatchEvent(dropEvent);
|
||||||
|
|
||||||
|
// Create and dispatch dragend event on source
|
||||||
|
const dragEndEvent = new DragEvent('dragend', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
sourceElement.dispatchEvent(dragEndEvent);
|
||||||
|
|
||||||
|
return dropResult;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates HTML5 drag and drop from element to coordinates
|
||||||
|
* @param {string} sourceSelector - CSS selector for source element
|
||||||
|
* @param {number} x - Target X coordinate
|
||||||
|
* @param {number} y - Target Y coordinate
|
||||||
|
* @returns {Promise<object>} - Result with success status and target element info
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.dragElementToCoordinates = async function(sourceSelector, x, y) {
|
||||||
|
const sourceElement = document.querySelector(sourceSelector);
|
||||||
|
|
||||||
|
if (!sourceElement) {
|
||||||
|
throw new Error(`Source element not found: ${sourceSelector}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find element at target coordinates
|
||||||
|
const targetElement = document.elementFromPoint(x, y);
|
||||||
|
if (!targetElement) {
|
||||||
|
throw new Error(`No element found at coordinates (${x}, ${y})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make source draggable if not already
|
||||||
|
if (!sourceElement.draggable) {
|
||||||
|
sourceElement.draggable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and dispatch dragstart event
|
||||||
|
const dragStartEvent = new DragEvent('dragstart', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: new DataTransfer()
|
||||||
|
});
|
||||||
|
|
||||||
|
dragStartEvent.dataTransfer.setData('text/plain', sourceElement.id || sourceSelector);
|
||||||
|
dragStartEvent.dataTransfer.effectAllowed = 'all';
|
||||||
|
|
||||||
|
const dragStartResult = sourceElement.dispatchEvent(dragStartEvent);
|
||||||
|
if (!dragStartResult) {
|
||||||
|
return { success: false, reason: 'Dragstart was cancelled', targetElement: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 50));
|
||||||
|
|
||||||
|
// Create dragover event with coordinates
|
||||||
|
const dragOverEvent = new DragEvent('dragover', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
clientX: x,
|
||||||
|
clientY: y,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
targetElement.dispatchEvent(dragOverEvent);
|
||||||
|
|
||||||
|
// Create drop event with coordinates
|
||||||
|
const dropEvent = new DragEvent('drop', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
clientX: x,
|
||||||
|
clientY: y,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
const dropResult = targetElement.dispatchEvent(dropEvent);
|
||||||
|
|
||||||
|
// Dispatch dragend
|
||||||
|
const dragEndEvent = new DragEvent('dragend', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
dataTransfer: dragStartEvent.dataTransfer
|
||||||
|
});
|
||||||
|
|
||||||
|
sourceElement.dispatchEvent(dragEndEvent);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: dropResult,
|
||||||
|
targetElement: {
|
||||||
|
tagName: targetElement.tagName,
|
||||||
|
id: targetElement.id,
|
||||||
|
className: targetElement.className,
|
||||||
|
hasDropListener: this.hasDropEventListener(targetElement)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an element can receive drop events
|
||||||
|
* @param {Element} element - Element to check
|
||||||
|
* @returns {boolean} - Whether element can receive drops
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.hasDropEventListener = function(element) {
|
||||||
|
// Check for ondrop attribute
|
||||||
|
if (element.ondrop) return true;
|
||||||
|
|
||||||
|
// Check for drop event listeners (this is limited but covers common cases)
|
||||||
|
if (element.getAttribute('ondrop')) return true;
|
||||||
|
|
||||||
|
// Check if element has dragover listeners (usually indicates drop capability)
|
||||||
|
if (element.ondragover || element.getAttribute('ondragover')) return true;
|
||||||
|
|
||||||
|
// Check for common drop zone classes/attributes
|
||||||
|
const dropIndicators = ['drop-zone', 'dropzone', 'droppable', 'drop-target'];
|
||||||
|
const className = element.className.toLowerCase();
|
||||||
|
const hasDropClass = dropIndicators.some(indicator => className.includes(indicator));
|
||||||
|
|
||||||
|
return hasDropClass;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds the best drop target at given coordinates
|
||||||
|
* @param {number} x - X coordinate
|
||||||
|
* @param {number} y - Y coordinate
|
||||||
|
* @returns {Element|null} - Best drop target element or null
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.findDropTargetAtCoordinates = function(x, y) {
|
||||||
|
const elements = document.elementsFromPoint(x, y);
|
||||||
|
|
||||||
|
// Look for elements that can receive drops, starting from topmost
|
||||||
|
for (const element of elements) {
|
||||||
|
if (this.hasDropEventListener(element)) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no explicit drop target found, return the topmost element
|
||||||
|
return elements[0] || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhanced drag to coordinates that finds the best drop target
|
||||||
|
* @param {string} sourceSelector - CSS selector for source element
|
||||||
|
* @param {number} x - Target X coordinate
|
||||||
|
* @param {number} y - Target Y coordinate
|
||||||
|
* @returns {Promise<object>} - Enhanced result with target analysis
|
||||||
|
*/
|
||||||
|
window.cremoteDragDrop.smartDragToCoordinates = async function(sourceSelector, x, y) {
|
||||||
|
const sourceElement = document.querySelector(sourceSelector);
|
||||||
|
|
||||||
|
if (!sourceElement) {
|
||||||
|
throw new Error(`Source element not found: ${sourceSelector}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the best drop target at coordinates
|
||||||
|
const targetElement = this.findDropTargetAtCoordinates(x, y);
|
||||||
|
if (!targetElement) {
|
||||||
|
throw new Error(`No suitable drop target found at coordinates (${x}, ${y})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const canReceiveDrops = this.hasDropEventListener(targetElement);
|
||||||
|
|
||||||
|
// If we found a proper drop target, use element-to-element drag
|
||||||
|
if (canReceiveDrops) {
|
||||||
|
const success = await this.dragElementToElement(sourceSelector,
|
||||||
|
targetElement.id ? `#${targetElement.id}` : targetElement.tagName.toLowerCase());
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: success,
|
||||||
|
method: 'element-to-element',
|
||||||
|
targetElement: {
|
||||||
|
tagName: targetElement.tagName,
|
||||||
|
id: targetElement.id,
|
||||||
|
className: targetElement.className,
|
||||||
|
hasDropListener: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Fall back to coordinate-based drag
|
||||||
|
const result = await this.dragElementToCoordinates(sourceSelector, x, y);
|
||||||
|
result.method = 'coordinate-based';
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Cremote drag and drop helpers loaded successfully');
|
||||||
|
})();
|
||||||
309
drag-drop-test.html
Normal file
309
drag-drop-test.html
Normal file
@@ -0,0 +1,309 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Drag and Drop Test Page</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-section {
|
||||||
|
margin: 30px 0;
|
||||||
|
padding: 20px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: move;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 10px;
|
||||||
|
user-select: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draggable:hover {
|
||||||
|
background: #45a049;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone {
|
||||||
|
width: 200px;
|
||||||
|
height: 150px;
|
||||||
|
border: 3px dashed #ccc;
|
||||||
|
background: #fafafa;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #666;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone.drag-over {
|
||||||
|
border-color: #4CAF50;
|
||||||
|
background: #e8f5e8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-zone.has-item {
|
||||||
|
background: #d4edda;
|
||||||
|
border-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.log {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
padding: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coordinates {
|
||||||
|
position: absolute;
|
||||||
|
background: rgba(0,0,0,0.8);
|
||||||
|
color: white;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#coordinate-target {
|
||||||
|
width: 300px;
|
||||||
|
height: 200px;
|
||||||
|
border: 2px solid #007bff;
|
||||||
|
background: #e7f3ff;
|
||||||
|
position: relative;
|
||||||
|
margin: 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropped-item {
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #ff6b6b;
|
||||||
|
border-radius: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Drag and Drop Test Page</h1>
|
||||||
|
<p>This page tests various drag and drop scenarios for debugging automation tools.</p>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Test 1: Element to Element Drag</h2>
|
||||||
|
<p>Drag the green box to the drop zone</p>
|
||||||
|
<div class="container">
|
||||||
|
<div class="draggable" id="drag-item-1" draggable="true">Drag Me</div>
|
||||||
|
<div class="drop-zone" id="drop-zone-1">Drop Here</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Test 2: Multiple Draggable Items</h2>
|
||||||
|
<p>Drag any item to any drop zone</p>
|
||||||
|
<div class="container">
|
||||||
|
<div class="draggable" id="drag-item-2a" draggable="true">Item A</div>
|
||||||
|
<div class="draggable" id="drag-item-2b" draggable="true">Item B</div>
|
||||||
|
<div class="draggable" id="drag-item-2c" draggable="true">Item C</div>
|
||||||
|
<div class="drop-zone" id="drop-zone-2a">Zone 1</div>
|
||||||
|
<div class="drop-zone" id="drop-zone-2b">Zone 2</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Test 3: Coordinate-based Drop</h2>
|
||||||
|
<p>Drag the item and drop it at specific coordinates in the blue area</p>
|
||||||
|
<div class="container">
|
||||||
|
<div class="draggable" id="drag-item-3" draggable="true">Drag to XY</div>
|
||||||
|
<div id="coordinate-target">
|
||||||
|
<div style="position: absolute; top: 10px; left: 10px; font-size: 12px; color: #666;">
|
||||||
|
Drop anywhere in this blue area
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="test-section">
|
||||||
|
<h2>Test 4: Offset-based Drag</h2>
|
||||||
|
<p>Drag the item by a relative offset</p>
|
||||||
|
<div class="container">
|
||||||
|
<div class="draggable" id="drag-item-4" draggable="true" style="position: relative;">Offset Drag</div>
|
||||||
|
<div style="margin-left: 150px; margin-top: 50px; padding: 20px; border: 2px dashed #666; border-radius: 8px;">
|
||||||
|
Expected drop area (150px right, 50px down)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="clearLog()">Clear Log</button>
|
||||||
|
<button onclick="resetAll()">Reset All</button>
|
||||||
|
|
||||||
|
<div class="log" id="event-log">
|
||||||
|
<div><strong>Event Log:</strong></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let dragCounter = 0;
|
||||||
|
|
||||||
|
function log(message) {
|
||||||
|
const logDiv = document.getElementById('event-log');
|
||||||
|
const timestamp = new Date().toLocaleTimeString();
|
||||||
|
logDiv.innerHTML += `<div>[${timestamp}] ${message}</div>`;
|
||||||
|
logDiv.scrollTop = logDiv.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLog() {
|
||||||
|
document.getElementById('event-log').innerHTML = '<div><strong>Event Log:</strong></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAll() {
|
||||||
|
// Reset all drop zones
|
||||||
|
document.querySelectorAll('.drop-zone').forEach(zone => {
|
||||||
|
zone.classList.remove('has-item');
|
||||||
|
zone.innerHTML = zone.id.includes('2') ?
|
||||||
|
(zone.id.includes('2a') ? 'Zone 1' : 'Zone 2') : 'Drop Here';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset coordinate target
|
||||||
|
document.querySelectorAll('.dropped-item').forEach(item => item.remove());
|
||||||
|
|
||||||
|
// Reset draggable positions
|
||||||
|
document.querySelectorAll('.draggable').forEach(item => {
|
||||||
|
item.style.position = '';
|
||||||
|
item.style.left = '';
|
||||||
|
item.style.top = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
log('All tests reset');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add drag and drop event listeners
|
||||||
|
document.querySelectorAll('.draggable').forEach(item => {
|
||||||
|
item.addEventListener('dragstart', (e) => {
|
||||||
|
e.dataTransfer.setData('text/plain', e.target.id);
|
||||||
|
e.target.style.opacity = '0.5';
|
||||||
|
log(`Drag started: ${e.target.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
item.addEventListener('dragend', (e) => {
|
||||||
|
e.target.style.opacity = '1';
|
||||||
|
log(`Drag ended: ${e.target.id}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll('.drop-zone').forEach(zone => {
|
||||||
|
zone.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
zone.classList.add('drag-over');
|
||||||
|
});
|
||||||
|
|
||||||
|
zone.addEventListener('dragleave', (e) => {
|
||||||
|
zone.classList.remove('drag-over');
|
||||||
|
});
|
||||||
|
|
||||||
|
zone.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
zone.classList.remove('drag-over');
|
||||||
|
zone.classList.add('has-item');
|
||||||
|
|
||||||
|
const draggedId = e.dataTransfer.getData('text/plain');
|
||||||
|
zone.innerHTML = `Dropped: ${draggedId}`;
|
||||||
|
log(`Drop successful: ${draggedId} -> ${zone.id}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Coordinate target handling
|
||||||
|
const coordinateTarget = document.getElementById('coordinate-target');
|
||||||
|
coordinateTarget.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
coordinateTarget.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const rect = coordinateTarget.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const y = e.clientY - rect.top;
|
||||||
|
|
||||||
|
const droppedItem = document.createElement('div');
|
||||||
|
droppedItem.className = 'dropped-item';
|
||||||
|
droppedItem.style.left = x + 'px';
|
||||||
|
droppedItem.style.top = y + 'px';
|
||||||
|
droppedItem.title = `Dropped at (${Math.round(x)}, ${Math.round(y)})`;
|
||||||
|
|
||||||
|
coordinateTarget.appendChild(droppedItem);
|
||||||
|
|
||||||
|
const draggedId = e.dataTransfer.getData('text/plain');
|
||||||
|
log(`Coordinate drop: ${draggedId} at (${Math.round(x)}, ${Math.round(y)})`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mouse tracking for debugging
|
||||||
|
let mouseCoords = null;
|
||||||
|
document.addEventListener('mousemove', (e) => {
|
||||||
|
if (mouseCoords) {
|
||||||
|
mouseCoords.style.left = (e.clientX + 10) + 'px';
|
||||||
|
mouseCoords.style.top = (e.clientY - 30) + 'px';
|
||||||
|
mouseCoords.textContent = `(${e.clientX}, ${e.clientY})`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', (e) => {
|
||||||
|
if (e.target.classList.contains('draggable')) {
|
||||||
|
mouseCoords = document.createElement('div');
|
||||||
|
mouseCoords.className = 'coordinates';
|
||||||
|
document.body.appendChild(mouseCoords);
|
||||||
|
log(`Mouse down on ${e.target.id} at (${e.clientX}, ${e.clientY})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', (e) => {
|
||||||
|
if (mouseCoords) {
|
||||||
|
mouseCoords.remove();
|
||||||
|
mouseCoords = null;
|
||||||
|
log(`Mouse up at (${e.clientX}, ${e.clientY})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
log('Drag and drop test page loaded');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user