tallyweb_frontend/
saving.rs1use 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}