Make new ChatMsg trait for Chat message rendering

This commit is contained in:
Joscha 2022-08-01 19:57:05 +02:00
parent 5c9c6e9d98
commit 4ac0b5f074
13 changed files with 122 additions and 69 deletions

View file

@ -1,8 +1,10 @@
pub mod api;
mod conn;
mod message;
mod room;
mod util;
pub use conn::{Joined, Joining, Status};
pub use message::Message;
pub use room::Room;
pub use util::{hue, nick_color, nick_style};

62
src/euph/message.rs Normal file
View file

@ -0,0 +1,62 @@
use crossterm::style::{ContentStyle, Stylize};
use time::OffsetDateTime;
use toss::styled::Styled;
use crate::store::Msg;
use crate::ui::ChatMsg;
use super::api::{Snowflake, Time};
use super::util;
#[derive(Debug, Clone)]
pub struct Message {
pub id: Snowflake,
pub parent: Option<Snowflake>,
pub time: Time,
pub nick: String,
pub content: String,
}
fn styled_nick(nick: &str) -> Styled {
Styled::new_plain("[")
.then(nick, util::nick_style(nick))
.then_plain("]")
}
fn styled_content(content: &str) -> Styled {
Styled::new_plain(content.trim())
}
impl Msg for Message {
type Id = Snowflake;
fn id(&self) -> Self::Id {
self.id
}
fn parent(&self) -> Option<Self::Id> {
self.parent
}
fn last_possible_id() -> Self::Id {
Snowflake::MAX
}
}
impl ChatMsg for Message {
fn time(&self) -> OffsetDateTime {
self.time.0
}
fn styled(&self) -> (Styled, Styled) {
Self::pseudo(&self.nick, &self.content)
}
fn edit(nick: &str, content: &str) -> (Styled, Styled) {
(styled_nick(nick), styled_content(content))
}
fn pseudo(nick: &str, content: &str) -> (Styled, Styled) {
(styled_nick(nick), styled_content(content))
}
}

View file

@ -8,9 +8,10 @@ use time::format_description::FormatItem;
use time::macros::format_description;
use unicode_width::UnicodeWidthStr;
use crate::euph;
use crate::euph::api::Snowflake;
use crate::store::{MsgStore, Tree};
use crate::vault::{EuphMsg, Vault};
use crate::vault::Vault;
const TIME_FORMAT: &[FormatItem<'_>] =
format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
@ -43,7 +44,7 @@ pub async fn export(vault: &Vault, room: String, file: &Path) -> anyhow::Result<
fn write_tree(
file: &mut BufWriter<File>,
tree: &Tree<EuphMsg>,
tree: &Tree<euph::Message>,
id: Snowflake,
indent: usize,
) -> anyhow::Result<()> {
@ -64,7 +65,11 @@ fn write_tree(
Ok(())
}
fn write_msg(file: &mut BufWriter<File>, indent_string: &str, msg: &EuphMsg) -> anyhow::Result<()> {
fn write_msg(
file: &mut BufWriter<File>,
indent_string: &str,
msg: &euph::Message,
) -> anyhow::Result<()> {
let nick = &msg.nick;
let nick_empty = " ".repeat(nick.width());

View file

@ -10,6 +10,7 @@ use tokio::sync::mpsc;
use toss::styled::Styled;
use crate::store::{Msg, MsgStore, Path, Tree};
use crate::ui::ChatMsg;
#[derive(Debug, Clone)]
pub struct LogMsg {
@ -30,28 +31,35 @@ impl Msg for LogMsg {
None
}
fn last_possible_id() -> Self::Id {
Self::Id::MAX
}
}
impl ChatMsg for LogMsg {
fn time(&self) -> OffsetDateTime {
self.time
}
fn nick(&self) -> Styled {
let style = match self.level {
fn styled(&self) -> (Styled, Styled) {
let nick_style = match self.level {
Level::Error => ContentStyle::default().bold().red(),
Level::Warn => ContentStyle::default().bold().yellow(),
Level::Info => ContentStyle::default().bold().green(),
Level::Debug => ContentStyle::default().bold().blue(),
Level::Trace => ContentStyle::default().bold().magenta(),
};
let text = format!("{}", self.level);
Styled::new(text, style)
let nick = Styled::new(format!("{}", self.level), nick_style);
let content = Styled::new_plain(&self.content);
(nick, content)
}
fn content(&self) -> Styled {
Styled::new_plain(&self.content)
fn edit(nick: &str, content: &str) -> (Styled, Styled) {
panic!("log is not editable")
}
fn last_possible_id() -> Self::Id {
Self::Id::MAX
fn pseudo(nick: &str, content: &str) -> (Styled, Styled) {
panic!("log is not editable")
}
}

View file

@ -4,18 +4,12 @@ use std::hash::Hash;
use std::vec;
use async_trait::async_trait;
use time::OffsetDateTime;
use toss::styled::Styled;
pub trait Msg {
type Id: Clone + Debug + Hash + Eq + Ord;
fn id(&self) -> Self::Id;
fn parent(&self) -> Option<Self::Id>;
fn time(&self) -> OffsetDateTime;
fn nick(&self) -> Styled;
fn content(&self) -> Styled;
fn last_possible_id() -> Self::Id;
}

View file

@ -18,6 +18,7 @@ use toss::terminal::Terminal;
use crate::logger::{LogMsg, Logger};
use crate::vault::Vault;
pub use self::chat::ChatMsg;
use self::chat::ChatState;
use self::rooms::Rooms;
use self::widgets::BoxedWidget;

View file

@ -6,7 +6,9 @@ use std::sync::Arc;
use async_trait::async_trait;
use crossterm::event::KeyEvent;
use parking_lot::FairMutex;
use time::OffsetDateTime;
use toss::frame::{Frame, Size};
use toss::styled::Styled;
use toss::terminal::Terminal;
use crate::store::{Msg, MsgStore};
@ -15,6 +17,17 @@ use self::tree::{TreeView, TreeViewState};
use super::widgets::Widget;
///////////
// Trait //
///////////
pub trait ChatMsg {
fn time(&self) -> OffsetDateTime;
fn styled(&self) -> (Styled, Styled);
fn edit(nick: &str, content: &str) -> (Styled, Styled);
fn pseudo(nick: &str, content: &str) -> (Styled, Styled);
}
///////////
// State //
///////////
@ -87,7 +100,7 @@ pub enum Chat<M: Msg, S: MsgStore<M>> {
#[async_trait]
impl<M, S> Widget for Chat<M, S>
where
M: Msg,
M: Msg + ChatMsg,
M::Id: Send + Sync,
S: MsgStore<M> + Send + Sync,
{

View file

@ -18,6 +18,8 @@ use crate::ui::widgets::Widget;
use self::cursor::Cursor;
use super::ChatMsg;
///////////
// State //
///////////
@ -115,7 +117,7 @@ pub struct TreeView<M: Msg, S: MsgStore<M>>(Arc<Mutex<InnerTreeViewState<M, S>>>
#[async_trait]
impl<M, S> Widget for TreeView<M, S>
where
M: Msg,
M: Msg + ChatMsg,
M::Id: Send + Sync,
S: MsgStore<M> + Send + Sync,
{

View file

@ -4,11 +4,12 @@ use crate::store::{Msg, MsgStore, Path, Tree};
use crate::ui::chat::blocks::Block;
use crate::ui::widgets::empty::Empty;
use crate::ui::widgets::text::Text;
use crate::ui::ChatMsg;
use super::tree_blocks::{BlockId, Root, TreeBlocks};
use super::{widgets, Cursor, InnerTreeViewState};
impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
impl<M: Msg + ChatMsg, S: MsgStore<M>> InnerTreeViewState<M, S> {
async fn cursor_path(&self, cursor: &Cursor<M::Id>) -> Path<M::Id> {
match cursor {
Cursor::Msg(id) => self.store.path(id).await,

View file

@ -5,6 +5,7 @@ mod time;
use crossterm::style::{ContentStyle, Stylize};
use super::super::ChatMsg;
use crate::store::Msg;
use crate::ui::widgets::join::{HJoin, Segment};
use crate::ui::widgets::padding::Padding;
@ -19,7 +20,8 @@ pub fn style_placeholder() -> ContentStyle {
ContentStyle::default().dark_grey()
}
pub fn msg<M: Msg>(highlighted: bool, indent: usize, msg: &M) -> BoxedWidget {
pub fn msg<M: Msg + ChatMsg>(highlighted: bool, indent: usize, msg: &M) -> BoxedWidget {
let (nick, content) = msg.styled();
HJoin::new(vec![
Segment::new(
Padding::new(time::widget(Some(msg.time()), highlighted))
@ -27,10 +29,10 @@ pub fn msg<M: Msg>(highlighted: bool, indent: usize, msg: &M) -> BoxedWidget {
.right(1),
),
Segment::new(Indent::new(indent, highlighted)),
Segment::new(Padding::new(Text::new(msg.nick())).right(1)),
Segment::new(Padding::new(Text::new(nick)).right(1)),
// TODO Minimum content width
// TODO Minimizing and maximizing messages
Segment::new(Text::new(msg.content()).wrap(true)).priority(1),
Segment::new(Text::new(content).wrap(true)).priority(1),
])
.into()
}

View file

@ -10,7 +10,7 @@ use toss::terminal::Terminal;
use crate::euph::api::{SessionType, SessionView};
use crate::euph::{self, Joined, Status};
use crate::vault::{EuphMsg, EuphVault};
use crate::vault::EuphVault;
use super::chat::ChatState;
use super::widgets::background::Background;
@ -37,7 +37,7 @@ pub struct EuphRoom {
state: State,
room: Option<euph::Room>,
chat: ChatState<EuphMsg, EuphVault>,
chat: ChatState<euph::Message, EuphVault>,
nick_list: ListState<String>,
}

View file

@ -9,7 +9,7 @@ use rusqlite::Connection;
use tokio::sync::{mpsc, oneshot};
use self::euph::EuphRequest;
pub use self::euph::{EuphMsg, EuphVault};
pub use self::euph::EuphVault;
enum Request {
Close(oneshot::Sender<()>),

View file

@ -7,11 +7,11 @@ use rusqlite::types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef};
use rusqlite::{named_params, params, Connection, OptionalExtension, ToSql, Transaction};
use time::OffsetDateTime;
use tokio::sync::oneshot;
use toss::styled::Styled;
use crate::euph;
use crate::euph::api::{Message, Snowflake, Time};
use crate::store::{Msg, MsgStore, Path, Tree};
use crate::ui::ChatMsg;
use super::{Request, Vault};
@ -43,43 +43,6 @@ impl FromSql for Time {
}
}
#[derive(Debug, Clone)]
pub struct EuphMsg {
pub id: Snowflake,
pub parent: Option<Snowflake>,
pub time: Time,
pub nick: String,
pub content: String,
}
impl Msg for EuphMsg {
type Id = Snowflake;
fn id(&self) -> Self::Id {
self.id
}
fn parent(&self) -> Option<Self::Id> {
self.parent
}
fn time(&self) -> OffsetDateTime {
self.time.0
}
fn nick(&self) -> Styled {
(&self.nick, euph::nick_style(&self.nick)).into()
}
fn content(&self) -> Styled {
self.content.trim().into()
}
fn last_possible_id() -> Self::Id {
Snowflake::MAX
}
}
impl From<EuphRequest> for Request {
fn from(r: EuphRequest) -> Self {
Self::Euph(r)
@ -168,7 +131,7 @@ impl EuphVault {
}
#[async_trait]
impl MsgStore<EuphMsg> for EuphVault {
impl MsgStore<euph::Message> for EuphVault {
async fn path(&self, id: &Snowflake) -> Path<Snowflake> {
// TODO vault::Error
let (tx, rx) = oneshot::channel();
@ -181,7 +144,7 @@ impl MsgStore<EuphMsg> for EuphVault {
rx.await.unwrap()
}
async fn tree(&self, tree_id: &Snowflake) -> Tree<EuphMsg> {
async fn tree(&self, tree_id: &Snowflake) -> Tree<euph::Message> {
// TODO vault::Error
let (tx, rx) = oneshot::channel();
let request = EuphRequest::GetTree {
@ -290,7 +253,7 @@ pub(super) enum EuphRequest {
GetTree {
room: String,
root: Snowflake,
result: oneshot::Sender<Tree<EuphMsg>>,
result: oneshot::Sender<Tree<euph::Message>>,
},
GetPrevTreeId {
room: String,
@ -681,7 +644,7 @@ impl EuphRequest {
conn: &Connection,
room: String,
root: Snowflake,
result: oneshot::Sender<Tree<EuphMsg>>,
result: oneshot::Sender<Tree<euph::Message>>,
) -> rusqlite::Result<()> {
let msgs = conn
.prepare(
@ -703,7 +666,7 @@ impl EuphRequest {
",
)?
.query_map(params![room, root], |row| {
Ok(EuphMsg {
Ok(euph::Message {
id: row.get(0)?,
parent: row.get(1)?,
time: row.get(2)?,