use tracing::{debug, instrument};
use viewpoint_js::js;
use super::Page;
use crate::error::LocatorError;
#[derive(Debug)]
pub struct DragAndDropBuilder<'a> {
page: &'a Page,
source: String,
target: String,
source_position: Option<(f64, f64)>,
target_position: Option<(f64, f64)>,
steps: u32,
}
impl<'a> DragAndDropBuilder<'a> {
pub(crate) fn new(page: &'a Page, source: String, target: String) -> Self {
Self {
page,
source,
target,
source_position: None,
target_position: None,
steps: 1,
}
}
#[must_use]
pub fn source_position(mut self, x: f64, y: f64) -> Self {
self.source_position = Some((x, y));
self
}
#[must_use]
pub fn target_position(mut self, x: f64, y: f64) -> Self {
self.target_position = Some((x, y));
self
}
#[must_use]
pub fn steps(mut self, steps: u32) -> Self {
self.steps = steps.max(1);
self
}
#[instrument(level = "debug", skip(self), fields(source = %self.source, target = %self.target))]
pub async fn send(self) -> Result<(), LocatorError> {
let source_box = self.get_element_box(&self.source).await?;
let target_box = self.get_element_box(&self.target).await?;
let (source_x, source_y) = if let Some((ox, oy)) = self.source_position {
(source_box.0 + ox, source_box.1 + oy)
} else {
(
source_box.0 + source_box.2 / 2.0,
source_box.1 + source_box.3 / 2.0,
)
};
let (target_x, target_y) = if let Some((ox, oy)) = self.target_position {
(target_box.0 + ox, target_box.1 + oy)
} else {
(
target_box.0 + target_box.2 / 2.0,
target_box.1 + target_box.3 / 2.0,
)
};
debug!(
"Dragging from ({}, {}) to ({}, {})",
source_x, source_y, target_x, target_y
);
self.page.mouse().move_(source_x, source_y).send().await?;
self.page.mouse().down().send().await?;
self.page
.mouse()
.move_(target_x, target_y)
.steps(self.steps)
.send()
.await?;
self.page.mouse().up().send().await?;
Ok(())
}
async fn get_element_box(&self, selector: &str) -> Result<(f64, f64, f64, f64), LocatorError> {
let js_code = js! {
(function() {
const el = document.querySelector(#{selector});
if (!el) return null;
const rect = el.getBoundingClientRect();
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
})()
};
let result = self.evaluate_js(&js_code).await?;
if result.is_null() {
return Err(LocatorError::NotFound(selector.to_string()));
}
let x = result
.get("x")
.and_then(serde_json::Value::as_f64)
.unwrap_or(0.0);
let y = result
.get("y")
.and_then(serde_json::Value::as_f64)
.unwrap_or(0.0);
let width = result
.get("width")
.and_then(serde_json::Value::as_f64)
.unwrap_or(0.0);
let height = result
.get("height")
.and_then(serde_json::Value::as_f64)
.unwrap_or(0.0);
Ok((x, y, width, height))
}
async fn evaluate_js(&self, expression: &str) -> Result<serde_json::Value, LocatorError> {
if self.page.is_closed() {
return Err(LocatorError::PageClosed);
}
self.page
.evaluate_js_raw(expression)
.await
.map_err(|e| LocatorError::EvaluationError(e.to_string()))
}
}