diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 222 | ||||
-rw-r--r-- | src/main.rs | 6 |
2 files changed, 228 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8e4f407 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,222 @@ +use std::{ops::{Deref, DerefMut}, str::FromStr, sync::Arc, thread::sleep, time::{self, Duration}}; + +use filamento::{db::Db, files::FilesMem, UpdateMessage}; +use jid::JID; +use leptos::{logging::debug_warn, prelude::*, task::spawn_local}; +use leptos_meta::Stylesheet; +use stylance::import_style; +use thiserror::Error; +use tokio::{sync::mpsc::Receiver}; + +pub enum AppState { + LoggedOut, + LoggedIn, +} + +#[derive(Clone)] +pub struct Client { + client: filamento::Client<FilesMem>, + jid: Arc<JID>, + file_store: FilesMem, +} + +impl Deref for Client { + type Target = filamento::Client<FilesMem>; + + fn deref(&self) -> &Self::Target { + &self.client + } +} + +impl DerefMut for Client { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.client + } +} + +#[component] +pub fn App() -> impl IntoView { + let (app, set_app) = signal(AppState::LoggedOut); + let client: RwSignal<Option<(Client, Receiver<UpdateMessage>)>, LocalStorage> = RwSignal::new_local(None); + + view! { + {move || match &*app.read() { + AppState::LoggedOut => view! { <LoginPage set_app set_client=client /> }.into_any(), + AppState::LoggedIn => { + if let Some((client, updates)) = client.write_untracked().take() { + debug_warn!("opening app"); + view! { <Macaw client updates /> }.into_any() + } else { + set_app.set(AppState::LoggedOut); + view! { <LoginPage set_app set_client=client /> }.into_any() + } + } + }} + } +} + +#[derive(Clone, Debug, Error)] +pub enum LoginError { + #[error("Missing Password")] + MissingPassword, + #[error("Missing JID")] + MissingJID, + #[error("Invalid JID: {0}")] + InvalidJID(#[from] jid::ParseError), +} + +#[component] +fn LoginPage(set_app: WriteSignal<AppState>, set_client: RwSignal<Option<(Client, Receiver<UpdateMessage>)>, LocalStorage>) -> impl IntoView { + let jid = RwSignal::new("".to_string()); + let password = RwSignal::new("".to_string()); + let remember_me = RwSignal::new(false); + let connect_on_login = RwSignal::new(true); + let (error, set_error) = signal(None::<LoginError>); + + let error_message = move || { + error.with(|error| { + if let Some(error) = error { + view! { <div class="error">{error.to_string()}</div> }.into_any() + } else { + view! {}.into_any() + } + }) + }; + + let (login_pending, set_login_pending) = signal(false); + + let login = Action::new(move |_| { + async move { + set_login_pending.set(true); + + if jid.read_untracked().is_empty() { + set_error.set(Some(LoginError::MissingJID)); + set_login_pending.set(false); + return + } + + if password.read_untracked().is_empty() { + set_error.set(Some(LoginError::MissingPassword)); + set_login_pending.set(false); + return + } + + let jid = match JID::from_str(&jid.read_untracked()) { + Ok(j) => j, + Err(e) => { + set_error.set(Some(e.into())); + set_login_pending.set(false); + return + }, + }; + + // initialise the client + let db = Db::create_connect_and_migrate("mem.db").await.unwrap(); + let files_mem = FilesMem::new(); + let (client, updates) = + filamento::Client::new(jid.clone(), password.read_untracked().clone(), db, files_mem.clone()); + // TODO: remember_me + // TODO: connect on login + let client = Client { + client, + jid: Arc::new(jid), + file_store: files_mem, + }; + + // debug!("before setting app state"); + set_client.set(Some((client, updates))); + debug_warn!("going"); + set_app.set(AppState::LoggedIn); + debug_warn!("gone"); + } + }); + + view! { + <div class="center fill"> + <div id="login-form" class="panel"> + <div id="hero"> + <img src="/assets/icon.png" /> + <h1>Macaw Instant Messenger</h1> + </div> + {error_message} + <form on:submit=move |ev| { + ev.prevent_default(); + login.dispatch(()); + }> + <label for="jid">JID</label> + <input + disabled=login_pending + placeholder="caw@macaw.chat" + type="text" + bind:value=jid + name="jid" + id="jid" + autofocus="true" + /> + <label for="password">Password</label> + <input + disabled=login_pending + placeholder="••••••••" + type="password" + bind:value=password + name="password" + id="password" + /> + <div> + <label for="remember_me">Remember me</label> + <input + disabled=login_pending + type="checkbox" + bind:checked=remember_me + name="remember_me" + id="remember_me" + /> + </div> + <div> + <label for="connect_on_login">Connect on login</label> + <input + disabled=login_pending + type="checkbox" + bind:checked=connect_on_login + name="connect_on_login" + id="connect_on_login" + /> + </div> + <input disabled=login_pending class="button" type="submit" value="Log In" /> + </form> + </div> + </div> + } +} + +#[component] +fn Macaw( + // TODO: logout + // app_state: WriteSignal<Option<essage>)>, LocalStorage>, + client: Client, + mut updates: Receiver<UpdateMessage>, +) -> impl IntoView { + let client = RwSignal::new_local(client); + provide_context(client); + debug_warn!("ajsdlkfjalsf"); + + let updates_routine = OnceResource::new(async move { + debug_warn!("started"); + while let Some(update) = updates.recv().await { + match update { + UpdateMessage::Online(online, items) => todo!(), + UpdateMessage::Offline(offline) => todo!(), + UpdateMessage::RosterUpdate(contact, user) => todo!(), + UpdateMessage::RosterDelete(jid) => todo!(), + UpdateMessage::Presence { from, presence } => todo!(), + UpdateMessage::Message { to, from, message } => todo!(), + UpdateMessage::MessageDelivery { id, chat, delivery } => todo!(), + UpdateMessage::SubscriptionRequest(jid) => todo!(), + UpdateMessage::NickChanged { jid, nick } => todo!(), + UpdateMessage::AvatarChanged { jid, id } => todo!(), + } + } + }); + + view! { "logged in" } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8b89d4d --- /dev/null +++ b/src/main.rs @@ -0,0 +1,6 @@ +use leptos::prelude::*; +use macaw_web::App; + +fn main() { + leptos::mount::mount_to_body(App) +} |