From b227cde173ed4a432c46b6800a5610d526c48485 Mon Sep 17 00:00:00 2001 From: jolavillette Date: Thu, 19 Feb 2026 15:50:17 +0100 Subject: [PATCH] various small UI improvements --- webui-src/app/chat/chat.js | 147 +++++++++++++++---------- webui-src/app/files/files_downloads.js | 24 ++-- webui-src/app/files/files_util.js | 2 +- webui-src/app/main.js | 39 +++++-- webui-src/app/rswebui.js | 46 ++++++-- 5 files changed, 167 insertions(+), 91 deletions(-) diff --git a/webui-src/app/chat/chat.js b/webui-src/app/chat/chat.js index f91fc87..0d72b84 100644 --- a/webui-src/app/chat/chat.js +++ b/webui-src/app/chat/chat.js @@ -8,7 +8,7 @@ function loadLobbyDetails(id, apply) { rs.rsJsonApiRequest( '/rsChats/getChatLobbyInfo', { - id, + id: { xstr64: id }, }, (detail, success) => { if (success && detail.retval) { @@ -18,10 +18,7 @@ function loadLobbyDetails(id, apply) { apply(null); } }, - true, - {}, - undefined, - () => '{"id":"' + id + '"}' + true ); } @@ -36,11 +33,11 @@ function loadDistantChatDetails(pid, apply) { if (success && detail.retval) { // Map to lobby-like structure for UI compatibility const info = detail.info; - info.chatType = 2; // DISTANT (was 4, matched BROADCAST in C++) + info.chatType = 2; // DISTANT (matches TYPE_PRIVATE_DISTANT in rschats.h) info.lobby_name = rs.userList.username(info.to_id) || 'Distant Chat ' + pid; info.lobby_topic = 'Private Encrypted Chat'; info.gxs_id = info.own_id; - info.lobby_id = { xstr64: pid }; // Use PID as lobby_id for UI internal routing + info.lobby_id = pid; // Distant IDs are 128-bit hex strings, NO xstr64 wrapper apply(info); } else { apply(null); @@ -94,7 +91,7 @@ const ChatRoomsModel = { // Deduplicate by ID to avoid double display if backend returns redundant info const seen = new Set(); const uniqueLobbies = data.public_lobbies.filter((lobby) => { - const id = lobby.lobby_id.xstr64; + const id = rs.idToHex(lobby.lobby_id); if (seen.has(id)) return false; seen.add(id); return true; @@ -114,7 +111,7 @@ const ChatRoomsModel = { (data) => { if (data && data.cl_list) { // Robust deduplication of IDs - const ids = [...new Set(data.cl_list.map((lid) => lid.xstr64))]; + const ids = [...new Set(data.cl_list.map((lid) => rs.idToHex(lid)))]; ChatRoomsModel.knownSubscrIds = ids; // Remove stale entries that are no longer in the subscribed list @@ -155,7 +152,7 @@ const ChatRoomsModel = { ); }, subscribed(info) { - return this.knownSubscrIds.includes(info.lobby_id.xstr64); + return this.knownSubscrIds.includes(rs.idToHex(info.lobby_id)); }, }; @@ -171,17 +168,15 @@ const Message = () => { const datetime = new Date(msg.sendTime * 1000).toLocaleTimeString(); // Handle both HistoryMsg (peerId) and ChatMessage (lobby_peer_gxs_id) const rawGxsId = msg.lobby_peer_gxs_id || msg.peerId; - let gxsId = rawGxsId; - if (rawGxsId && typeof rawGxsId === 'object') { - gxsId = (rawGxsId.xstr64 && rawGxsId.xstr64 !== '0') ? rawGxsId.xstr64 : rawGxsId; - } + let gxsId = rs.idToHex(rawGxsId); // Fallback for 1-to-1 chats where sender ID might be missing (zeros) - const isZero = (id) => !id || id === '00000000000000000000000000000000' || id.xstr64 === '0'; + const isZero = (id) => !id || id === '00000000000000000000000000000000'; if (isZero(gxsId)) { const lobby = ChatLobbyModel.currentLobby; + // Types 1 (Private), 2 (Distant) are "private" conversations here if (lobby && (lobby.chatType === 1 || lobby.chatType === 2)) { - gxsId = msg.incoming ? (lobby.to_id || lobby.peer_id) : (lobby.own_id || lobby.gxs_id); + gxsId = msg.incoming ? rs.idToHex(lobby.to_id || lobby.peer_id || lobby.distant_chat_id) : rs.idToHex(lobby.own_id || lobby.gxs_id); } } @@ -280,43 +275,47 @@ const ChatLobbyModel = { setIdentity(lobbyId, nick) { rs.rsJsonApiRequest( '/rsChats/setIdentityForChatLobby', - {}, - () => m.route.set('/chat/:lobby_id', { lobbyId }), - true, - {}, - JSON.parse, - () => '{"lobby_id":' + lobbyId + ',"nick":"' + nick + '"}' + { + lobby_id: { xstr64: lobbyId }, + nick: nick, + }, + () => m.route.set('/chat/:lobby', { lobby: lobbyId }), + true ); }, enterPublicLobby(lobbyId, nick) { // Set lobby nickname rs.rsJsonApiRequest( '/rsChats/joinVisibleChatLobby', - {}, + { + lobby_id: { xstr64: lobbyId }, + own_id: nick, + }, () => { loadLobbyDetails(lobbyId, (info) => { ChatRoomsModel.subscribedRooms[lobbyId] = info; ChatRoomsModel.loadSubscribedRooms(() => { - m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 }); + m.route.set('/chat/:lobby', { lobby: rs.idToHex(info.lobby_id) }); }); }); }, - true, - {}, - JSON.parse, - () => '{"lobby_id":' + lobbyId + ',"own_id":"' + nick + '"}' + true ); }, unsubscribeChatLobby(lobbyId, follow) { // Unsubscribe rs.rsJsonApiRequest( '/rsChats/unsubscribeChatLobby', - {}, - () => ChatRoomsModel.loadSubscribedRooms(follow), - true, - {}, - JSON.parse, - () => '{"lobby_id":' + lobbyId + '}' + { + id: { xstr64: lobbyId }, + }, + (data, success) => { + if (success) { + if (follow) { + follow(); + } + } + } ); }, chatId() { @@ -356,22 +355,30 @@ const ChatLobbyModel = { this.addMessages(l); }); - // Register for chatEvents for future messages // Register for chatEvents for future messages rs.events[15].notify = (chatMessage) => { + // DEBUG: Log incoming message structure + console.log('[RS-DEBUG] Incoming Chat Message:', JSON.stringify(chatMessage, null, 2)); + const msgCid = chatMessage.chat_id; - let match = false; - if (msgCid.type === detail.chatType) { - if (detail.chatType === 3) { - const clid = msgCid.lobby_id ? (typeof msgCid.lobby_id === 'object' ? msgCid.lobby_id.xstr64 : msgCid.lobby_id) : undefined; - if (clid === currentlobbyid) match = true; - } else if (detail.chatType === 2) { - const dChatIdRaw = msgCid.distant_chat_id; - const dChatId = typeof dChatIdRaw === 'object' ? (dChatIdRaw.xstr64 !== '0' ? dChatIdRaw.xstr64 : dChatIdRaw) : dChatIdRaw; - if (dChatId == currentlobbyid) match = true; - } + let msgId; + + if (msgCid.type === 3) { + msgId = rs.idToHex(msgCid.lobby_id); + } else if (msgCid.type === 2) { + // For Distant Chat, the ID is the distant_chat_id + msgId = rs.idToHex(msgCid.distant_chat_id); + } else if (msgCid.type === 1) { + // For Private Chat, the ID is the peer_id + msgId = rs.idToHex(msgCid.peer_id); + } else { + // Fallback + msgId = rs.idToHex(msgCid); } - if (match) { + + console.log('[RS-DEBUG] Resolved Msg ID:', msgId, 'Current Lobby ID:', currentlobbyid, 'Match:', msgId === currentlobbyid); + + if (msgId === currentlobbyid) { this.addMessages([chatMessage]); } }; @@ -411,7 +418,7 @@ const ChatLobbyModel = { this.setupAction = this.enterPublicLobby; this.isSubscribed = false; ChatRoomsModel.allRooms.forEach((it) => { - if (it.lobby_id.xstr64 === currentlobbyid) { + if (rs.idToHex(it.lobby_id) === currentlobbyid) { this.currentLobby = it; this.lobby_user = '???'; this.lobbyid = currentlobbyid; @@ -443,21 +450,20 @@ const ChatLobbyModel = { ); }, selected(info, selName, defaultName) { - const currid = (ChatLobbyModel.currentLobby.lobby_id || { xstr64: m.route.param('lobby') }) - .xstr64; - return (info.lobby_id.xstr64 === currid ? selName : '') + defaultName; + const currid = rs.idToHex(ChatLobbyModel.currentLobby.lobby_id || { xstr64: m.route.param('lobby') }); + return (rs.idToHex(info.lobby_id) === currid ? selName : '') + defaultName; }, switchToEvent(info) { return () => { ChatLobbyModel.currentLobby = info; - m.route.set('/chat/:lobby', { lobby: info.lobby_id.xstr64 }); - ChatLobbyModel.loadLobby(info.lobby_id.xstr64); // update + m.route.set('/chat/:lobby', { lobby: rs.idToHex(info.lobby_id) }); + ChatLobbyModel.loadLobby(rs.idToHex(info.lobby_id)); // update }; }, setupEvent(info) { return () => { - m.route.set('/chat/:lobby/setup', { lobby: info.lobby_id.xstr64 }); - ChatLobbyModel.loadPublicLobby(info.lobby_id.xstr64); // update + m.route.set('/chat/:lobby/setup', { lobby: rs.idToHex(info.lobby_id) }); + ChatLobbyModel.loadPublicLobby(rs.idToHex(info.lobby_id)); // update }; }, }; @@ -471,7 +477,7 @@ const Lobby = () => { return m( ChatLobbyModel.selected(info, '.selected-lobby', tagname), { - key: info.lobby_id.xstr64, + key: rs.idToHex(info.lobby_id), onclick, }, [ @@ -570,11 +576,36 @@ const LobbyName = () => { ChatLobbyModel.isSubscribed ? [m('span.chatusername', ChatLobbyModel.lobby_user), m('span.chatatchar', '@')] : [], + ChatLobbyModel.currentLobby.chatType === 2 + ? m('i.fas.fa-circle', { + style: { + color: + ChatLobbyModel.currentLobby.status === 2 + ? '#2ecc71' // Green (Can Talk) + : ChatLobbyModel.currentLobby.status === 1 + ? '#f39c12' // Orange (Tunnel Down) + : ChatLobbyModel.currentLobby.status === 3 + ? '#e74c3c' // Red (Remotely Closed) + : '#95a5a6', // Grey (Unknown) + fontSize: '0.6em', + marginRight: '10px', + verticalAlign: 'middle', + }, + title: + ChatLobbyModel.currentLobby.status === 2 + ? 'Tunnel Active (Can Talk)' + : ChatLobbyModel.currentLobby.status === 1 + ? 'Tunnel Down (Negotiating...)' + : ChatLobbyModel.currentLobby.status === 3 + ? 'Remotely Closed' + : 'Status Unknown', + }) + : [], m('span.chatlobbyname', ChatLobbyModel.currentLobby.lobby_name), m('.mobile-menu-icons', [ m('i.fas.fa-users', { onclick: () => MobileState.toggleUsers() }), ]), - m.route.param('subaction') !== 'setup' + m.route.param('subaction') !== 'setup' && ChatLobbyModel.currentLobby.chatType === 3 ? [ m('i.fas.fa-cog.setupicon', { title: 'configure lobby', @@ -739,8 +770,8 @@ const LayoutCreateDistant = () => { from_pid: id, notify: true, }, - (result) => { - m.route.set('/chat/:lobbyid', { lobbyid: result.pid }); + (res) => { + m.route.set('/chat/:lobby', { lobby: rs.idToHex(res.pid) }); } ), }, diff --git a/webui-src/app/files/files_downloads.js b/webui-src/app/files/files_downloads.js index 9a41a30..192c0a1 100644 --- a/webui-src/app/files/files_downloads.js +++ b/webui-src/app/files/files_downloads.js @@ -149,24 +149,24 @@ const Component = () => { Downloads.resetSearch(); }, view: () => [ - m('.widget__body-heading', [ - m('h3', `Downloads (${Downloads.hashes ? Downloads.hashes.length : 0} files)`), - m('.action', [ + m('.widget__body-heading', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-start' } }, [ + m('.action', { style: { marginBottom: '10px' } }, [ m('button', { onclick: () => widget.popupMessage(m(NewFileDialog)) }, 'Add new file'), m('button', { onclick: clearFileCompleted }, 'Clear completed'), ]), + m('h3', `Downloads (${Downloads.hashes ? Downloads.hashes.length : 0} files)`), ]), m('.widget__body-content', [ Downloads.statusMap && - Object.keys(Downloads.statusMap).map((hash) => - m(util.File, { - info: Downloads.statusMap[hash], - strategy: Downloads.strategies[hash], - direction: 'down', - transferred: Downloads.statusMap[hash].transfered.xint64, - chunksInfo: Downloads.chunksMap[hash], - }) - ), + Object.keys(Downloads.statusMap).map((hash) => + m(util.File, { + info: Downloads.statusMap[hash], + strategy: Downloads.strategies[hash], + direction: 'down', + transferred: Downloads.statusMap[hash].transfered.xint64, + chunksInfo: Downloads.chunksMap[hash], + }) + ), ]), ], }; diff --git a/webui-src/app/files/files_util.js b/webui-src/app/files/files_util.js index 226b297..59b029e 100644 --- a/webui-src/app/files/files_util.js +++ b/webui-src/app/files/files_util.js @@ -205,7 +205,7 @@ const File = () => { }); } return m('.file-view', { style: { display: info.isSearched ? 'block' : 'none' } }, [ - m('.file-view__heading', [ + m('.file-view__heading', { style: { display: 'flex', flexDirection: 'column', alignItems: 'flex-start' } }, [ m('h6', info.fname), chunkStrat !== undefined && direction === 'down' && [ diff --git a/webui-src/app/main.js b/webui-src/app/main.js index 2683564..ea178aa 100644 --- a/webui-src/app/main.js +++ b/webui-src/app/main.js @@ -37,19 +37,40 @@ const navbar = () => { }, [ m('.nav-menu__logo', [ - m('img', { - src: 'images/retroshare.svg', - alt: 'retroshare_icon', - }), - m('.nav-menu__logo-text', [ - m('h5', 'RetroShare'), - m('.webui-version-box', [ - m('span.webui-version', 'v87'), + m( + '.logo-container', + { + style: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + marginRight: '10px', + }, + }, + [ + m('img', { + src: 'images/retroshare.svg', + alt: 'retroshare_icon', + }), + m('i.fas.fa-circle', { + style: { + color: rs.connectionState.status ? '#2ecc71' : '#e74c3c', + fontSize: '0.6em', + marginTop: '5px', + transition: 'color 0.3s ease', + }, + title: rs.connectionState.status ? 'Connected to RetroShare Core' : 'Connection Lost', + }), + m('span.webui-version', { style: { fontSize: '0.7em', marginTop: '3px', color: '#888' } }, 'v108'), m('i.fas.fa-sync-alt.refresh-icon', { + style: { fontSize: '0.8em', marginTop: '2px', cursor: 'pointer', color: '#888' }, onclick: () => window.location.reload(true), title: 'Force reload application', }), - ]), + ] + ), + m('.nav-menu__logo-text', [ + m('h5', 'RetroShare'), ]), ]), m('.nav-menu__box', [ diff --git a/webui-src/app/rswebui.js b/webui-src/app/rswebui.js index 3df939a..5d735b8 100644 --- a/webui-src/app/rswebui.js +++ b/webui-src/app/rswebui.js @@ -96,6 +96,10 @@ function logout() { m.route.set('/'); } +const connectionState = { + status: true, +}; + function rsJsonApiRequest( path, data = {}, @@ -137,12 +141,14 @@ function rsJsonApiRequest( }) .then((result) => { if (result.status === 200) { + connectionState.status = true; try { callback(result.body, true); } catch (e) { console.error('[RS] Error in success callback for path:', path, e); } } else { + connectionState.status = false; if (result.status === 401) { setKeys(loginKey.username, loginKey.passwd, loginKey.url, false); m.route.set('/'); @@ -156,6 +162,7 @@ function rsJsonApiRequest( return result; }) .catch(function (e) { + connectionState.status = false; try { callback(e, false); } catch (cbErr) { @@ -204,17 +211,10 @@ const eventQueue = { // #define RS_CHAT_TYPE_PUBLIC 1 // #define RS_CHAT_TYPE_PRIVATE 2 - 1: (chatId) => { - const id = chatId.peer_id; - if (typeof id === 'object') return id.xstr64 !== '0' ? id.xstr64 : JSON.stringify(id); - return id; - }, // RS_CHAT_TYPE_PRIVATE - 2: (chatId) => { - const id = chatId.distant_chat_id; - if (typeof id === 'object') return id.xstr64 !== '0' ? id.xstr64 : JSON.stringify(id); - return id; - }, // RS_CHAT_TYPE_DISTANT - 3: (chatId) => (typeof chatId.lobby_id === 'object' ? chatId.lobby_id.xstr64 : chatId.lobby_id), // RS_CHAT_TYPE_LOBBY + 1: (cid) => hexId(cid), + 2: (cid) => hexId(cid), + 3: (cid) => hexId(cid), + 4: (cid) => hexId(cid), }, messages: {}, chatMessages: (chatId, owner, action) => { @@ -514,8 +514,32 @@ function formatBytes(bytes, decimals = 2) { return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } +function hexId(id) { + if (!id) return ''; + if (typeof id === 'string') return id; + if (typeof id === 'number') return String(id); + if (typeof id === 'object') { + // 1. Check for xstr64 (64-bit wrapped ID) + if (id.xstr64 && id.xstr64 !== '0') return id.xstr64; + + // 2. Search for any hex string of appropriate length (128-bit or 64-bit) + const keys = Object.keys(id); + for (let i = 0; i < keys.length; i++) { + const val = id[keys[i]]; + if (typeof val === 'string' && val.length >= 16 && val !== '00000000000000000000000000000000') return val; + // Search deeper for nested xstr64 + if (val && typeof val === 'object' && val.xstr64 && val.xstr64 !== '0') return val.xstr64; + } + // 3. Last resort fallbacks + if (id.xstr64 !== undefined) return String(id.xstr64); + } + return String(id); +} + module.exports = { rsJsonApiRequest, + idToHex: hexId, + connectionState, setKeys, setBackgroundTask, logon,