use std::collections::HashMap;
use dioxus::prelude::*;
use crate::core::{use_dnd, DropOutcome, DropZone, ZoneId};
use crate::pointer::PointerDraggable;
pub type ContainerId = ZoneId;
#[derive(Debug, Clone, PartialEq)]
pub struct BoardPayload<T> {
pub item: T,
pub from: ContainerId,
pub index: usize,
}
#[derive(Debug, Clone, PartialEq)]
pub struct MoveEvent<T> {
pub item: T,
pub from: (ContainerId, usize),
pub to: (ContainerId, Option<usize>),
}
pub fn apply_move<T>(board: &mut HashMap<ContainerId, Vec<T>>, mv: MoveEvent<T>) {
let (from_col, from_ix) = mv.from;
if let Some(src) = board.get_mut(&from_col) {
if from_ix < src.len() {
src.remove(from_ix);
}
}
let (to_col, to_ix) = mv.to;
let dst = board.entry(to_col).or_default();
match to_ix {
Some(ix) if ix <= dst.len() => dst.insert(ix, mv.item),
_ => dst.push(mv.item),
}
}
#[component]
pub fn BoardItem<T: Clone + PartialEq + 'static>(
item: T,
column: ContainerId,
index: usize,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
rsx! {
PointerDraggable::<BoardPayload<T>> {
payload: BoardPayload { item, from: column, index },
zone: column,
attributes,
{children}
}
}
}
#[component]
pub fn BoardColumn<T: Clone + PartialEq + 'static>(
id: ContainerId,
#[props(default)]
label: Option<String>,
on_move: EventHandler<MoveEvent<T>>,
#[props(default)]
accepts: Option<Callback<BoardPayload<T>, bool>>,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
rsx! {
DropZone::<BoardPayload<T>> {
id,
label,
accepts,
on_drop: move |outcome: DropOutcome<BoardPayload<T>>| {
let p = outcome.payload;
on_move.call(MoveEvent {
item: p.item,
from: (p.from, p.index),
to: (id, None),
});
},
attributes,
{children}
}
}
}
#[component]
pub fn BoardSlot<T: Clone + PartialEq + 'static>(
column: ContainerId,
index: usize,
on_move: EventHandler<MoveEvent<T>>,
#[props(extends = div, extends = GlobalAttributes)] attributes: Vec<Attribute>,
children: Element,
) -> Element {
let dnd = use_dnd::<BoardPayload<T>>();
rsx! {
div {
"data-active": dnd.dragging(),
ondragover: move |evt: DragEvent| {
if dnd.dragging() {
evt.prevent_default();
}
},
ondrop: {
let mut dnd = dnd;
move |evt: DragEvent| {
evt.prevent_default();
evt.stop_propagation();
if let Some((p, _)) = dnd.take() {
on_move.call(MoveEvent {
item: p.item,
from: (p.from, p.index),
to: (column, Some(index)),
});
}
}
},
..attributes,
{children}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn move_between_columns() {
let a = crate::core::ZoneId(1);
let b = crate::core::ZoneId(2);
let mut board: HashMap<ContainerId, Vec<&str>> = HashMap::new();
board.insert(a, vec!["x", "y"]);
board.insert(b, vec!["z"]);
apply_move(
&mut board,
MoveEvent {
item: "y",
from: (a, 1),
to: (b, Some(0)),
},
);
assert_eq!(board[&a], vec!["x"]);
assert_eq!(board[&b], vec!["y", "z"]);
let c = crate::core::ZoneId(3);
apply_move(
&mut board,
MoveEvent {
item: "x",
from: (a, 0),
to: (c, None),
},
);
assert!(board[&a].is_empty());
assert_eq!(board[&c], vec!["x"]);
}
}