summaryrefslogtreecommitdiffstats
path: root/src/lib.rs
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-04-25 23:00:14 +0100
committerLibravatar cel 🌸 <cel@bunny.garden>2025-04-25 23:00:14 +0100
commitb1fc29669b54d544e02d2d73b8dc841d43a5c92a (patch)
treec972506b2ab5730a6fb0b750b40871f164d49152 /src/lib.rs
downloadmacaw-web-b1fc29669b54d544e02d2d73b8dc841d43a5c92a.tar.gz
macaw-web-b1fc29669b54d544e02d2d73b8dc841d43a5c92a.tar.bz2
macaw-web-b1fc29669b54d544e02d2d73b8dc841d43a5c92a.zip
initial commit
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs222
1 files changed, 222 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" }
+}