diff --git a/src/UI.js b/src/UI.js
new file mode 100644
index 0000000..6587298
--- /dev/null
+++ b/src/UI.js
@@ -0,0 +1,500 @@
+/**
+// this is needed for Tauri to work properly!
+const { invoke } = window.__TAURI__.core;
+//
+
+let greetInputEl;
+let greetMsgEl;
+
+async function greet() {
+ // Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
+ greetMsgEl.textContent = await invoke("greet", { name: greetInputEl.value });
+}
+
+window.addEventListener("DOMContentLoaded", () => {
+ greetInputEl = document.querySelector("#greet-input");
+ greetMsgEl = document.querySelector("#greet-msg");
+ document.querySelector("#greet-form").addEventListener("submit", (e) => {
+ e.preventDefault();
+ greet();
+ });
+});
+*/
+
+// UI State Management
+const uiState = {
+ connected: false,
+ activeTab: null,
+ channels: [],
+ collapsedSidebar: false,
+ userColors: new Map(),
+ currentUser: 'IRCDUser'
+};
+
+// DOM Elements
+const sidebar = document.getElementById('channels');
+const sidebarResizeHandle = document.getElementById('sidebarResizeHandle');
+const toggleSidebarBtn = document.getElementById('toggleSidebar');
+const connectBtn = document.getElementById('connectBtn');
+const disconnectBtn = document.getElementById('disconnectBtn');
+const joinBtn = document.getElementById('joinBtn');
+const partBtn = document.getElementById('partBtn');
+const sendBtn = document.getElementById('sendBtn');
+const statusDot = document.getElementById('statusDot');
+const statusText = document.getElementById('status');
+const tabBar = document.getElementById('tabBar');
+const messagesContainer = document.getElementById('messages');
+const channelList = document.getElementById('channelList');
+const messageInput = document.getElementById('messageInput');
+const channelInput = document.getElementById('newChannel');
+const commandSuggestions = document.getElementById('commandSuggestions');
+
+// Color palette for users
+const userColors = [
+ '#4b82ff', '#ff6b6b', '#7edba5', '#ffa94d',
+ '#cc5de8', '#20c997', '#f06595', '#748ffc',
+ '#ffd43b', '#69db7c', '#a9e34b', '#ff8787'
+];
+
+// Initialize UI
+function initUI() {
+ // Set current user from nickname input
+ const nicknameInput = document.getElementById('nickname');
+ uiState.currentUser = nicknameInput.value;
+ nicknameInput.addEventListener('change', (e) => {
+ uiState.currentUser = e.target.value;
+ });
+
+ // Add event listeners
+ toggleSidebarBtn.addEventListener('click', toggleSidebar);
+ connectBtn.addEventListener('click', handleConnect);
+ disconnectBtn.addEventListener('click', handleDisconnect);
+ joinBtn.addEventListener('click', handleJoin);
+ partBtn.addEventListener('click', handlePart);
+ sendBtn.addEventListener('click', handleSend);
+ messageInput.addEventListener('keydown', handleMessageInput);
+ channelInput.addEventListener('keydown', handleChannelInput);
+
+ // Set up sidebar resizing
+ setupSidebarResize();
+
+ // Set active tab
+ setActiveTab('welcome');
+}
+
+// Setup sidebar resizing
+function setupSidebarResize() {
+ let isResizing = false;
+
+ sidebarResizeHandle.addEventListener('mousedown', (e) => {
+ isResizing = true;
+ sidebarResizeHandle.classList.add('resizing');
+ document.body.style.cursor = 'col-resize';
+ e.preventDefault();
+ });
+
+ document.addEventListener('mousemove', (e) => {
+ if (!isResizing) return;
+
+ const newWidth = e.clientX;
+ if (newWidth > 200 && newWidth < 500) {
+ sidebar.style.width = `${newWidth}px`;
+ }
+ });
+
+ document.addEventListener('mouseup', () => {
+ if (isResizing) {
+ isResizing = false;
+ sidebarResizeHandle.classList.remove('resizing');
+ document.body.style.cursor = '';
+ }
+ });
+}
+
+// Toggle sidebar
+function toggleSidebar() {
+ uiState.collapsedSidebar = !uiState.collapsedSidebar;
+ sidebar.classList.toggle('collapsed', uiState.collapsedSidebar);
+
+ // Update toggle button icon
+ const icon = toggleSidebarBtn.querySelector('svg');
+ if (uiState.collapsedSidebar) {
+ icon.innerHTML = '';
+ } else {
+ icon.innerHTML = '';
+ }
+}
+
+// Get color for a user
+function getUserColor(username) {
+ if (!uiState.userColors.has(username)) {
+ // Assign a color from the palette based on username hash
+ const hash = username.split('').reduce((a, b) => {
+ a = ((a << 5) - a) + b.charCodeAt(0);
+ return a & a;
+ }, 0);
+ const colorIndex = Math.abs(hash) % userColors.length;
+ uiState.userColors.set(username, userColors[colorIndex]);
+ }
+ return uiState.userColors.get(username);
+}
+
+// Connection handlers
+function handleConnect() {
+ uiState.connected = true;
+ updateConnectionStatus();
+ addSystemMessage(`Connecting to ${document.getElementById('server').value}...`);
+
+ // Simulate connection
+ setTimeout(() => {
+ addSystemMessage('Connected to server!');
+ }, 1000);
+}
+
+function handleDisconnect() {
+ uiState.connected = false;
+ updateConnectionStatus();
+ addSystemMessage('Disconnected from server.');
+}
+
+function updateConnectionStatus() {
+ if (uiState.connected) {
+ statusDot.classList.add('online');
+ statusText.textContent = 'Connected';
+ connectBtn.disabled = true;
+ disconnectBtn.disabled = false;
+ } else {
+ statusDot.classList.remove('online');
+ statusText.textContent = 'Disconnected';
+ connectBtn.disabled = false;
+ disconnectBtn.disabled = true;
+ }
+}
+
+// Channel handlers
+function handleJoin() {
+ let channel = channelInput.value.trim();
+ if (channel && !channel.startsWith('#')) {
+ channel = '#' + channel;
+ channelInput.value = channel;
+ }
+
+ if (channel) {
+ addChannel(channel);
+ channelInput.value = '';
+ addSystemMessage(`Joined ${channel}`);
+ }
+}
+
+function handlePart() {
+ if (uiState.activeTab && uiState.activeTab !== 'welcome') {
+ const channel = uiState.activeTab;
+ removeChannel(channel);
+ addSystemMessage(`Left ${channel}`);
+ }
+}
+
+function addChannel(name) {
+ if (!uiState.channels.includes(name)) {
+ uiState.channels.push(name);
+
+ // Add to channel list
+ const channelItem = document.createElement('div');
+ channelItem.className = 'channel-item';
+ channelItem.innerHTML = `
+
#
+ ${name}
+ `;
+ channelItem.addEventListener('click', () => setActiveTab(name));
+ channelList.appendChild(channelItem);
+
+ // Add tab
+ addTab(name);
+
+ // Auto-join the channel when connected
+ if (uiState.connected) {
+ invoke('join_channel', { channel: name }).catch(e => {
+ addSystemMessage(`Failed to join ${name}: ${e}`);
+ });
+ }
+ }
+}
+
+function removeChannel(name) {
+ const index = uiState.channels.indexOf(name);
+ if (index > -1) {
+ uiState.channels.splice(index, 1);
+
+ // Remove from channel list
+ const channelItems = channelList.querySelectorAll('.channel-item');
+ channelItems.forEach(item => {
+ if (item.querySelector('.channel-name').textContent === name) {
+ item.remove();
+ }
+ });
+
+ // Remove tab
+ removeTab(name);
+
+ // Auto-leave the channel when connected
+ if (uiState.connected) {
+ invoke('part_channel', { channel: name }).catch(e => {
+ addSystemMessage(`Failed to leave ${name}: ${e}`);
+ });
+ }
+ }
+}
+
+// Tab management
+function addTab(name) {
+ const tab = document.createElement('div');
+ tab.className = 'tab';
+ tab.dataset.tab = name;
+ tab.innerHTML = `
+ ${name}
+ ×
+ `;
+
+ tab.addEventListener('click', (e) => {
+ if (e.target.classList.contains('tab-close')) {
+ removeTab(name);
+ } else {
+ setActiveTab(name);
+ }
+ });
+
+ tabBar.appendChild(tab);
+ setActiveTab(name);
+}
+
+function removeTab(name) {
+ const tab = tabBar.querySelector(`[data-tab="${name}"]`);
+ if (tab) {
+ tab.remove();
+
+ // If this was the active tab, switch to another
+ if (uiState.activeTab === name) {
+ if (uiState.channels.length > 0) {
+ setActiveTab(uiState.channels[0]);
+ } else {
+ setActiveTab('welcome');
+ }
+ }
+ }
+}
+
+function setActiveTab(name) {
+ uiState.activeTab = name;
+
+ // Update tabs
+ const tabs = tabBar.querySelectorAll('.tab');
+ tabs.forEach(tab => {
+ tab.classList.toggle('active', tab.dataset.tab === name);
+ });
+
+ // Update channels
+ const channelItems = channelList.querySelectorAll('.channel-item');
+ channelItems.forEach(item => {
+ const channelName = item.querySelector('.channel-name').textContent;
+ item.classList.toggle('active', channelName === name);
+ });
+
+ // Update messages
+ updateMessagesForTab(name);
+}
+
+function updateMessagesForTab(name) {
+ // Clear messages
+ messagesContainer.innerHTML = '';
+
+ // Add appropriate messages for the tab
+ if (name === 'welcome') {
+ addSystemMessage('IRCD - Welcome! SystemTime: ' + new Date().toLocaleString());
+ } else {
+ addSystemMessage(`Welcome to #${name}!`);
+ addSystemMessage('This is the beginning of the channel.');
+ }
+}
+
+// Message handling
+function handleSend() {
+ const message = messageInput.value.trim();
+ if (message) {
+ if (message.startsWith('/')) {
+ handleCommand(message);
+ } else {
+ const target = uiState.activeTab;
+ if (target && target !== 'welcome') {
+ addMessage(uiState.currentUser, message, true);
+ // Send the message to the server
+ invoke('send_message', { target, message }).catch(e => {
+ addSystemMessage(`Failed to send message: ${e}`);
+ });
+ } else {
+ addSystemMessage('Join a channel to send messages.');
+ }
+ }
+ messageInput.value = '';
+ }
+}
+
+function handleCommand(command) {
+ addSystemMessage(`Command: ${command}`);
+ // Handle specific commands here
+ if (command.startsWith('/join ')) {
+ const channel = command.split(' ')[1];
+ if (channel) {
+ addChannel(channel);
+ }
+ }
+}
+
+function addMessage(sender, content, isUser = false) {
+ const messageLine = document.createElement('div');
+ messageLine.className = 'message-line';
+
+ const senderElement = document.createElement('div');
+ senderElement.className = `message-sender ${isUser ? 'you' : ''}`;
+ senderElement.textContent = isUser ? `${sender} (You):` : `${sender}:`;
+ senderElement.style.color = isUser ? '' : getUserColor(sender);
+
+ const contentElement = document.createElement('div');
+ contentElement.className = 'message-content';
+ contentElement.textContent = content;
+
+ messageLine.appendChild(senderElement);
+ messageLine.appendChild(contentElement);
+
+ messagesContainer.appendChild(messageLine);
+ scrollToBottom();
+}
+
+function addSystemMessage(content) {
+ const systemMessage = document.createElement('div');
+ systemMessage.className = 'system-message';
+ systemMessage.textContent = content;
+
+ messagesContainer.appendChild(systemMessage);
+ scrollToBottom();
+}
+
+function addIRCMessage(content) {
+ const ircMessage = document.createElement('div');
+ ircMessage.className = 'irc-message';
+ ircMessage.textContent = content;
+
+ messagesContainer.appendChild(ircMessage);
+ scrollToBottom();
+}
+
+function scrollToBottom() {
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
+}
+
+// Input handlers
+function handleMessageInput(e) {
+ if (e.key === 'Enter') {
+ handleSend();
+ }
+}
+
+function handleChannelInput(e) {
+ if (e.key === 'Enter') {
+ handleJoin();
+ }
+}
+
+// Format IRC messages
+function formatIRCMessage(message) {
+ try {
+ // Parse the message object from Tauri
+ if (typeof message === 'string') {
+ return message;
+ }
+
+ // Handle different message types
+ if (message.command) {
+ const command = message.command;
+
+ // Handle PRIVMSG (regular messages)
+ if (command === 'PRIVMSG' && message.params && message.params.length >= 2) {
+ const sender = message.prefix ? extractNickname(message.prefix) : 'Unknown';
+ const target = message.params[0];
+ const content = message.params[1];
+
+ // Check if this message is for the active tab
+ if (target === uiState.activeTab || (target === uiState.currentUser && uiState.activeTab === sender)) {
+ addMessage(sender, content);
+ }
+ return `${sender} -> ${target}: ${content}`;
+ }
+
+ // Handle JOIN
+ if (command === 'JOIN' && message.params && message.params.length >= 1) {
+ const sender = message.prefix ? extractNickname(message.prefix) : 'Unknown';
+ const channel = message.params[0];
+
+ if (sender === uiState.currentUser) {
+ addSystemMessage(`You joined ${channel}`);
+ if (!uiState.channels.includes(channel)) {
+ addChannel(channel);
+ }
+ } else {
+ addSystemMessage(`${sender} joined ${channel}`);
+ }
+ return `${sender} joined ${channel}`;
+ }
+
+ // Handle PART
+ if (command === 'PART' && message.params && message.params.length >= 1) {
+ const sender = message.prefix ? extractNickname(message.prefix) : 'Unknown';
+ const channel = message.params[0];
+ const reason = message.params[1] || '';
+
+ if (sender === uiState.currentUser) {
+ addSystemMessage(`You left ${channel}${reason ? `: ${reason}` : ''}`);
+ removeChannel(channel);
+ } else {
+ addSystemMessage(`${sender} left ${channel}${reason ? `: ${reason}` : ''}`);
+ }
+ return `${sender} left ${channel}`;
+ }
+
+ // Handle server responses
+ if (typeof command === 'object' && command.Response) {
+ const [code, ...params] = command.Response;
+ return `[${code}] ${params.join(' ')}`;
+ }
+
+ // Handle raw commands
+ if (typeof command === 'object' && command.Raw) {
+ const [code, ...params] = command.Raw;
+ return `[RAW ${code}] ${params.join(' ')}`;
+ }
+
+ // Fallback: stringify the message
+ return JSON.stringify(message);
+ }
+
+ return String(message);
+ } catch (e) {
+ console.error('Error formatting IRC message:', e);
+ return String(message);
+ }
+}
+
+function extractNickname(prefix) {
+ if (typeof prefix === 'string') {
+ return prefix.split('!')[0];
+ }
+ if (prefix && prefix.Nickname) {
+ return prefix.Nickname[0];
+ }
+ if (prefix && prefix.ServerName) {
+ return prefix.ServerName;
+ }
+ return 'Unknown';
+}
+
+// Initialize the UI when the page loads
+document.addEventListener('DOMContentLoaded', initUI);
\ No newline at end of file
diff --git a/src/formatIRC.js b/src/formatIRC.js
deleted file mode 100644
index b7ca4f9..0000000
--- a/src/formatIRC.js
+++ /dev/null
@@ -1,42 +0,0 @@
-function formatIRCMessage(msg) {
- // Tauri gives us the message as a stringified Rust Debug object
- // e.g. Message { tags: None, prefix: Some(Nickname("Nick", "", "")), command: PRIVMSG("#chan", "Hello") }
- // We'll try to parse key info using regex
-
- try {
- // Extract the prefix (who sent it)
- const prefixMatch = msg.match(/prefix: Some\(([^)]+)\)/);
- let sender = prefixMatch ? prefixMatch[1] : "Unknown";
-
- // Extract the command and arguments
- const cmdMatch = msg.match(/command: (\w+)\((.*)\)/);
- if (!cmdMatch) return msg;
-
- const command = cmdMatch[1];
- const argsRaw = cmdMatch[2];
-
- // Simple handler for common IRC commands
- switch(command) {
- case 'PRIVMSG': {
- const args = argsRaw.split(/,(.+)/); // split first comma
- const target = args[0].replace(/^"|"$/g, '');
- const message = args[1].replace(/^"|"$/g, '');
- return `[${target}] <${sender}> ${message}`;
- }
- case 'NOTICE': {
- const args = argsRaw.split(/,(.+)/);
- const target = args[0].replace(/^"|"$/g, '');
- const message = args[1].replace(/^"|"$/g, '');
- return `[NOTICE ${target}] ${message}`;
- }
- case 'Response':
- case 'Raw':
- case 'UserMODE':
- return `[${command}] ${argsRaw}`;
- default:
- return msg; // fallback for unknown messages
- }
- } catch(e) {
- return msg; // fallback if parsing fails
- }
-}
diff --git a/src/index.html b/src/index.html
index 271a86d..7f9738c 100644
--- a/src/index.html
+++ b/src/index.html
@@ -1,49 +1,92 @@
-
-
-Tauri IRC Client
-
+
+
+ IRCd
+
+
+
+
+
+