summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-05-06 20:46:16 +0100
committerLibravatar cel 🌸 <cel@bunny.garden>2025-05-06 21:16:58 +0100
commit7859947fcc643a96d20b7c56df912d8e3230429d (patch)
tree0dede9bac67e1fd2182a7ffd975f3450ae510fa9
parente3b5d43978f06f4a8c06d49467e4bb1d1f740375 (diff)
downloadmacaw-web-7859947fcc643a96d20b7c56df912d8e3230429d.tar.gz
macaw-web-7859947fcc643a96d20b7c56df912d8e3230429d.tar.bz2
macaw-web-7859947fcc643a96d20b7c56df912d8e3230429d.zip
feat: message buffer
Diffstat (limited to '')
-rw-r--r--Cargo.lock11
-rw-r--r--Cargo.toml2
-rw-r--r--assets/style.scss84
-rw-r--r--src/lib.rs110
4 files changed, 197 insertions, 10 deletions
diff --git a/Cargo.lock b/Cargo.lock
index c7e44d3..3bfef73 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -324,6 +324,15 @@ dependencies = [
]
[[package]]
+name = "chrono-humanize"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799627e6b4d27827a814e837b9d8a504832086081806d45b1afa34dc982b023b"
+dependencies = [
+ "chrono",
+]
+
+[[package]]
name = "circular"
version = "0.3.0"
source = "git+https://bunny.garden/forks/circular#0a9c9ab1e2f4e3eb912ad9fe91e3ea953e066453"
@@ -1895,6 +1904,8 @@ dependencies = [
name = "macaw-web"
version = "0.1.0"
dependencies = [
+ "chrono",
+ "chrono-humanize",
"console_error_panic_hook",
"filamento",
"futures",
diff --git a/Cargo.toml b/Cargo.toml
index 4b80be7..8ba81a1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,6 +4,8 @@ version = "0.1.0"
edition = "2024"
[dependencies]
+chrono = "0.4.41"
+chrono-humanize = "0.2.3"
console_error_panic_hook = "0.1.7"
filamento = { path = "../luz/filamento", features = ["serde", "reactive_stores"] }
futures = "0.3.31"
diff --git a/assets/style.scss b/assets/style.scss
index deccd7c..b829501 100644
--- a/assets/style.scss
+++ b/assets/style.scss
@@ -289,6 +289,90 @@ p {
grid-area: 1 / 1 / 2 / 2;
}
+.open-chat-view {
+ flex-grow: 1;
+ max-height: 100%;
+}
+
+.messages-buffer {
+ display: flex;
+ flex-direction: column-reverse;
+ flex-grow: 1;
+ overflow: scroll;
+}
+
+.chat-message {
+ display: flex;
+ padding: 4px 0;
+}
+
+.chat-message:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.chat-message .left {
+ width: 48px;
+ flex: none;
+ padding: 0 8px;
+}
+
+.chat-message .middle {
+ width: auto;
+ flex-grow: 1;
+}
+
+.chat-message .right {
+ width: 16px;
+ padding: 0 8px;
+ flex: none;
+}
+
+.message-info {
+ display: flex;
+ align-items: baseline;
+ gap: 8px;
+}
+
+.message-text {
+ white-space: pre-wrap;
+}
+
+.chat-message.major {
+ margin-top: 8px;
+}
+
+.chat-message.major .left {
+ height: 48px;
+}
+
+.chat-message.major .middle {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+}
+
+.message-info .message-user-name {
+ font-weight: bold;
+ font-size: 1.1em;
+ margin-bottom: 4px;
+}
+
+.chat-message.major .message-timestamp {
+ font-weight: light;
+ font-size: 0.8em;
+}
+
+.chat-message.minor .message-timestamp {
+ font-weight: light;
+ font-size: 0.7em;
+ /* TODO: better alignment */
+ margin-top: 0.3em;
+}
+
+.chat-message.minor:not(:hover) .message-timestamp {
+ opacity: 0;
+}
+
/* font-families */
/* thai */
diff --git a/src/lib.rs b/src/lib.rs
index a05f511..7ff95d7 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,6 +11,7 @@ use std::{
time::{self, Duration},
};
+use chrono::{NaiveDateTime, TimeDelta};
use filamento::{
chat::{Body, Chat, ChatStoreFields, Message, MessageStoreFields}, db::Db, error::{CommandError, ConnectionError, DatabaseError}, files::FilesMem, roster::{Contact, ContactStoreFields}, user::{User, UserStoreFields}, UpdateMessage
};
@@ -521,7 +522,94 @@ pub fn ChatViewHeader(chat: MacawChat) -> impl IntoView {
#[component]
pub fn MessageHistoryBuffer(chat: MacawChat) -> impl IntoView {
- view! {}
+ let (messages, set_messages) = signal(IndexMap::new());
+ let chat_chat = *chat.chat;
+ let chat_user = *chat.user;
+
+ let load_messages = LocalResource::new(move || async move {
+ let client = use_context::<Client>().expect("client not in context");
+ let messages = client.get_messages_with_users(chat_chat.correspondent().get_untracked()).await.map_err(|e| e.to_string());
+ match messages {
+ Ok(m) => {
+ let messages = m.into_iter().map(|(message, message_user)| {
+ (message.id, MacawMessage::got_message_and_user(message, message_user))
+ }).rev().collect::<IndexMap<Uuid, _>>();
+ set_messages.set(messages);
+ },
+ Err(_) => {
+ // TODO: show error message at top of chats list
+ },
+ }
+ });
+
+ // TODO: filter new messages signal
+ let new_messages_signal: RwSignal<MessageSubscriptions> = use_context().unwrap();
+ let _load_new_messages = LocalResource::new(move || async move {
+ load_messages.await;
+ let mut new_messages = new_messages_signal.write().subscribe_chat(chat_chat.correspondent().get_untracked());
+ while let Some(new_message) = new_messages.recv().await {
+ debug!("got new message in let message buffer");
+ let mut messages= set_messages.write();
+ if let Some((_, last)) = messages.last() {
+ if *last.message.timestamp().read_untracked() < *new_message.timestamp().read_untracked() {
+ messages
+ .insert(new_message.message.id().get_untracked(), new_message);
+ debug!("set the new message in message buffer");
+ } else {
+ let index = match messages.binary_search_by(|_, value| {
+ value.message.timestamp().read_untracked().cmp(&new_message.message.timestamp().read_untracked())
+ }) {
+ Ok(i) => i,
+ Err(i) => i,
+ };
+ messages.insert_before(
+ // TODO: check if this logic is correct
+ index,
+ new_message.message.id().get_untracked(),
+ new_message,
+ );
+ debug!("set the new message in message buffer");
+ }
+ } else {
+ messages
+ .insert(new_message.message.id().get_untracked(), new_message);
+ debug!("set the new message in message buffer");
+ }
+ }
+ });
+
+ let each = move || {
+ let mut last_timestamp = NaiveDateTime::MIN;
+ let mut last_user: Option<JID> = None;
+ let mut messages = messages.get().into_iter().map(|(id, message)| {
+ let message_timestamp = message.message.timestamp().read().naive_local();
+ // if message_timestamp.date() > last_timestamp.date() {
+ // messages_view = messages_view.push(date(message_timestamp.date()));
+ // }
+ let major = if last_user.as_ref() != Some(&message.message.read().from)
+ || message_timestamp - last_timestamp > TimeDelta::minutes(3)
+ {
+ true
+ } else {
+ false
+ };
+ last_user = Some(message.message.from().get());
+ last_timestamp = message_timestamp;
+ (id, (message, major, false))
+ }).collect::<Vec<_>>();
+ if let Some((_id, (_, _, last))) = messages.last_mut() {
+ *last = true
+ }
+ messages.into_iter().rev()
+ };
+
+ view! {
+ <div class="messages-buffer">
+ <For each=each key=|message| message.0 let(message)>
+ <Message message=message.1.0 major=message.1.1 r#final=message.1.2 />
+ </For>
+ </div>
+ }
}
#[component]
@@ -533,30 +621,31 @@ pub fn Message(message: MacawMessage, major: bool, r#final: bool) -> impl IntoVi
// TODO: chrono-humanize?
// TODO: if final, show delivery not only on hover.
+ // {move || message_message.delivery().read().map(|delivery| delivery.to_string()).unwrap_or_default()}
if major {
view! {
<div class="chat-message major">
- <img class="avatar" src=avatar />
- <div>
+ <div class="left"><img class="avatar" src=avatar /></div>
+ <div class="middle">
<div class="message-info">
- <div>{name}</div>
- <div>{move || message_message.timestamp().read().to_string()}</div>
+ <div class="message-user-name">{name}</div>
+ <div class="message-timestamp">{move || message_message.timestamp().read().format("%H:%M").to_string()}</div>
</div>
<div class="message-text">
{move || message_message.body().read().body.clone()}
</div>
</div>
- <div class="message-delivery"></div>
+ <div class="right message-delivery"></div>
</div>
}.into_any()
} else {
view! {
<div class="chat-message minor">
- <div class="message-timestamp">
- {move || message_message.timestamp().read().to_string()}
+ <div class="left message-timestamp">
+ {move || message_message.timestamp().read().format("%H:%M").to_string()}
</div>
- <div class="message-text">{move || message_message.body().read().body.clone()}</div>
- <div class="message-delivery"></div>
+ <div class="middle message-text">{move || message_message.body().read().body.clone()}</div>
+ <div class="right message-delivery"></div>
</div>
}.into_any()
}
@@ -967,6 +1056,7 @@ fn ChatsList() -> impl IntoView {
debug!("got new message in let");
let mut chats = set_chats.write();
if let Some((chat, _latest_message)) = chats.shift_remove(&to) {
+ // TODO: check if new message is actually latest message
debug!("chat existed");
debug!("new message: {}", new_message.read().body.body);
chats.insert_before(0, to, (chat.clone(), new_message));