use std::rc::Rc;
use leptos::*;
use leptos_use::{use_document, use_event_listener, use_window};
use wasm_bindgen::{prelude::Closure, JsCast};
use web_sys::{Event, KeyboardEvent, MouseEvent};
use crate::{
contexts::{
global_click_event::GlobalClickEvent, global_keyboard_event::GlobalKeyboardEvent,
global_mouseup_event::GlobalMouseupEvent, global_resize_event::GlobalResizeEvent,
global_scroll_event::GlobalScrollEvent,
},
prelude::*,
};
#[derive(Debug, Clone, Copy)]
pub struct Leptonic {
pub is_mobile_device: Signal<bool>,
pub is_desktop_device: Signal<bool>,
}
#[component]
#[allow(clippy::too_many_lines)]
pub fn Root<T>(
#[allow(unused_variables)]
#[prop(into, default = Oco::Borrowed("js"))]
runtime_js_dir: Oco<'static, str>,
default_theme: T,
children: Children,
) -> impl IntoView
where
T: Theme + 'static,
{
if let Some(_root_context) = use_context::<Leptonic>() {
tracing::warn!("The <Root> component must only be used once! Detected that <Root> was rendered when it was already rendered higher up the stack. Remove this usage.");
}
let win = use_window();
let doc = use_document();
let (g_keyboard_event, set_g_keyboard_event) = create_signal::<Option<KeyboardEvent>>(None);
let mut onkeydown = None;
if let Some(doc) = &*doc {
let boxed: Box<dyn FnMut(KeyboardEvent)> =
Box::new(move |e| set_g_keyboard_event.set(Some(e)));
let closure = Closure::wrap(boxed);
doc.set_onkeydown(Some(closure.as_ref().unchecked_ref()));
onkeydown = Some(Rc::new(Box::new(closure)));
}
provide_context(GlobalKeyboardEvent::new(
onkeydown,
g_keyboard_event,
set_g_keyboard_event,
));
let (g_click_event, set_g_click_event) = create_signal::<Option<MouseEvent>>(None);
let mut onclick = None;
if let Some(doc) = &*doc {
let boxed: Box<dyn FnMut(MouseEvent)> = Box::new(move |e| set_g_click_event.set(Some(e)));
let closure = Closure::wrap(boxed);
doc.set_onclick(Some(closure.as_ref().unchecked_ref()));
onclick = Some(Rc::new(Box::new(closure)));
}
provide_context(GlobalClickEvent::new(
onclick,
g_click_event,
set_g_click_event,
));
let (g_mouseup_event, set_g_mouseup_event) = create_signal::<Option<MouseEvent>>(None);
let mut onmouseup = None;
if let Some(doc) = &*doc {
let boxed: Box<dyn FnMut(MouseEvent)> = Box::new(move |e| set_g_mouseup_event.set(Some(e)));
let closure = Closure::wrap(boxed);
doc.set_onmouseup(Some(closure.as_ref().unchecked_ref()));
onmouseup = Some(Rc::new(Box::new(closure)));
}
provide_context(GlobalMouseupEvent::new(
onmouseup,
g_mouseup_event,
set_g_mouseup_event,
));
let (g_resize_event, set_g_resize_event) = create_signal::<Option<Event>>(None);
let mut onresize = None;
if let Some(win) = &*win {
let boxed: Box<dyn FnMut(Event)> = Box::new(move |e| set_g_resize_event.set(Some(e)));
let closure = Closure::wrap(boxed);
win.set_onresize(Some(closure.as_ref().unchecked_ref()));
onresize = Some(Rc::new(Box::new(closure)));
}
provide_context(GlobalResizeEvent::new(
onresize,
g_resize_event,
set_g_resize_event,
));
let (g_scroll_event, set_g_scroll_event) = create_signal::<Option<Event>>(None);
let mut onscroll = None;
if let Some(doc) = &*doc {
let boxed: Box<dyn FnMut(Event)> = Box::new(move |e| set_g_scroll_event.set(Some(e)));
let closure = Closure::wrap(boxed);
doc.set_onscroll(Some(closure.as_ref().unchecked_ref()));
onscroll = Some(Rc::new(Box::new(closure)));
}
provide_context(GlobalScrollEvent::new(
onscroll,
g_scroll_event,
set_g_scroll_event,
));
let update_vh = move || {
#[derive(Debug)]
enum Error {
InnerHeightIndeterminable,
InnerHeightNotNumber,
DocumentIndeterminable,
SetPropertyFailed,
}
if let Some(window) = &*use_window() {
let inner_height = window
.inner_height()
.map_err(|_err| Error::InnerHeightIndeterminable)?;
let inner_height = inner_height.as_f64().ok_or(Error::InnerHeightNotNumber)?;
if let Some(document) = &*use_document() {
document
.document_element()
.ok_or(Error::DocumentIndeterminable)?
.unchecked_into::<web_sys::HtmlElement>()
.style()
.set_property("--leptonic-vh", format!("{inner_height}px").as_str())
.map_err(|_err| Error::SetPropertyFailed)?;
}
}
std::result::Result::<(), Error>::Ok(())
};
if let Err(err) = update_vh() {
tracing::warn!(?err, "Could not calculate real viewport height");
}
if let Some(win) = &*win {
let _cleanup = use_event_listener(win.clone(), leptos::ev::resize, move |_e| {
if let Err(err) = update_vh() {
tracing::warn!(?err, "Could not calculate real viewport height");
}
});
}
let is_mobile_device = Signal::derive(move || {
use_window()
.as_ref()
.map(|window| {
window
.navigator()
.user_agent()
.map(|agent| agent.to_lowercase().contains("mobi"))
.unwrap_or(false)
})
.unwrap_or(false)
});
provide_context(Leptonic {
is_mobile_device,
is_desktop_device: Signal::derive(move || !is_mobile_device.get()),
});
cfg_if::cfg_if! { if #[cfg(feature="tiptap")] {
use leptos_meta::Script;
let tiptap_js_module_includes = view! {
<Script type_="module" src=format!("/{}/tiptap-bundle.min.js", runtime_js_dir)/>
<Script type_="module" src=format!("/{}/tiptap.js", runtime_js_dir)/>
};
} else {
let tiptap_js_module_includes = view! {};
}}
view! {
{ tiptap_js_module_includes }
<ThemeProvider theme=create_signal_ls("theme", default_theme)>
<ToastRoot>
<ModalRoot>
{ children() }
</ModalRoot>
</ToastRoot>
</ThemeProvider>
}
}