119 lines
3.3 KiB
Rust

use gloo_console::error;
use serde::{Deserialize, Serialize};
use stylist::css;
use thiserror::Error;
use visions_types::CardId;
use yew::prelude::*;
use crate::{clone, design};
#[derive(Debug, Error)]
pub enum DragError {
#[error("No data transfer object found in the drag event")]
NoData,
#[error("Data in the drag event could not be deserialized: {0}")]
MalformedData(String),
#[error("Data cannot be serialized to json")]
Unserializable,
#[error("Data transfer failed")]
DataTransferFailed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum DraggableItem {
Card(CardId),
}
#[derive(Properties, PartialEq)]
pub struct DragSourceProps {
pub item: DraggableItem,
pub children: Html,
}
#[function_component]
pub fn DragSource(DragSourceProps { item, children }: &DragSourceProps) -> Html {
html! {
<div draggable="true" ondragstart={
Callback::from(clone!(item,
move |e: DragEvent| on_drag_item(e, item.clone())
))
}>
{children.clone()}
</div>
}
}
fn on_drag_item(e: DragEvent, item: DraggableItem) {
let result: Result<(), DragError> =
e.data_transfer()
.ok_or(DragError::NoData)
.and_then(|data_transfer| {
serde_json::to_string(&item)
.map_err(|_| DragError::Unserializable)
.and_then(|js| {
data_transfer
.set_data("application/json", &js)
.map_err(|_| DragError::DataTransferFailed)
})
});
if let Err(error) = result {
error!(format!("Drag start failed: {:?}", error));
}
}
#[derive(Properties, PartialEq)]
pub struct DropTargetProps {
pub on_drop: Callback<DraggableItem>,
pub children: Html,
}
#[function_component]
pub fn DropTarget(DropTargetProps { on_drop, children }: &DropTargetProps) -> Html {
let styles = css!(
border: ${*design::BORDER_HOVER_SHALLOW};
);
let current_styles = use_state(|| css!());
html! {
<div
ondragover={Callback::from(clone!((styles, current_styles), move |e: DragEvent| {
e.prevent_default();
current_styles.set(styles.clone());
}))}
ondragleave={Callback::from(clone!(current_styles, move |_: DragEvent| {
current_styles.set(css!());
}))}
ondrop={Callback::from(clone!((current_styles, on_drop), move |e: DragEvent| {
current_styles.set(css!());
on_drop_item(e, on_drop.clone())
})
)}
class={(*current_styles).clone()}
>
{children.clone()}
</div>
}
}
fn on_drop_item(e: DragEvent, callback: Callback<DraggableItem>) {
let result: Result<DraggableItem, DragError> = e
.data_transfer()
.ok_or(DragError::NoData)
.and_then(|transfer| {
transfer
.get_data("application/json")
.map_err(|_| DragError::NoData)
})
.and_then(|json| serde_json::from_str(&json).map_err(|_| DragError::MalformedData(json)));
match result {
Ok(item) => callback.emit(item),
Err(error) => error!(format!("drop error: {:?}", error)),
}
}