summaryrefslogtreecommitdiffstats
path: root/src/components/new_chat.rs
blob: 2f9b9432c4a2bef454dafc4637562f3a2249c066 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use std::str::FromStr;

use filamento::{chat::Chat, error::{CommandError, DatabaseError}, user::User};
use jid::{BareJID, JID};
use leptos::{html::Input, prelude::*};
use reactive_stores::{ArcStore, Store};
use thiserror::Error;

use crate::{chat::{ArcMacawChat, MacawChat}, client::Client, open_chats::OpenChatsPanel, state_store::StateStore, user::{fetch_avatar, ArcMacawUser, MacawUser}};

#[derive(Clone, Debug, Error)]
pub enum NewChatError {
    #[error("Missing JID")]
    MissingJID,
    #[error("Invalid JID: {0}")]
    InvalidJID(#[from] jid::ParseError),
    #[error("Database: {0}")]
    Db(#[from] CommandError<DatabaseError>),
}

#[component]
pub fn NewChatWidget(set_open_new_chat: WriteSignal<bool>) -> impl IntoView {
    let jid = RwSignal::new("".to_string());

    // TODO: compartmentalise into error component, form component...
    let (error, set_error) = signal(None::<NewChatError>);
    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 (new_chat_pending, set_new_chat_pending) = signal(false);
 
    let open_chats: Store<OpenChatsPanel> =
        use_context().expect("no open chats panel store in context");
    let client = use_context::<Client>().expect("client not in context");

    let chat_state_store: StateStore<BareJID, ArcStore<Chat>> =
        use_context().expect("no chat state store");
    let user_state_store: StateStore<BareJID, (ArcStore<User>, ArcRwSignal<String>)> =
        use_context().expect("no user state store");

    let open_chat = Action::new_local(move |_| {
        let client = client.clone();
        async move {
            set_new_chat_pending.set(true);

            if jid.read_untracked().is_empty() {
                set_error.set(Some(NewChatError::MissingJID));
                set_new_chat_pending.set(false);
                return;
            }

            let jid = match JID::from_str(&jid.read_untracked()) {
                // TODO: ability to direct address a resource?
                Ok(j) => j.to_bare(),
                Err(e) => {
                    set_error.set(Some(e.into()));
                    set_new_chat_pending.set(false);
                    return;
                }
            };

            let chat_jid = jid;
            let (chat, user) = match client.get_chat_and_user(chat_jid).await {
                Ok(c) => c,
                Err(e) => {
                    set_error.set(Some(e.into()));
                    set_new_chat_pending.set(false);
                    return;
                },
            };

            let chat = {
                // let user = MacawUser::got_user(user);
                // let user = user_state_store.store(user.jid.clone(), ArcStore::new(user));
                let old_user = user_state_store.get_listener(user.jid.clone());
                let user = if let Some(old_user) = old_user {
                    old_user.update(|(old_user, _avatar)| { old_user.set(user); });
                    old_user
                } else {
                    let avatar = fetch_avatar(user.avatar.as_deref()).await;
                    let avatar = ArcRwSignal::new(avatar);
                    user_state_store.store(user.jid.clone(), (ArcStore::new(user), avatar))
                };
                let user = ArcMacawUser { user };
                let chat = chat_state_store.store(chat.correspondent.clone(), ArcStore::new(chat));
                ArcMacawChat { chat, user }
            };
            open_chats.update(|open_chats| open_chats.open(chat.clone()));
            set_open_new_chat.set(false);
        }
    });

    let jid_input = NodeRef::<Input>::new();
    let _focus = Effect::new(move |_| {
        if let Some(input) = jid_input.get() {
            let _ = input.focus();
            input.set_text_content(Some(""));
            // input.style("height: 0");
            // let height = input.scroll_height();
            // input.style(format!("height: {}px", height));
        }
    });

    view! {
        <div class="new-chat-widget">
            <form on:submit=move |ev| {
                ev.prevent_default();
                open_chat.dispatch(());
            }>
                {error_message}
                <input
                    disabled=new_chat_pending
                    placeholder="JID"
                    type="text"
                    node_ref=jid_input
                    bind:value=jid
                    name="jid"
                    id="jid"
                    autofocus="true"
                />
                <input disabled=new_chat_pending class="button" type="submit" value="Start Chat" />
            </form>
        </div>
    }
}