Skip to content

Commit 01766a0

Browse files
authored
Merge pull request #91 from tofubert/feature/colour-usernames
Feature/colour usernames
2 parents 8497b28 + 8519ea0 commit 01766a0

File tree

7 files changed

+169
-1
lines changed

7 files changed

+169
-1
lines changed

Cargo.lock

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ async-trait = "=0.1.88"
6767
serde_with = "=3.14.0"
6868
tokio-util = "=0.7.15"
6969
tui-logger = { version = "=0.17.3", features = ["crossterm" ] }
70+
colorhash = "0.1.3"
7071

7172

7273
[workspace.lints.rust]

src/backend/nc_message.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@ impl NCMessage {
106106
pub fn has_reactions(&self) -> bool {
107107
!self.0.reactions.is_empty()
108108
}
109+
110+
/// return actorId
111+
pub fn get_actor_id(&self) -> String {
112+
self.0.actorId.clone()
113+
}
109114
}

src/backend/nc_request/nc_req_data_user.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use serde::{Deserialize, Deserializer, Serialize};
44
pub struct NCReqDataParticipants {
55
attendeeId: i32,
66
actorType: String,
7-
actorId: String,
7+
pub actorId: String,
88
pub displayName: String,
99
participantType: i32,
1010
lastPing: i32,

src/ui/user_styles.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use color_hash::color_hash_hex;
2+
use ratatui::style::{Color, Style};
3+
use serde::{Deserialize, Serialize};
4+
use std::collections::HashMap;
5+
use std::fs::File;
6+
use std::io::{BufReader, BufWriter};
7+
use std::path::{Path, PathBuf};
8+
use std::str::FromStr;
9+
10+
#[derive(Serialize, Deserialize, Default)]
11+
pub struct UserStyles {
12+
/// Map between user and style
13+
pub user_style_map: HashMap<String, Style>,
14+
/// Styles to allocate to users
15+
pub styles: Vec<Style>,
16+
/// Index of next style to allocate
17+
pub styles_index: usize,
18+
/// Path to use for serialization/deserialization
19+
#[serde(skip)]
20+
pub data_log_path: PathBuf,
21+
}
22+
23+
impl UserStyles {
24+
pub fn new(data_log_path: &Path) -> Self {
25+
// First try to read data from saved data
26+
let mut tmp_data_log_path = data_log_path.to_path_buf();
27+
tmp_data_log_path.push("UserStyles.json");
28+
let path = tmp_data_log_path.as_path();
29+
if path.exists() {
30+
let buf_reader = BufReader::new(File::open(path).unwrap());
31+
let mut user_styles: Self = serde_json::from_reader(buf_reader).unwrap();
32+
user_styles.data_log_path = tmp_data_log_path;
33+
return user_styles;
34+
}
35+
36+
let mut styles = Vec::new();
37+
UserStyles::generate_new_styles(&mut styles);
38+
Self {
39+
user_style_map: HashMap::<String, Style>::new(),
40+
styles,
41+
styles_index: 0,
42+
data_log_path: tmp_data_log_path,
43+
}
44+
}
45+
46+
pub fn update(&mut self, user_id: &str) {
47+
if !self.user_style_map.contains_key(user_id) {
48+
log::trace!("added {user_id}");
49+
self.user_style_map.insert(
50+
user_id.to_string(),
51+
*self.styles.get(self.styles_index).expect("style not found"),
52+
);
53+
self.styles_index += 1;
54+
if self.styles_index >= self.styles.len() {
55+
UserStyles::generate_new_styles(&mut self.styles);
56+
}
57+
}
58+
}
59+
60+
pub fn write_to_log(&self) -> Result<(), std::io::Error> {
61+
let path = self.data_log_path.as_path();
62+
let file = match std::fs::File::create(path) {
63+
Err(why) => {
64+
log::error!(
65+
"couldn't create user_styles log file {}: {}",
66+
path.as_os_str()
67+
.to_str()
68+
.expect("Path didn't become string"),
69+
why,
70+
);
71+
return Err(why);
72+
}
73+
Ok(file) => file,
74+
};
75+
let buf_writer = BufWriter::new(file);
76+
serde_json::to_writer(buf_writer, &self)?;
77+
Ok(())
78+
}
79+
80+
fn generate_new_styles(styles: &mut Vec<Style>) {
81+
for _ in 0..50 {
82+
styles.push(
83+
Color::from_str(RandomColor::new().to_hex().as_str())
84+
.expect("not a color")
85+
.into(),
86+
);
87+
}
88+
}
89+
}
90+
91+
#[cfg(test)]
92+
mod tests {
93+
use super::*;
94+
95+
#[test]
96+
fn update() {
97+
let mut user_styles = UserStyles::new(&PathBuf::new());
98+
let user_id_0 = "Terror";
99+
user_styles.update(user_id_0);
100+
let user_id_1 = "Licky";
101+
user_styles.update(user_id_1);
102+
103+
assert_eq!(
104+
user_styles.user_style_map.get(user_id_0).unwrap(),
105+
user_styles.styles.first().unwrap(),
106+
);
107+
assert_eq!(
108+
user_styles.user_style_map.get(user_id_1).unwrap(),
109+
user_styles.styles.get(1).unwrap(),
110+
);
111+
}
112+
}

src/ui/widget/chat_box.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::backend::nc_request::Token;
22
use crate::backend::{nc_room::NCRoomInterface, nc_talk::NCBackend};
33
use crate::config::Config;
44
use chrono::{DateTime, Local, Utc};
5+
use colorhash::ColorHash;
56
use ratatui::{
67
prelude::*,
78
widgets::{Block, Cell, HighlightSpacing, Row, Table, TableState},
@@ -23,6 +24,7 @@ pub struct ChatBox<'a> {
2324
unread_message_style: Style,
2425
table_header_style: Style,
2526
date_format: String,
27+
user_styles: ColorHash,
2628
}
2729

2830
impl ChatBox<'_> {
@@ -40,6 +42,7 @@ impl ChatBox<'_> {
4042
default_highlight_style: config.theme.default_highlight_style(),
4143
table_header_style: config.theme.table_header_style(),
4244
date_format: config.data.ui.date_format.clone(),
45+
user_styles: ColorHash::new().lightness(70.0),
4346
}
4447
}
4548

@@ -90,13 +93,24 @@ impl ChatBox<'_> {
9093
last_date = date_str;
9194
}
9295

96+
let colour = self.user_styles.rgb(&message_data.get_actor_id());
97+
98+
#[allow(clippy::cast_possible_truncation)]
99+
#[allow(clippy::cast_sign_loss)]
100+
let name_style = Style::new().fg(Color::Rgb(
101+
colour.red() as u8,
102+
colour.green() as u8,
103+
colour.blue() as u8,
104+
));
105+
93106
let name = textwrap::wrap(
94107
message_data.get_name().to_string().as_str(),
95108
Options::new(NAME_WIDTH.into()).break_words(true),
96109
)
97110
.into_iter()
98111
.map(std::borrow::Cow::into_owned)
99112
.map(Line::from)
113+
.map(|l| l.style(name_style))
100114
.collect_vec();
101115

102116
let message_string = message_data
@@ -237,21 +251,25 @@ mod tests {
237251
let mut mock_nc_backend = MockNCTalk::new();
238252
let mut mock_room = MockNCRoomInterface::new();
239253
let timestamp_1 = DateTime::<Utc>::from_timestamp(2000, 0).unwrap();
254+
let actor_id_1 = "abcd1234".to_string();
240255
let mock_message_1 = NCMessage::from(NCReqDataMessage {
241256
id: 0,
242257
message: "Butz".to_string(),
243258
messageType: "comment".to_string(),
244259
actorDisplayName: "Hundi".to_string(),
245260
timestamp: timestamp_1.timestamp(),
261+
actorId: actor_id_1.clone(),
246262
..Default::default()
247263
});
248264
let timestamp_2 = DateTime::<Utc>::from_timestamp(200_000, 0).unwrap();
265+
let actor_id_2 = "1234abcd".to_string();
249266
let mock_message_2 = NCMessage::from(NCReqDataMessage {
250267
id: 1,
251268
message: "Bert".to_string(),
252269
messageType: "comment".to_string(),
253270
actorDisplayName: "Stinko".to_string(),
254271
timestamp: timestamp_2.timestamp(),
272+
actorId: actor_id_2.clone(),
255273
..Default::default()
256274
});
257275
let message_tree = BTreeMap::from([(1, mock_message_1), (2, mock_message_2)]);
@@ -294,6 +312,9 @@ mod tests {
294312

295313
terminal.backend().assert_buffer(&expected);
296314

315+
let user_style_1 = Style::default().fg(Color::Rgb(196, 205, 151)); // Hash for Hundi
316+
let user_style_2 = Style::default().fg(Color::Rgb(151, 205, 156)); // Hash for Stinko
317+
297318
chat_box.update_messages(&mock_nc_backend, &"123".to_string());
298319

299320
terminal
@@ -318,13 +339,15 @@ mod tests {
318339
Rect::new(0, 1, 40, 1),
319340
config.theme.default_highlight_style(),
320341
);
342+
expected.set_style(Rect::new(6, 2, 20, 1), user_style_1);
321343
expected.set_style(
322344
Rect::new(27, 1, 13, 1),
323345
config
324346
.theme
325347
.default_highlight_style()
326348
.add_modifier(Modifier::BOLD),
327349
);
350+
expected.set_style(Rect::new(6, 4, 20, 1), user_style_2);
328351
expected.set_style(
329352
Rect::new(27, 3, 13, 1),
330353
config

src/ui/widget/users.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ impl Users<'_> {
3333
table_header_style: config.theme.table_header_style(),
3434
}
3535
}
36+
pub fn get_user_list(&self) -> &Vec<Row<'_>> {
37+
&self.user_list
38+
}
3639
pub fn render_area(&self, frame: &mut Frame, area: Rect) {
3740
frame.render_stateful_widget(self, area, &mut self.state.clone());
3841
}

0 commit comments

Comments
 (0)