summaryrefslogtreecommitdiffstats
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs167
1 files changed, 164 insertions, 3 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 5961204..3b26827 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,9 +12,9 @@ use std::{
};
use base64::{Engine, prelude::BASE64_STANDARD};
-use chrono::{NaiveDateTime, TimeDelta};
+use chrono::{NaiveDateTime, TimeDelta, Utc};
use filamento::{
- chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::{opfs::OPFSError, FileStore, FilesMem, FilesOPFS}, presence::{Presence, PresenceType, Show}, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage
+ chat::{Body, Chat, ChatStoreFields, Delivery, Message, MessageStoreFields}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::{opfs::OPFSError, FileStore, FilesMem, FilesOPFS}, presence::{Offline, Online, Presence, PresenceType, Show}, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage
};
use futures::stream::StreamExt;
use indexmap::IndexMap;
@@ -46,6 +46,7 @@ pub enum AppState {
#[derive(Clone)]
pub struct Client {
client: filamento::Client<Files>,
+ resource: ArcRwSignal<Option<String>>,
jid: Arc<JID>,
file_store: Files,
}
@@ -224,15 +225,19 @@ fn LoginPage(
files.clone(),
);
// TODO: remember_me
+ let resource = ArcRwSignal::new(None::<String>);
let client = Client {
client,
+ resource: resource.clone(),
jid: Arc::new(jid),
file_store: files,
};
if *connect_on_login.read_untracked() {
match client.connect().await {
- Ok(_) => {}
+ Ok(r) => {
+ resource.set(Some(r))
+ }
Err(e) => {
set_error.set(Some(e.into()));
set_login_pending.set(false);
@@ -591,6 +596,17 @@ impl Presences {
presence,
);
}
+
+ pub fn resource_presence(&mut self, resource: String) -> Presence {
+ if let Some(presence) = self.presences.get(&resource) {
+ presence.clone()
+ } else {
+ Presence {
+ timestamp: Utc::now(),
+ presence: PresenceType::Offline(Offline::default()),
+ }
+ }
+ }
}
#[component]
@@ -851,11 +867,24 @@ pub fn PersonalStatus() -> impl IntoView {
let user: Store<User> = <ArcStore<filamento::user::User> as Clone>::clone(&(*user.user)).into();
view! {
<div class="dock-item" class:focused=move || *open.read() on:click=move |_| {
+ debug!("set open to true");
set_open.update(|state| *state = !*state)
}>
<AvatarWithPresence user=user />
<div class="dock-pill"></div>
</div>
+ {move || {
+ let open = open.get();
+ debug!("open = {:?}", open);
+ if open {
+ view! {
+ <Overlay set_open>
+ <PersonalStatusMenu user />
+ </Overlay>
+ }.into_any()
+ } else {
+ view! {}.into_any()
+ }}}
}.into_any()
} else {
view! {}.into_any()
@@ -863,6 +892,138 @@ pub fn PersonalStatus() -> impl IntoView {
}
#[component]
+pub fn PersonalStatusMenu(user: Store<User>) -> impl IntoView {
+ let user_presences: Store<UserPresences> = use_context().expect("no user presence store");
+
+ let client = use_context::<Client>().expect("client not in context");
+ let (show_value, set_show_value) = signal({
+ let show = match user_presences.write().get_user_presences(&user.jid().read().as_bare()).write().resource_presence(client.resource.read().clone().unwrap_or_default()).presence {
+ PresenceType::Online(online) => match online.show {
+ Some(s) => match s {
+ Show::Away => 3,
+ Show::Chat => 0,
+ Show::DoNotDisturb => 2,
+ Show::ExtendedAway => 4,
+ },
+ None => 1,
+ },
+ PresenceType::Offline(_offline) => 5,
+ };
+ debug!("initial show = {show}");
+ show
+ });
+
+ let show_select: NodeRef<html::Select> = NodeRef::new();
+
+ let set_status = Action::new_local(move |show_value: &i32| {
+ let show_value = show_value.to_owned();
+ let client = client.clone();
+ async move {
+ if let Err(e) = match show_value {
+ 0 => {
+ if let Ok(r) = client.connect().await {
+ client.resource.set(Some(r))
+ };
+ client.set_status(Online { show: Some(Show::Chat), ..Default::default() }).await
+ },
+ 1 => {
+ if let Ok(r) = client.connect().await {
+ client.resource.set(Some(r))
+ };
+ client.set_status(Online { show: None, ..Default::default() }).await
+ },
+ 2 => {
+ if let Ok(r) = client.connect().await {
+ client.resource.set(Some(r))
+ };
+ client.set_status(Online { show: Some(Show::DoNotDisturb), ..Default::default() }).await
+ },
+ 3 => {
+ if let Ok(r) = client.connect().await {
+ client.resource.set(Some(r))
+ };
+ client.set_status(Online { show: Some(Show::Away), ..Default::default() }).await
+ },
+ 4 => {
+ if let Ok(r) = client.connect().await {
+ client.resource.set(Some(r))
+ };
+ client.set_status(Online { show: Some(Show::ExtendedAway), ..Default::default() }).await
+ },
+ 5 => {
+ if let Ok(_) = client.disconnect(Offline::default()).await {
+ client.resource.set(None)
+ }
+ set_show_value.set(5);
+ return
+ }
+ _ => {
+ error!("invalid availability select");
+ return
+ }
+ } {
+ error!("show set error: {e}");
+ return
+ }
+ set_show_value.set(show_value);
+ }
+ });
+
+ view! {
+ <div class="personal-status-menu menu">
+ <div class="user">
+ <AvatarWithPresence user=user />
+ <div class="user-info">
+ <div class="nick">{move || get_name(user)}</div>
+ <div class="jid">{move || user.jid().with(|jid| jid.to_string())}</div>
+ </div>
+ </div>
+ <div class="status-edit">
+ <select
+ node_ref=show_select
+ on:change:target=move |ev| {
+ let show_value = ev.target().value().parse().unwrap();
+ set_status.dispatch(show_value);
+ }
+ prop:show_value=move || show_value.get().to_string()
+ >
+ <option value="0" selected=move || show_value.get_untracked() == 0>Available to Chat</option>
+ <option value="1" selected=move || show_value.get_untracked() == 1>Online</option>
+ <option value="2" selected=move || show_value.get_untracked() == 2>Do not disturb</option>
+ <option value="3" selected=move || show_value.get_untracked() == 3>Away</option>
+ <option value="4" selected=move || show_value.get_untracked() == 4>Extended Away</option>
+ <option value="5" selected=move || show_value.get_untracked() == 5>Offline</option>
+ </select>
+ </div>
+ <hr />
+ <div class="menu-item">
+ Profile
+ </div>
+ <div class="menu-item">
+ Settings
+ </div>
+ <hr />
+ <div class="menu-item">
+ Log out
+ </div>
+ </div>
+ }
+}
+
+#[component]
+pub fn Overlay(set_open: WriteSignal<bool>, children: Children) -> impl IntoView {
+ view! {
+ <div class="overlay">
+ <div class="overlay-background" on:click=move |_| {
+ debug!("set open to false");
+ set_open.update(|state| *state = false)
+ }></div>
+ <div class="overlay-content">{children()}</div>
+ </div>
+ }
+}
+
+#[component]
pub fn OpenChatsPanelView() -> impl IntoView {
let open_chats: Store<OpenChatsPanel> = use_context().expect("no open chats panel in context");