use std::collections::HashSet;
use std::marker::PhantomData;
use derivative::Derivative;
use web_sys::*;
use yew::html::Scope;
use yew::prelude::*;
use crate::components::column_selector::{EmptyColumn, InPlaceColumn};
use crate::custom_elements::ColumnDropDownElement;
use crate::dragdrop::*;
pub trait DragDropListItemProps: Properties {
type Item: Clone + PartialEq;
fn get_item(&self) -> Self::Item;
}
pub trait DragContext<T> {
fn close(index: usize) -> T;
fn dragleave() -> T;
fn dragenter(index: usize) -> T;
fn create(col: InPlaceColumn) -> T;
}
#[derive(Properties, Derivative)]
#[derivative(Clone(bound = ""))]
pub struct DragDropListProps<T, U>
where
T: Component,
U: Component,
<U as Component>::Properties: DragDropListItemProps,
{
pub parent: Scope<T>,
pub dragdrop: DragDrop,
pub name: &'static str,
pub column_dropdown: ColumnDropDownElement,
pub exclude: HashSet<String>,
pub children: ChildrenWithProps<U>,
#[prop_or_default]
pub is_dragover: Option<(
usize,
<<U as Component>::Properties as DragDropListItemProps>::Item,
)>,
#[prop_or_default]
pub allow_duplicates: bool,
}
impl<T, U> PartialEq for DragDropListProps<T, U>
where
T: Component,
U: Component,
<U as Component>::Properties: DragDropListItemProps,
{
fn eq(&self, other: &Self) -> bool {
self.name == other.name
&& self.children == other.children
&& self.allow_duplicates == other.allow_duplicates
&& self.is_dragover == other.is_dragover
}
}
pub enum DragDropListMsg {
Freeze(bool),
}
pub struct DragDropList<T, U, V>
where
T: Component,
U: Component,
<U as Component>::Properties: DragDropListItemProps,
V: DragContext<T::Message> + 'static,
{
parent_type: PhantomData<T>,
item_type: PhantomData<U>,
draggable_type: PhantomData<V>,
elem: NodeRef,
frozen_size: Option<f64>,
}
impl<T, U, V> Component for DragDropList<T, U, V>
where
T: Component,
U: Component,
<U as Component>::Properties: DragDropListItemProps,
V: DragContext<T::Message> + 'static,
{
type Message = DragDropListMsg;
type Properties = DragDropListProps<T, U>;
fn create(_ctx: &Context<Self>) -> Self {
Self {
parent_type: PhantomData,
item_type: PhantomData,
draggable_type: PhantomData,
elem: NodeRef::default(),
frozen_size: None,
}
}
fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool {
true
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
DragDropListMsg::Freeze(freeze) => {
if freeze && self.frozen_size.is_none() {
let elem = self.elem.cast::<HtmlElement>().unwrap();
self.frozen_size = Some({
let txt = window()
.unwrap()
.get_computed_style(&elem)
.unwrap()
.unwrap()
.get_property_value("width")
.unwrap();
let px = &txt[..txt.len() - 2];
px.parse::<f64>().unwrap()
});
true
} else if !freeze {
self.frozen_size = None;
false
} else {
false
}
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let dragover = Callback::from(|_event: DragEvent| _event.prevent_default());
let drag_container = DragDropContainer::new(
{
let total = ctx.props().children.len();
let parent = ctx.props().parent.clone();
let link = ctx.link().clone();
move || {
link.send_message(DragDropListMsg::Freeze(true));
parent.send_message(V::dragenter(total))
}
},
{
let parent = ctx.props().parent.clone();
let link = ctx.link().clone();
move || {
link.send_message(DragDropListMsg::Freeze(false));
parent.send_message(V::dragleave())
}
},
);
let drop = Callback::from({
let dragdrop = ctx.props().dragdrop.clone();
let link = ctx.link().clone();
move |event| {
link.send_message(DragDropListMsg::Freeze(false));
dragdrop.notify_drop(&event);
}
});
let columns_html = {
let mut columns = ctx
.props()
.children
.iter()
.map(|x| (true, Some(x)))
.collect::<Vec<_>>();
if let Some((x, column)) = &ctx.props().is_dragover {
let index = *x;
let col_vchild = columns
.iter()
.map(|z| z.1.as_ref().unwrap())
.find(|x| x.props.get_item() == *column)
.cloned();
let changed = if !ctx.props().allow_duplicates {
let old_size = columns.len();
columns.retain(|x| x.1.as_ref().unwrap().props.get_item() != *column);
columns.len() < old_size
} else {
false
};
if index < columns.len() {
columns.insert(index, (false, col_vchild));
} else {
columns.push((false, col_vchild));
}
if changed {
columns.push((true, None));
}
}
columns
.into_iter()
.enumerate()
.map(|(idx, column)| {
let close = ctx.props().parent.callback(move |_| V::close(idx));
let dragenter = ctx.props().parent.callback({
let link = ctx.link().clone();
move |event: DragEvent| {
event.stop_propagation();
event.prevent_default();
link.send_message(DragDropListMsg::Freeze(true));
V::dragenter(idx)
}
});
if let (true, Some(column)) = column {
html! {
<div class="pivot-column" ondragenter={ dragenter }>
{ Html::from(column) }
<span class="row_close" onmousedown={ close }></span>
</div>
}
} else if let (_, Some(column)) = column {
html! {
<div class="pivot-column" ondragenter={ dragenter }>
{ Html::from(column) }
<span class="row_close" style="opacity:0.3"></span>
</div>
}
} else {
html! {
<div class="pivot-column" ondragenter={ dragenter }>
<div class="config-drop"></div>
</div>
}
}
})
.collect::<Html>()
};
let column_dropdown = ctx.props().column_dropdown.clone();
let exclude = ctx.props().exclude.clone();
let on_select = ctx.props().parent.callback(V::create);
html! {
<div ref={ &self.elem } class="rrow">
<div
id={ ctx.props().name }
ondragover={ dragover }
ondragenter={ drag_container.dragenter }
ondragleave={ drag_container.dragleave }
ref={ drag_container.noderef }
ondrop={ drop }>
<div class="psp-text-field">
<ul class="psp-text-field__input" for={ ctx.props().name }>
{ columns_html }
if ctx.props().is_dragover.is_none() {
<EmptyColumn
{ column_dropdown }
{ exclude }
{ on_select } />
}
</ul>
<label class="pivot-selector-label" for={ ctx.props().name }></label>
</div>
</div>
</div>
}
}
}