Make new ChatMsg trait for Chat message rendering
This commit is contained in:
parent
5c9c6e9d98
commit
4ac0b5f074
13 changed files with 122 additions and 69 deletions
|
|
@ -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
62
src/euph/message.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<()>),
|
||||
|
|
|
|||
|
|
@ -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)?,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue