tallyweb_frontend/
saving.rs

1use super::AppError;
2use components::MessageJar;
3use leptos::{create_action, create_effect, expect_context};
4use std::error::Error;
5
6#[typetag::serde(tag = "type")]
7pub trait Savable {
8    fn indexed_db_name(&self) -> String;
9    fn save_indexed<'a>(
10        &'a self,
11        obj: indexed_db::ObjectStore<AppError>,
12    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), AppError>> + 'a>>;
13    fn save_endpoint(
14        &self,
15    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), leptos::ServerFnError>>>>;
16    fn message(&self) -> Option<leptos::View>;
17    fn clone_box(&self) -> Box<dyn Savable>;
18    fn has_change(&self) -> bool;
19}
20
21pub type ErrorFn = Box<dyn Fn(&dyn Error) + 'static>;
22
23pub trait SaveHandler {
24    fn save(&self, value: Box<dyn Savable>, on_error: ErrorFn) -> Result<(), AppError>;
25    fn clone_box(&self) -> Box<dyn SaveHandler>;
26}
27
28#[allow(dead_code)]
29#[derive(Clone, Copy)]
30pub struct ServerSaveHandler {}
31
32impl ServerSaveHandler {
33    pub fn new() -> Self {
34        Self {}
35    }
36}
37
38impl SaveHandler for ServerSaveHandler {
39    fn save(
40        &self,
41        value: Box<dyn Savable>,
42        on_error: Box<dyn Fn(&dyn Error) + 'static>,
43    ) -> Result<(), AppError> {
44        if !value.has_change() {
45            return Ok(());
46        }
47
48        let msg = expect_context::<MessageJar>();
49
50        #[allow(clippy::borrowed_box)]
51        let action = create_action(move |val: &Box<dyn Savable>| val.save_endpoint());
52
53        let msg_id = value
54            .message()
55            .map(|msg_view| msg.with_handle().set_msg_view(msg_view));
56        action.dispatch(value.clone_box());
57
58        create_effect(move |_| {
59            match action.value()() {
60                Some(Err(err)) => {
61                    if let Some(id) = msg_id {
62                        msg.fade_out(id);
63                    }
64                    if !is_offline(&err) {
65                        msg.without_timeout().set_server_err(&err);
66                        on_error(&leptos::ServerFnErrorErr::from(err))
67                    }
68                }
69                Some(_) => {
70                    if let Some(id) = msg_id {
71                        msg.fade_out(id);
72                    }
73                }
74                _ => {}
75            };
76        });
77
78        Ok(())
79    }
80
81    fn clone_box(&self) -> Box<dyn SaveHandler> {
82        Box::new(*self)
83    }
84}
85
86fn is_offline(err: &leptos::ServerFnError) -> bool {
87    matches!(err, leptos::ServerFnError::Request(_))
88}
89
90pub struct SaveHandlers {
91    handlers: Vec<Box<dyn SaveHandler>>,
92}
93
94impl SaveHandlers {
95    pub fn new() -> Self {
96        Self {
97            handlers: Vec::new(),
98        }
99    }
100
101    pub fn connect_handler(&mut self, handler: Box<dyn SaveHandler>) {
102        self.handlers.push(handler)
103    }
104}
105
106impl SaveHandler for SaveHandlers {
107    fn save(
108        &self,
109        value: Box<dyn Savable>,
110        on_error: Box<dyn Fn(&dyn Error) + 'static>,
111    ) -> Result<(), AppError> {
112        let res = || -> Result<(), AppError> {
113            for h in self.handlers.iter() {
114                h.save(value.clone_box(), Box::new(|_| ()))?
115            }
116            Ok(())
117        };
118
119        res().inspect_err(|err| {
120            on_error(err);
121        })?;
122
123        Ok(())
124    }
125
126    fn clone_box(&self) -> Box<dyn SaveHandler> {
127        Box::new(self.clone())
128    }
129}
130
131impl Clone for SaveHandlers {
132    fn clone(&self) -> Self {
133        Self {
134            handlers: self.handlers.iter().map(|h| h.clone_box()).collect(),
135        }
136    }
137}