various small UI improvements

This commit is contained in:
jolavillette 2026-02-19 15:50:17 +01:00
parent 5438bba025
commit b227cde173
5 changed files with 167 additions and 91 deletions

View File

@ -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) });
}
),
},

View File

@ -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],
})
),
]),
],
};

View File

@ -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' && [

View File

@ -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', [

View File

@ -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,