use dioxus::html::HasFileData;
use dioxus::prelude::*;
use crate::core::{client_point, element_point, Point};
#[derive(Debug, Clone, PartialEq)]
pub enum ExternalPayload {
Url(String),
Html(String),
Text(String),
}
#[derive(Clone, PartialEq)]
pub struct ExternalDrop {
pub payloads: Vec<ExternalPayload>,
pub files: Vec<dioxus::html::FileData>,
pub client: Point,
pub element: Point,
}
impl ExternalDrop {
pub fn best(&self) -> Option<&ExternalPayload> {
self.payloads.first()
}
pub fn url(&self) -> Option<&str> {
self.payloads.iter().find_map(|p| match p {
ExternalPayload::Url(u) => Some(u.as_str()),
_ => None,
})
}
pub fn text(&self) -> Option<&str> {
self.payloads.iter().find_map(|p| match p {
ExternalPayload::Text(t) => Some(t.as_str()),
_ => None,
})
}
}
pub fn classify(evt: &DragEvent) -> Vec<ExternalPayload> {
let dt = evt.data_transfer();
let mut out = Vec::new();
if let Some(uris) = dt.get_data("text/uri-list") {
for line in uris.lines() {
let line = line.trim();
if !line.is_empty() && !line.starts_with('#') {
out.push(ExternalPayload::Url(line.to_string()));
}
}
}
if let Some(html) = dt.get_data("text/html") {
if !html.is_empty() {
out.push(ExternalPayload::Html(html));
}
}
if let Some(text) = dt.get_data("text/plain") {
if !text.is_empty() {
out.push(ExternalPayload::Text(text));
}
}
out
}
#[component]
pub fn ExternalDropZone(
on_drop: EventHandler<ExternalDrop>,
#[props(default)]
on_hover: Option<EventHandler<bool>>,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let mut depth = use_signal(|| 0u32);
rsx! {
div {
ondragover: move |evt: DragEvent| {
evt.prevent_default();
},
ondragenter: move |evt: DragEvent| {
evt.prevent_default();
let d = depth() + 1;
depth.set(d);
if d == 1 {
if let Some(h) = &on_hover {
h.call(true);
}
}
},
ondragleave: move |_| {
let d = depth().saturating_sub(1);
depth.set(d);
if d == 0 {
if let Some(h) = &on_hover {
h.call(false);
}
}
},
ondrop: move |evt: DragEvent| {
evt.prevent_default();
depth.set(0);
if let Some(h) = &on_hover {
h.call(false);
}
let payloads = classify(&evt);
let files = evt.files();
if payloads.is_empty() && files.is_empty() {
return;
}
on_drop.call(ExternalDrop {
payloads,
files,
client: client_point(&evt),
element: element_point(&evt),
});
},
..attributes,
{children}
}
}
}
#[cfg(feature = "serde")]
pub mod typed {
use dioxus::prelude::*;
pub fn store<T: serde::Serialize>(evt: &DragEvent, value: &T) -> Result<(), String> {
let json = serde_json::to_string(value).map_err(|e| e.to_string())?;
evt.data_transfer().set_data("application/json", &json)
}
pub fn retrieve<T: for<'de> serde::Deserialize<'de>>(
evt: &DragEvent,
) -> Result<Option<T>, String> {
match evt.data_transfer().get_data("application/json") {
Some(json) => serde_json::from_str(&json)
.map(Some)
.map_err(|e| e.to_string()),
None => Ok(None),
}
}
}