tallyweb_frontend/
lib.rs

1#![feature(async_closure)]
2#![feature(let_chains)]
3#![feature(half_open_range_patterns_in_slices)]
4#![feature(fn_traits)]
5#![feature(unboxed_closures)]
6
7use leptos::leptos_dom;
8use wasm_bindgen::{prelude::Closure, JsCast};
9
10pub mod app;
11mod session;
12pub(crate) use session::SessionFormInput;
13pub use session::UserSession;
14mod screen;
15pub(crate) use screen::{ProvideScreenSignal, Screen, ScreenStyle};
16mod preferences;
17pub(crate) use preferences::{PrefResource, Preferences};
18mod tests;
19pub(crate) use tests::*;
20
21pub(crate) mod api;
22pub(crate) mod elements;
23mod pages;
24pub(crate) mod saving;
25
26use saving::*;
27
28pub mod countable;
29pub(crate) use countable::*;
30
31use cfg_if::cfg_if;
32
33#[cfg(feature = "ssr")]
34pub mod middleware;
35
36cfg_if! {
37    if #[cfg(docsrs)] {
38        pub const LEPTOS_OUTPUT_NAME: &str = "docsrs";
39        pub const TALLYWEB_VERSION: &str = "0.3.1";
40    } else {
41        pub const LEPTOS_OUTPUT_NAME: &str = env!("LEPTOS_OUTPUT_NAME");
42        pub const TALLYWEB_VERSION: &str = env!("TALLYWEB_VERSION");
43    }
44}
45
46cfg_if! {
47    if #[cfg(feature = "hydrate")] {
48
49        use wasm_bindgen::prelude::wasm_bindgen;
50
51        #[wasm_bindgen]
52        pub fn hydrate() {
53            use app::*;
54            use leptos::*;
55
56            console_error_panic_hook::set_once();
57
58            leptos::mount_to_body(move || {
59                view! { <App/> }
60            });
61        }
62    }
63}
64
65pub type SelectionSignal = leptos::RwSignal<components::SelectionModel<uuid::Uuid, Countable>>;
66pub type StateResource =
67    leptos::Resource<UserSession, Result<CountableStore, leptos::ServerFnError>>;
68
69#[derive(
70    Debug, Clone, PartialEq, Eq, thiserror::Error, Default, serde::Serialize, serde::Deserialize,
71)]
72pub enum AppError {
73    #[error("Internal server Error")]
74    #[default]
75    Internal,
76    #[error("Failed to write to browser LocalStorage")]
77    SetLocalStorage,
78    #[error("Connection Error")]
79    Connection,
80    #[error("Authentication Error")]
81    Authentication,
82    #[error("User is missing auth_token")]
83    MissingToken,
84    #[error("Invalid Token")]
85    InvalidToken,
86    #[error("Expired Token")]
87    ExpiredToken,
88    #[error("Invalid Username or Password")]
89    InvalidSecrets,
90    #[error("Invalid Password provided")]
91    InvalidPassword,
92    #[error("Invalid Username provided")]
93    InvalidUsername,
94    #[error("User data not found")]
95    UserNotFound,
96    #[error("Failed to lock a Countable Mutex")]
97    LockMutex(String),
98    #[error("Error connecting to db pool\nGot Error: {0}")]
99    DbConnection(String),
100    #[error("Error extracting actix web data\nGot Error: {0}")]
101    Extraction(String),
102    #[error("Could not get data from database\nGot Error: {0}")]
103    DatabaseError(String),
104    #[error("Actix web Error")]
105    ActixError(String),
106    #[error("Missing Session cookie")]
107    MissingSession,
108    #[error("Invalid Session cookie")]
109    InvalidSession(String),
110    #[error("Internal error converting to Any type")]
111    AnyConversion,
112    #[error("No Connection")]
113    ConnectionError,
114    #[error("{0}")]
115    ServerError(String),
116    #[error("Unable to get window size, Got: {0}")]
117    WindowSize(String),
118    #[error("Url payload error")]
119    UrlPayload,
120    #[error("{0}: cannot contain children")]
121    CannotContainChildren(String),
122    #[error("Could not find requested countable id")]
123    CountableNotFound,
124    #[error("Unauthorized")]
125    Unauthorized,
126    #[error("Indexed db error: {0}")]
127    Indexed(String),
128    #[error("Javascript error: {0}")]
129    Javascript(String),
130    #[error("Serialization error: {0}")]
131    Serialization(String),
132    #[error("Incorrect or missing env variable: {0}")]
133    Environment(String),
134}
135
136impl From<gloo_storage::errors::StorageError> for AppError {
137    fn from(_: gloo_storage::errors::StorageError) -> Self {
138        Self::SetLocalStorage
139    }
140}
141
142impl From<leptos::ServerFnError> for AppError {
143    fn from(value: leptos::ServerFnError) -> Self {
144        match value {
145            leptos::ServerFnError::Request(_) => AppError::ConnectionError,
146            leptos::ServerFnError::ServerError(str) => AppError::ServerError(str),
147            _ => serde_json::from_str(&value.to_string())
148                .unwrap_or(AppError::ServerError(value.to_string())),
149        }
150    }
151}
152
153impl From<serde_qs::Error> for AppError {
154    fn from(_: serde_qs::Error) -> Self {
155        Self::UrlPayload
156    }
157}
158
159#[cfg(feature = "ssr")]
160impl Into<actix_web::Error> for AppError {
161    fn into(self) -> actix_web::Error {
162        match self {
163            AppError::MissingToken => actix_web::error::ErrorBadRequest(self),
164            _ => todo!(),
165        }
166    }
167}
168
169impl<T> From<std::sync::TryLockError<std::sync::MutexGuard<'_, T>>> for AppError {
170    fn from(value: std::sync::TryLockError<std::sync::MutexGuard<'_, T>>) -> Self {
171        Self::LockMutex(value.to_string())
172    }
173}
174
175impl<T> From<std::sync::PoisonError<std::sync::MutexGuard<'_, T>>> for AppError {
176    fn from(value: std::sync::PoisonError<std::sync::MutexGuard<'_, T>>) -> Self {
177        Self::LockMutex(value.to_string())
178    }
179}
180
181impl From<indexed_db::Error<AppError>> for AppError {
182    fn from(value: indexed_db::Error<AppError>) -> Self {
183        match value {
184            indexed_db::Error::NotInBrowser => Self::Indexed(value.to_string()),
185            indexed_db::Error::IndexedDbDisabled => Self::Indexed(value.to_string()),
186            indexed_db::Error::OperationNotSupported => Self::Indexed(value.to_string()),
187            indexed_db::Error::OperationNotAllowed => Self::Indexed(value.to_string()),
188            indexed_db::Error::InvalidKey => Self::Indexed(value.to_string()),
189            indexed_db::Error::VersionMustNotBeZero => Self::Indexed(value.to_string()),
190            indexed_db::Error::VersionTooOld => Self::Indexed(value.to_string()),
191            indexed_db::Error::InvalidCall => Self::Indexed(value.to_string()),
192            indexed_db::Error::InvalidArgument => Self::Indexed(value.to_string()),
193            indexed_db::Error::AlreadyExists => Self::Indexed(value.to_string()),
194            indexed_db::Error::DoesNotExist => Self::Indexed(value.to_string()),
195            indexed_db::Error::DatabaseIsClosed => Self::Indexed(value.to_string()),
196            indexed_db::Error::ObjectStoreWasRemoved => Self::Indexed(value.to_string()),
197            indexed_db::Error::ReadOnly => Self::Indexed(value.to_string()),
198            indexed_db::Error::FailedClone => Self::Indexed(value.to_string()),
199            indexed_db::Error::InvalidRange => Self::Indexed(value.to_string()),
200            indexed_db::Error::CursorCompleted => Self::Indexed(value.to_string()),
201            indexed_db::Error::User(err) => err,
202            _ => unreachable!(),
203        }
204    }
205}
206
207impl From<wasm_bindgen::JsValue> for AppError {
208    fn from(value: wasm_bindgen::JsValue) -> Self {
209        Self::Javascript(value.as_string().unwrap_or_default())
210    }
211}
212
213impl From<serde_json::Error> for AppError {
214    fn from(value: serde_json::Error) -> Self {
215        Self::Serialization(value.to_string())
216    }
217}
218
219pub fn connect_on_window_resize(f: Box<dyn FnMut()>) {
220    let closure = Closure::wrap(f as Box<dyn FnMut()>);
221    leptos_dom::window().set_onresize(Some(closure.as_ref().unchecked_ref()));
222    closure.forget();
223}