ui changes

This commit is contained in:
2025-11-18 21:48:55 +01:00
parent fc5f285a57
commit 2e3d0793bc
8 changed files with 1190 additions and 76 deletions

View File

@@ -4,6 +4,7 @@ use tokio::sync::Mutex;
use tauri::{AppHandle, Emitter};
use irc::client::prelude::*;
use futures::stream::StreamExt;
use crate::message_formatter::format_message;
pub struct ConnectionManager {
pub client: Option<Arc<Mutex<Client>>>,
@@ -57,7 +58,7 @@ impl ConnectionManager {
async move {
let mut stream = arc_client.lock().await.stream().unwrap(); // <-- .await for Tokio Mutex
while let Some(message) = stream.next().await.transpose().unwrap() {
let msg_str = format!("{:?}", message);
let msg_str = format_message(&message);
let _ = app_handle.emit("irc-message", msg_str);
}
}

View File

@@ -1,5 +1,6 @@
mod connection_manager;
mod command; // now it exists
mod message_formatter;
use connection_manager::ConnectionManager;
use command::{

View File

@@ -0,0 +1,197 @@
use irc::client::prelude::*;
/// Escape HTML special characters
fn escape_html(s: &str) -> String {
s.replace('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.replace('"', "&quot;")
.replace('\'', "&#39;")
}
/// Format an IRC message with HTML colors and beautification
pub fn format_message(message: &Message) -> String {
// Extract prefix (server or user)
let prefix_str = match &message.prefix {
Some(Prefix::ServerName(server)) => {
format!("<span style='color: #5ddfff;'>{}</span>", escape_html(server))
},
Some(Prefix::Nickname(nick, user, host)) => {
if !host.is_empty() {
format!("<span style='color: #5af78e;'>{}</span><span style='color: #666;'>@{}</span>",
escape_html(nick), escape_html(host))
} else if !user.is_empty() {
format!("<span style='color: #5af78e;'>{}</span><span style='color: #666;'>@{}</span>",
escape_html(nick), escape_html(user))
} else {
format!("<span style='color: #5af78e;'>{}</span>", escape_html(nick))
}
},
None => String::new(),
};
// Format based on command type
match &message.command {
Command::NOTICE(target, text) => {
format!("<span style='color: #666;'><span style='color: #ffd700;'>[NOTICE]</span> </span>{}<span style='color: #666;'> → </span><span style='color: #666;'>{}</span><span style='color: #666;'>: </span><span style='color: #ffd700;'>{}</span>",
prefix_str,
escape_html(target),
escape_html(text))
},
Command::PRIVMSG(target, text) => {
format!("{}<span style='color: #666;'> → </span><span style='color: #666;'>{}</span><span style='color: #666;'>: </span><span style='color: #fff;'>{}</span>",
prefix_str,
escape_html(target),
escape_html(text))
},
Command::JOIN(channel, keys, _) => {
let keys_str = if let Some(k) = keys {
format!(" <span style='color: #666;'>(key: {})</span>", escape_html(k))
} else {
String::new()
};
format!("{}<span style='color: #5af78e;'> joined </span><span style='color: #5ddfff;'>{}</span>{}",
prefix_str,
escape_html(channel),
keys_str)
},
Command::PART(channel, msg) => {
let msg_str = if let Some(m) = msg {
format!(" <span style='color: #666;'>({})</span>", escape_html(m))
} else {
String::new()
};
format!("{}<span style='color: #ff5f87;'> left </span><span style='color: #5ddfff;'>{}</span>{}",
prefix_str,
escape_html(channel),
msg_str)
},
Command::QUIT(msg) => {
let msg_str = if let Some(m) = msg {
format!(" <span style='color: #666;'>({})</span>", escape_html(m))
} else {
String::new()
};
format!("{}<span style='color: #ff5f87;'> quit</span>{}",
prefix_str,
msg_str)
},
Command::NICK(new_nick) => {
format!("{}<span style='color: #5af78e;'> is now known as </span><span style='color: #5af78e;'>{}</span>",
prefix_str,
escape_html(new_nick))
},
Command::TOPIC(channel, topic) => {
if let Some(t) = topic {
format!("{}<span style='color: #5ddfff;'>Topic for </span><span style='color: #5ddfff;'>{}</span><span style='color: #666;'>: </span><span style='color: #ffd700;'>{}</span>",
prefix_str,
escape_html(channel),
escape_html(t))
} else {
format!("{}<span style='color: #5ddfff;'>Topic for </span><span style='color: #5ddfff;'>{}</span><span style='color: #666;'> cleared</span>",
prefix_str,
escape_html(channel))
}
},
Command::KICK(channel, user, msg) => {
let msg_str = if let Some(m) = msg {
format!(" <span style='color: #666;'>({})</span>", escape_html(m))
} else {
String::new()
};
format!("{}<span style='color: #ff5f87;'> kicked </span><span style='color: #ff5f87;'>{}</span><span style='color: #666;'> from </span><span style='color: #5ddfff;'>{}</span>{}",
prefix_str,
escape_html(user),
escape_html(channel),
msg_str)
},
Command::Response(code, params) => {
// Special handling for RPL_NAMREPLY (353) - format user list nicely
if let Response::RPL_NAMREPLY = code {
if params.len() >= 4 {
let channel = &params[2];
let users_str = &params[3];
// Split users and format them
let users: Vec<&str> = users_str.split_whitespace().collect();
let formatted_users: Vec<String> = users.iter()
.map(|u| {
// Check for channel prefixes (@ for ops, + for voiced)
if u.starts_with('@') {
format!("<span style='color: #ff5f87; font-weight: bold;'>{}</span>", escape_html(u))
} else if u.starts_with('+') {
format!("<span style='color: #5af78e;'>{}</span>", escape_html(u))
} else {
format!("<span style='color: #fff;'>{}</span>", escape_html(u))
}
})
.collect();
return format!("{}<span style='color: #666;'>[</span><span style='color: #5ddfff;'>NAMES</span><span style='color: #666;'>] </span><span style='color: #5ddfff;'>{}</span><span style='color: #666;'>: </span>{}",
prefix_str,
escape_html(channel),
formatted_users.join(" "));
}
}
// Special handling for RPL_ENDOFNAMES (366)
if let Response::RPL_ENDOFNAMES = code {
if params.len() >= 2 {
let channel = &params[1];
return format!("{}<span style='color: #666;'>[</span><span style='color: #5ddfff;'>ENDOFNAMES</span><span style='color: #666;'>] </span><span style='color: #5ddfff;'>{}</span><span style='color: #666;'>: End of /NAMES list.</span>",
prefix_str,
escape_html(channel));
}
}
let code_str = format!("{:?}", code);
let params_str = params.iter()
.map(|p| format!("<span style='color: #fff;'>{}</span>", escape_html(p)))
.collect::<Vec<_>>()
.join(" ");
format!("{}<span style='color: #666;'>[</span><span style='color: #5ddfff;'>{}</span><span style='color: #666;'>]</span> {}",
prefix_str,
escape_html(&code_str),
params_str)
},
Command::PING(server1, server2) => {
let servers = if let Some(s2) = server2 {
format!("{}, {}", server1, s2)
} else {
server1.clone()
};
format!("<span style='color: #666;'><span style='color: #5af78e;'>[PING]</span> </span>{}",
escape_html(&servers))
},
Command::PONG(server1, server2) => {
let servers = if let Some(s2) = server2 {
format!("{}, {}", server1, s2)
} else {
server1.clone()
};
format!("<span style='color: #666;'><span style='color: #5af78e;'>[PONG]</span> </span>{}",
escape_html(&servers))
},
Command::INVITE(user, channel) => {
format!("{}<span style='color: #ff87d7;'> invited </span><span style='color: #5af78e;'>{}</span><span style='color: #666;'> to </span><span style='color: #5ddfff;'>{}</span>",
prefix_str,
escape_html(user),
escape_html(channel))
},
Command::Raw(code, params) => {
let params_str = params.iter()
.map(|p| format!("<span style='color: #666;'>{}</span>", escape_html(p)))
.collect::<Vec<_>>()
.join(" ");
format!("{}<span style='color: #666;'>[RAW {}]</span> {}",
prefix_str,
code,
params_str)
},
_ => {
// Fallback for unhandled commands
format!("{}<span style='color: #666;'>[{:?}]</span>",
prefix_str,
message.command)
}
}
}

View File

@@ -11,8 +11,8 @@
"windows": [
{
"title": "ircd",
"width": 800,
"height": 600
"width": 860,
"height": 800
}
],
"security": {