diff --git a/src/app.rs b/src/app.rs index 8d694ef..ca989d5 100644 --- a/src/app.rs +++ b/src/app.rs @@ -20,7 +20,7 @@ impl History { fn down(&mut self, x: u16) { if self.count < self.height { return }; - let delta = self.count - self.height; + let delta = self.count - (self.height/2); if self.offset >= delta { return } self.offset += std::cmp::min(x, delta - self.offset); } @@ -103,12 +103,17 @@ pub async fn run<T: ratatui::backend::Backend>(term: &mut Terminal<T>, args: cra let tl_input_len = ((tl_input.len() as u16 + 3) / history.width) + 1; let tl_header = args.server.clone(); // TODO only render bottom lines - let tl_text = history + let mut tl_text_vec = history .lines .iter() .map(|x| format!("{x}")) - .collect::<Vec<String>>() - .join("\n"); + .collect::<Vec<String>>(); + + // two empty lines because my math is wrong and im lazy rn + tl_text_vec.push("".to_string()); + tl_text_vec.push("".to_string()); + + let tl_text = tl_text_vec.join("\n"); term.draw(|frame| { let layout = Layout::default() diff --git a/src/chat.rs b/src/chat.rs index 8a11ab4..647d57c 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -27,16 +27,20 @@ pub enum ChatLine { body: String, }, Error(String), + Rename(String, String), Join(String), Leave(String), Connect(String), + Raw(String), } impl Display for ChatEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let time = self.0.format("%H:%M:%S"); match &self.1 { + ChatLine::Raw(s) => write!(f, "{time} {s}"), ChatLine::Error(err) => write!(f, "{time} /!\\ {err}"), + ChatLine::Rename(before, after) => write!(f, "{time} user <{before}> renamed to <{after}>"), ChatLine::Join(user) => write!(f, "{time} [{user} joined]"), ChatLine::Leave(user) => write!(f, "{time} [{user} left]"), ChatLine::Connect(user) => write!(f, "{time} [{user} connected]"), @@ -110,16 +114,23 @@ impl Chat { Message::Ping(_) | tokio_tungstenite::tungstenite::Message::Pong(_) => {}, // ignore Message::Text(payload) => for line in payload.lines() { match serde_json::from_str::<Event>(line) { - Err(e) => chan.send(ChatEvent(chrono::Utc::now(), ChatLine::Error(format!("failed parsing message: {e}"))))?, + Err(e) => { + chan.send(ChatEvent(chrono::Utc::now(), ChatLine::Error(format!("failed parsing message: {e}"))))?; + chan.send(ChatEvent(chrono::Utc::now(), ChatLine::Raw(line.to_string())))?; + }, Ok(event) => match event.inner { - EventInner::Chat { body, visible: _ } => - chan.send(ChatEvent(event.timestamp, ChatLine::Message { user: event.user.display_name, body }))?, - EventInner::ConnectedUserInfo => - chan.send(ChatEvent(event.timestamp, ChatLine::Connect(event.user.display_name)))?, - EventInner::UserJoined => - chan.send(ChatEvent(event.timestamp, ChatLine::Join(event.user.display_name)))?, - EventInner::UserParted => - chan.send(ChatEvent(event.timestamp, ChatLine::Leave(event.user.display_name)))?, + EventInner::Chat { body, user, visible } => + chan.send(ChatEvent(event.timestamp, if visible { ChatLine::Message { user: user.display_name, body } } else { ChatLine::Raw("| REDACTED |".into()) }))?, + EventInner::ConnectedUserInfo { user } => + chan.send(ChatEvent(event.timestamp, ChatLine::Connect(user.display_name)))?, + EventInner::UserJoined { user } => + chan.send(ChatEvent(event.timestamp, ChatLine::Join(user.display_name)))?, + EventInner::UserParted { user } => + chan.send(ChatEvent(event.timestamp, ChatLine::Leave(user.display_name)))?, + EventInner::NameChange { old_name, user } => + chan.send(ChatEvent(event.timestamp, ChatLine::Rename(old_name, user.display_name)))?, + EventInner::ChatAction { body } => + chan.send(ChatEvent(event.timestamp, ChatLine::Raw(body)))?, } } }, diff --git a/src/proto.rs b/src/proto.rs index a64efb6..97230ae 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -33,26 +33,41 @@ pub mod ws { pub struct Event { pub id: String, pub timestamp: chrono::DateTime<chrono::Utc>, - pub user: User, #[serde(flatten)] pub inner: EventInner, } // TODO can i avoid repeating id,timestamp,user in each msg type?? #[derive(Debug, serde::Deserialize)] - #[serde(tag = "type")] + #[serde(tag = "type", rename_all = "camelCase")] pub enum EventInner { #[serde(rename = "CHAT")] Chat { body: String, visible: bool, + user: User, }, #[serde(rename = "CONNECTED_USER_INFO")] - ConnectedUserInfo, + ConnectedUserInfo { + user: User, + }, #[serde(rename = "USER_JOINED")] - UserJoined, + UserJoined { + user: User, + }, #[serde(rename = "USER_PARTED")] - UserParted, + UserParted { + user: User, + }, + #[serde(rename = "NAME_CHANGE")] + NameChange { + old_name: String, + user: User, + }, + #[serde(rename = "CHAT_ACTION")] + ChatAction { + body: String, + } } #[derive(Debug, serde::Deserialize)]