summaryrefslogblamecommitdiffstats
path: root/src/lib.rs
blob: 8e4f4071fd3a438aff69f63fa93819ee2c8e83c7 (plain) (tree)





























































































































































































































                                                                                                                                              
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" }
}