use std::collections::HashSet;
use std::marker::PhantomData;
use derivative::Derivative;
use perspective_client::proto::ColumnType;
use web_sys::*;
use yew::html::Scope;
use yew::prelude::*;
use crate::components::column_dropdown::ColumnDropDownElement;
use crate::components::column_selector::{EmptyColumn, InPlaceColumn, InvalidColumn};
use crate::components::type_icon::TypeIcon;
use crate::dragdrop::*;
use crate::utils::DragTarget;
#[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 disabled: bool,
#[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
&& self.disabled == other.disabled
}
}
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 invalid_drag: bool;
let mut valid_duplicate_drag = false;
let columns_html = {
let mut columns = ctx
.props()
.children
.iter()
.map(|x| (true, Some(x)))
.enumerate()
.collect::<Vec<_>>();
invalid_drag = if let Some((x, column)) = &ctx.props().is_dragover {
let index = *x;
let is_append = index == columns.len();
let is_self_move = ctx
.props()
.dragdrop
.get_drag_target()
.map(|x| V::is_self_move(x))
.unwrap_or_default();
let is_duplicate = columns
.iter()
.position(|x| x.1.1.as_ref().unwrap().props.get_item() == *column);
valid_duplicate_drag = is_duplicate.is_some() && !ctx.props().allow_duplicates;
if let Some(duplicate) = is_duplicate
&& !is_append
&& (!ctx.props().allow_duplicates || is_self_move)
{
columns.remove(duplicate);
}
if index < columns.len() {
columns.insert(index, (usize::MAX, (false, None)));
false
} else if (!is_append && !ctx.props().allow_duplicates)
|| ((!is_append || !is_self_move)
&& (is_duplicate.is_none() || ctx.props().allow_duplicates))
{
columns.push((usize::MAX, (false, None)));
false
} else {
true
}
} else {
false
};
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 (key, (true, Some(column))) = column {
html! {
<div {key} class="pivot-column" ondragenter={dragenter}>
{ Html::from(column) }
<span class="row_close" onmousedown={close} />
</div>
}
} else if let (key, (_, Some(column))) = column {
html! {
<div {key} class="pivot-column" ondragenter={dragenter}>
{ Html::from(column) }
<span class="row_close" style="opacity: 0.3" />
</div>
}
} else {
let (key, _) = column;
html! {
<div {key} class="pivot-column" ondragenter={dragenter}>
<div class="config-drop" />
</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);
let class = classes!("rrow");
let is_enabled = true;
html! {
<div ref={&self.elem} {class}>
<div
id={ctx.props().name}
ondragover={is_enabled.then_some(dragover)}
ondragenter={is_enabled.then_some(drag_container.dragenter)}
ondragleave={is_enabled.then_some(drag_container.dragleave)}
ref={drag_container.noderef}
ondrop={is_enabled.then_some(drop)}
>
<div class="psp-text-field">
<ul class="psp-text-field__input" for={ctx.props().name}>
{ columns_html }
if ctx.props().disabled && ctx.props().is_dragover.is_none() {
<div class="pivot-column">
<div class="pivot-column-border pivot-column-total">
<span class="drag-handle icon" />
<TypeIcon ty={ColumnType::Integer} />
<span class="column_name">{ "TOTAL" }</span>
</div>
<span
class="toggle-mode is_column_active"
onmousedown={ctx.props().parent.callback(move |_| V::close(0))}
/>
</div>
} else if ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag) {
<EmptyColumn {column_dropdown} {exclude} {on_select} />
} else if invalid_drag {
<InvalidColumn />
}
</ul>
<label class="pivot-selector-label" for={ctx.props().name} />
</div>
</div>
</div>
}
}
}
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;
fn is_self_move(effect: DragTarget) -> bool;
}