Skip to main content

sequoia_view/
error_boundary.rs

1use std::rc::Rc;
2
3use sequoia_layout::LayoutStyle;
4use sequoia_reactive::Signal;
5
6use crate::any::AnyView;
7use crate::runtime::{ElementId, Runtime, RuntimeError};
8use crate::view::View;
9use crate::views::Unit;
10
11#[derive(Clone)]
12pub struct ErrorBoundary {
13    child: Rc<dyn Fn() -> AnyView>,
14    fallback: Rc<dyn Fn(&RuntimeError) -> AnyView>,
15    on_error: Option<Rc<dyn Fn(&RuntimeError)>>,
16    failed: Signal<Option<RuntimeError>>,
17}
18
19impl ErrorBoundary {
20    pub fn new(child: impl Fn() -> AnyView + 'static) -> Self {
21        Self {
22            child: Rc::new(child),
23            fallback: Rc::new(|e: &RuntimeError| {
24                AnyView::new(crate::views::Text::new(format!(
25                    "Something went wrong ({})",
26                    e.message
27                )))
28            }),
29            on_error: None,
30            failed: Signal::new(None),
31        }
32    }
33
34    pub fn wrapping<V: View + Clone + 'static>(child: V) -> Self {
35        Self::new(move || AnyView::new(child.clone()))
36    }
37
38    pub fn fallback<V: View + 'static>(mut self, f: impl Fn(&RuntimeError) -> V + 'static) -> Self {
39        self.fallback = Rc::new(move |e| AnyView::new(f(e)));
40        self
41    }
42
43    pub fn on_error(mut self, f: impl Fn(&RuntimeError) + 'static) -> Self {
44        self.on_error = Some(Rc::new(f));
45        self
46    }
47
48    pub fn reset(&self) {
49        self.failed.set(None);
50    }
51
52    pub fn is_failed(&self) -> bool {
53        self.failed.with_untracked(|f| f.is_some())
54    }
55
56    pub(crate) fn notify(&self, rt: &Runtime, el: ElementId, err: &RuntimeError) -> bool {
57        if self.failed.with_untracked(|f| f.is_some()) {
58            return true;
59        }
60        self.failed.set(Some(err.clone()));
61        if let Some(cb) = &self.on_error {
62            cb(err);
63        }
64        rt.request_rebuild(el);
65        true
66    }
67}
68
69impl View for ErrorBoundary {
70    fn body(&self) -> impl View + 'static {
71        Unit
72    }
73
74    fn children_override(&self) -> Option<Vec<AnyView>> {
75        match self.failed.get() {
76            Some(err) => Some(vec![(self.fallback)(&err)]),
77            None => Some(vec![(self.child)()]),
78        }
79    }
80
81    fn layout_style(&self) -> LayoutStyle {
82        LayoutStyle::default()
83    }
84
85    fn layout_style_ctx(&self, rt: &Runtime, el: ElementId) -> LayoutStyle {
86        let _ = rt.errors_seq().get();
87        if self.failed.with_untracked(|f| f.is_none())
88            && let Some(err) = rt
89                .errors()
90                .into_iter()
91                .rev()
92                .find(|e| rt.is_descendant_of(e.element, el))
93        {
94            self.failed.set(Some(err.clone()));
95            if let Some(cb) = &self.on_error {
96                cb(&err);
97            }
98        }
99        LayoutStyle::default()
100    }
101}