use std::cell::RefCell;
use std::ops::Deref;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::*;
use yew::html::ImplicitClone;
use yew::prelude::*;
use crate::utils::*;
use crate::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DragTarget {
Active,
GroupBy,
SplitBy,
Sort,
Filter,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum DragEffect {
Copy,
Move(DragTarget),
}
#[derive(Clone)]
struct DragFrom {
column: String,
effect: DragEffect,
}
struct DragOver {
target: DragTarget,
index: usize,
}
enum DragState {
NoDrag,
DragInProgress(DragFrom),
DragOverInProgress(DragFrom, DragOver),
}
impl Default for DragState {
fn default() -> Self {
Self::NoDrag
}
}
impl DragState {
const fn is_drag_in_progress(&self) -> bool {
!matches!(self, Self::NoDrag)
}
}
#[derive(Default)]
pub struct DragDropState {
drag_state: RefCell<DragState>,
pub drop_received: PubSub<(String, DragTarget, DragEffect, usize)>,
pub dragstart_received: PubSub<DragEffect>,
pub dragend_received: PubSub<()>,
}
#[derive(Clone, Default)]
pub struct DragDrop(Rc<DragDropState>);
impl Deref for DragDrop {
type Target = Rc<DragDropState>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl PartialEq for DragDrop {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl ImplicitClone for DragDrop {}
impl DragDrop {
pub fn get_drag_column(&self) -> Option<String> {
match *self.drag_state.borrow() {
DragState::DragInProgress(DragFrom { ref column, .. })
| DragState::DragOverInProgress(DragFrom { ref column, .. }, _) => Some(column.clone()),
_ => None,
}
}
pub fn set_drag_image(&self, event: &DragEvent) -> ApiResult<()> {
event.stop_propagation();
if let Some(dt) = event.data_transfer() {
dt.set_drop_effect("move");
dt.set_data("text/plain", "{}").unwrap();
}
let original: HtmlElement = event.target().into_apierror()?.unchecked_into();
let elem: HtmlElement = original
.children()
.get_with_index(0)
.unwrap()
.clone_node_with_deep(true)?
.unchecked_into();
elem.class_list().toggle("snap-drag-image")?;
original.append_child(&elem)?;
event.data_transfer().into_apierror()?.set_drag_image(
&elem,
event.offset_x(),
event.offset_y(),
);
ApiFuture::spawn(async move {
await_animation_frame().await?;
original.remove_child(&elem)?;
Ok(())
});
Ok(())
}
pub fn is_dragover(&self, drag_target: DragTarget) -> Option<(usize, String)> {
match *self.drag_state.borrow() {
DragState::DragOverInProgress(
DragFrom { ref column, .. },
DragOver { target, index },
) if target == drag_target => Some((index, column.clone())),
_ => None,
}
}
pub fn notify_drop(&self, event: &DragEvent) {
event.prevent_default();
event.stop_propagation();
let action = match &*self.drag_state.borrow() {
DragState::DragOverInProgress(
DragFrom { column, effect },
DragOver { target, index },
) => Some((column.to_string(), *target, *effect, *index)),
_ => None,
};
if let Some(action) = action {
*self.drag_state.borrow_mut() = DragState::NoDrag;
self.drop_received.emit_all(action);
}
}
pub fn notify_drag_start(&self, column: String, effect: DragEffect) {
*self.drag_state.borrow_mut() = DragState::DragInProgress(DragFrom { column, effect });
let emit = self.dragstart_received.callback();
ApiFuture::spawn(async move {
await_animation_frame().await?;
emit.emit(effect);
Ok(())
});
}
pub fn notify_drag_end(&self) {
if self.drag_state.borrow().is_drag_in_progress() {
*self.drag_state.borrow_mut() = DragState::NoDrag;
let emit = self.dragend_received.callback();
emit.emit(());
}
}
pub fn notify_drag_leave(&self, drag_target: DragTarget) {
let reset = match *self.drag_state.borrow() {
DragState::DragOverInProgress(
DragFrom { ref column, effect },
DragOver { target, .. },
) if target == drag_target => Some((column.clone(), effect)),
_ => None,
};
if let Some((column, effect)) = reset {
self.notify_drag_start(column, effect);
}
}
pub fn notify_drag_enter(&self, target: DragTarget, index: usize) -> bool {
let mut drag_state = self.drag_state.borrow_mut();
let should_render = match &*drag_state {
DragState::DragOverInProgress(_, drag_to) => {
drag_to.target != target || drag_to.index != index
}
_ => true,
};
*drag_state = match &*drag_state {
DragState::DragOverInProgress(drag_from, _) | DragState::DragInProgress(drag_from) => {
DragState::DragOverInProgress(drag_from.clone(), DragOver { target, index })
}
_ => DragState::NoDrag,
};
should_render
}
}
pub fn dragenter_helper(callback: impl Fn() + 'static, target: NodeRef) -> Callback<DragEvent> {
Callback::from({
move |event: DragEvent| {
js_log_maybe!({
event.stop_propagation();
event.prevent_default();
if event.related_target().is_none() {
target
.cast::<HtmlElement>()
.into_apierror()?
.dataset()
.set("safaridragleave", "true")?;
}
});
callback();
}
})
}
pub fn dragleave_helper(callback: impl Fn() + 'static, drag_ref: NodeRef) -> Callback<DragEvent> {
Callback::from({
clone!(drag_ref);
move |event: DragEvent| {
js_log_maybe!({
event.stop_propagation();
event.prevent_default();
let mut related_target = event
.related_target()
.or_else(|| Some(JsValue::UNDEFINED.unchecked_into::<EventTarget>()))
.and_then(|x| x.dyn_into::<Element>().ok());
if related_target
.as_ref()
.map(|x| x.has_attribute("aria-hidden"))
.unwrap_or_default()
{
related_target = Some(
related_target
.into_apierror()?
.parent_node()
.into_apierror()?
.dyn_ref::<ShadowRoot>()
.ok_or_else(|| JsValue::from("Chrome drag/drop bug detection failed"))?
.host()
.unchecked_into::<Element>(),
)
}
let current_target = drag_ref.cast::<HtmlElement>().unwrap();
match related_target {
Some(ref related) => {
if !current_target.contains(Some(related)) {
callback();
}
}
None => {
let dataset = current_target.dataset();
if dataset.get("safaridragleave").is_some() {
dataset.delete("safaridragleave");
} else {
callback();
}
}
};
})
}
})
}
#[derive(Clone)]
pub struct DragDropContainer {
pub noderef: NodeRef,
pub dragenter: Callback<DragEvent>,
pub dragleave: Callback<DragEvent>,
}
impl DragDropContainer {
pub fn new<F: Fn() + 'static, G: Fn() + 'static>(ondragenter: F, ondragleave: G) -> Self {
let noderef = NodeRef::default();
Self {
dragenter: dragenter_helper(ondragenter, noderef.clone()),
dragleave: dragleave_helper(ondragleave, noderef.clone()),
noderef,
}
}
}