Skip to content

Instantly share code, notes, and snippets.

@MartinKavik
Created May 12, 2023 15:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MartinKavik/409c455322f61632c1ae6f48fab5c715 to your computer and use it in GitHub Desktop.
Save MartinKavik/409c455322f61632c1ae6f48fab5c715 to your computer and use it in GitHub Desktop.
MoonZoon Sortable wrapper v1
use std::{cell::Cell, iter, rc::Rc};
use zoon::*;
#[static_ref]
fn load_assets_once() -> &'static Mutable<bool> {
Task::start(async {
load_script(public_url("sortable/sortable_1.15.0.min.js")).await;
load_assets_once().set(true);
});
Mutable::default()
}
// ------ Sortable ------
type ElementId = String;
pub struct Sortable {
raw_el: RawHtmlEl<web_sys::HtmlElement>,
controller: Mutable<Option<js_bridge::SortableController>>,
}
impl Sortable {
pub fn new(el: impl Element) -> Self {
load_assets_once();
let controller = Mutable::new(None::<js_bridge::SortableController>);
let controller_creator = Rc::new(Cell::new(None));
let raw_element = el.into_raw_element();
let mut raw_el = match raw_element {
RawElement::El(raw_el) => raw_el,
_ => panic!("Sortable supports only HTML Elements"),
};
raw_el = raw_el
.after_insert(
clone!((controller, controller_creator) move |html_element| {
controller_creator.set(Some(Task::start_droppable(async move {
load_assets_once().signal().wait_for(true).await;
controller.set(Some(js_bridge::SortableController::new(&html_element)));
})));
}),
)
.after_remove(clone!((controller) move |_| {
drop(controller);
drop(controller_creator);
}));
Self { raw_el, controller }
}
pub fn on_start(mut self, on_start: impl Fn(ElementId) + 'static) -> Self {
let callback = move |element_id: JsValue| {
on_start(element_id.as_string().unwrap_throw());
};
let closure = Closure::new(callback);
let on_start_setter = Task::start_droppable(self.controller.signal_cloned().for_each_sync(
move |controller| {
if let Some(controller) = controller {
controller.on_start(&closure);
}
},
));
self.raw_el = self.raw_el.after_remove(move |_| drop(on_start_setter));
self
}
pub fn on_end(mut self, on_end: impl Fn(ElementId, usize) + 'static) -> Self {
let callback = move |element_id: JsValue, new_element_index: usize| {
on_end(element_id.as_string().unwrap_throw(), new_element_index);
};
let closure = Closure::new(callback);
let on_end_setter = Task::start_droppable(self.controller.signal_cloned().for_each_sync(
move |controller| {
if let Some(controller) = controller {
controller.on_end(&closure);
}
},
));
self.raw_el = self.raw_el.after_remove(move |_| drop(on_end_setter));
self
}
}
impl Element for Sortable {
fn into_raw_element(self) -> RawElement {
RawElement::El(self.raw_el)
}
}
impl IntoIterator for Sortable {
type Item = Self;
type IntoIter = iter::Once<Self>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
iter::once(self)
}
}
// ------ ------
// JS Bridge
// ------ ------
mod js_bridge {
use super::*;
// https://rustwasm.github.io/wasm-bindgen/reference/js-snippets.html
#[wasm_bindgen(module = "/js/sortable/sortable_controller.js")]
extern "C" {
#[derive(Clone)]
pub type SortableController;
#[wasm_bindgen(constructor)]
pub fn new(element: &JsValue) -> SortableController;
#[wasm_bindgen(method)]
pub fn on_start(this: &SortableController, on_start: &Closure<dyn Fn(JsValue)>);
#[wasm_bindgen(method)]
pub fn on_end(this: &SortableController, on_end: &Closure<dyn Fn(JsValue, usize)>);
}
}
export class SortableController {
constructor(element) {
this.sortable = new Sortable(element, {
animation: 150,
filter: ".sortable-ignore",
// Undesirable white background and slow with native DnD on Chrome
// https://github.com/SortableJS/Sortable/issues/2082
// and `forceFallback: true` resolves it.
forceFallback: true,
fallbackTolerance: 3,
});
}
on_start(on_start) {
this.sortable.option("onStart", event => {
on_start(event.item.id);
});
}
on_end(on_end) {
this.sortable.option("onEnd", event => {
on_end(event.item.id, event.newDraggableIndex);
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment