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}