sequoia_view/
error_boundary.rs1use 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}