use silex_core::error::{ErrorContext, SilexError};
use silex_core::reactivity::{Effect, provide_context, signal};
use silex_dom::view::View;
use silex_html::div;
use std::rc::Rc;
use web_sys::Node;
pub struct ErrorBoundaryProps<F, C> {
pub fallback: F,
pub children: C,
}
pub struct ErrorBoundaryView<F, C> {
props: ErrorBoundaryProps<F, C>,
}
#[allow(non_snake_case)]
pub fn ErrorBoundary<F, C, V1, V2>(props: ErrorBoundaryProps<F, C>) -> ErrorBoundaryView<F, C>
where
F: Fn(SilexError) -> V1 + 'static,
C: Fn() -> V2 + 'static,
V1: View + 'static,
V2: View + 'static,
{
ErrorBoundaryView { props }
}
impl<F, C, V1, V2> View for ErrorBoundaryView<F, C>
where
F: Fn(SilexError) -> V1 + 'static,
C: Fn() -> V2 + 'static,
V1: View + 'static,
V2: View + 'static,
{
fn mount(self, parent: &Node) {
let (error, set_error) = signal::<Option<SilexError>>(None);
provide_context(ErrorContext(Rc::new(move |e| {
silex_core::log::console_error(&format!("ErrorBoundary caught error: {}", e));
wasm_bindgen_futures::spawn_local(async move {
set_error.set(Some(e));
});
})));
let wrapper = div(()).style("display: contents");
let wrapper_dom = wrapper.dom_element.clone();
wrapper.mount(parent);
let props = self.props;
Effect::new(move |_| {
wrapper_dom.set_inner_html("");
if let Some(e) = error.get() {
(props.fallback)(e).mount(&wrapper_dom);
} else {
let process = || {
let view = (props.children)();
view.mount(&wrapper_dom);
};
if let Err(payload) =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(process))
{
let msg = if let Some(s) = payload.downcast_ref::<&str>() {
format!("Panic: {}", s)
} else if let Some(s) = payload.downcast_ref::<String>() {
format!("Panic: {}", s)
} else {
"Unknown Panic".to_string()
};
silex_core::log::console_error(&format!("ErrorBoundary caught panic: {}", msg));
let err = SilexError::Javascript(msg);
wasm_bindgen_futures::spawn_local(async move {
set_error.set(Some(err));
});
}
}
});
}
}