mirror of
https://github.com/RetroShare/RSNewWebUI.git
synced 2026-09-12 19:50:04 +05:00
Added vote feature for chat parcipants
Added statusbar
This commit is contained in:
parent
2053a29867
commit
9cb3b9bb4c
@ -247,6 +247,15 @@ const Message = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMuted = ChatHubState.mutedUsers && ChatHubState.mutedUsers.has(gxsId);
|
||||||
|
const details = ChatHubState.gxsDetails[gxsId];
|
||||||
|
const opinion = details && details.mReputation ? details.mReputation.mOwnOpinion : 1;
|
||||||
|
const isBanned = opinion === 0;
|
||||||
|
|
||||||
|
if (isMuted || isBanned) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let username = rs.userList.username(gxsId) || msg.peerName || '???';
|
let username = rs.userList.username(gxsId) || msg.peerName || '???';
|
||||||
// If we only have the hex ID, try to fallback to the peerName from the message
|
// If we only have the hex ID, try to fallback to the peerName from the message
|
||||||
if (username === gxsId && msg.peerName) {
|
if (username === gxsId && msg.peerName) {
|
||||||
@ -695,8 +704,10 @@ const ChatHubState = {
|
|||||||
newRoomTopic: '',
|
newRoomTopic: '',
|
||||||
newRoomIdentity: '',
|
newRoomIdentity: '',
|
||||||
newRoomPublic: true,
|
newRoomPublic: true,
|
||||||
|
newRoomSigned: false,
|
||||||
ownGxsIdentities: [],
|
ownGxsIdentities: [],
|
||||||
createRoomError: '',
|
createRoomError: '',
|
||||||
|
userSortMethod: 'name',
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========================= Emoji Data =========================
|
// ========================= Emoji Data =========================
|
||||||
@ -1240,109 +1251,160 @@ const ChatConversationView = () => {
|
|||||||
]),
|
]),
|
||||||
m('.chat-hub-rightbar', [
|
m('.chat-hub-rightbar', [
|
||||||
m('.rightbar-title', 'Participants'),
|
m('.rightbar-title', 'Participants'),
|
||||||
m('.rightbar-users-list', ChatLobbyModel.users.map((user) => {
|
m('.rightbar-users-list', (() => {
|
||||||
const gxsId = user.key;
|
const sortedUsers = [...ChatLobbyModel.users];
|
||||||
const name = user.name;
|
if (ChatHubState.userSortMethod === 'activity') {
|
||||||
|
sortedUsers.sort((a, b) => b.lastAct - a.lastAct);
|
||||||
// Load details for avatar if not cached
|
} else {
|
||||||
if (gxsId && ChatHubState.gxsDetails[gxsId] === undefined) {
|
sortedUsers.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
ChatHubState.gxsDetails[gxsId] = null; // Mark as loading
|
|
||||||
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: gxsId }, (data) => {
|
|
||||||
if (data && data.details) {
|
|
||||||
ChatHubState.gxsDetails[gxsId] = data.details;
|
|
||||||
m.redraw();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
return sortedUsers.map((user) => {
|
||||||
|
const gxsId = user.key;
|
||||||
|
const name = user.name;
|
||||||
|
|
||||||
const details = ChatHubState.gxsDetails[gxsId];
|
// Load details for avatar if not cached
|
||||||
const avatar = getSafeAvatar(details);
|
if (gxsId && ChatHubState.gxsDetails[gxsId] === undefined) {
|
||||||
const firstLetter = (name || '?').slice(0, 1).toUpperCase();
|
ChatHubState.gxsDetails[gxsId] = null; // Mark as loading
|
||||||
|
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: gxsId }, (data) => {
|
||||||
// Calculate status color and tooltip
|
if (data && data.details) {
|
||||||
const now = Math.floor(Date.now() / 1000);
|
ChatHubState.gxsDetails[gxsId] = data.details;
|
||||||
const tLastAct = user.lastAct || 0;
|
m.redraw();
|
||||||
const isOwn = gxsId === rs.idToHex(ChatLobbyModel.currentLobby.gxs_id || '');
|
|
||||||
const isMuted = ChatHubState.mutedUsers && ChatHubState.mutedUsers.has(gxsId);
|
|
||||||
|
|
||||||
let statusColor = '#22c55e'; // active (green)
|
|
||||||
let statusTooltip = 'Active';
|
|
||||||
|
|
||||||
if (isMuted) {
|
|
||||||
statusColor = '#ef4444'; // muted (red)
|
|
||||||
statusTooltip = 'Muted';
|
|
||||||
} else if (isOwn) {
|
|
||||||
statusColor = '#3ba4d7'; // own identity (blue)
|
|
||||||
statusTooltip = 'You';
|
|
||||||
} else if (tLastAct + 600 < now) {
|
|
||||||
statusColor = '#cbd5e1'; // inactive > 10 mins (grey)
|
|
||||||
statusTooltip = 'Inactive';
|
|
||||||
} else if (tLastAct + 300 < now) {
|
|
||||||
statusColor = '#eab308'; // away > 5 mins (yellow)
|
|
||||||
statusTooltip = 'Away';
|
|
||||||
}
|
|
||||||
|
|
||||||
return m('.user', {
|
|
||||||
onmouseenter: (e) => {
|
|
||||||
if (ChatHubState.activeMenu) return; // skip tooltip if menu is open
|
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
|
||||||
const rightbar = document.querySelector('.chat-hub-rightbar');
|
|
||||||
if (rightbar) {
|
|
||||||
const parentRect = rightbar.getBoundingClientRect();
|
|
||||||
const top = rect.top - parentRect.top + rect.height / 2;
|
|
||||||
ChatHubState.hoveredUser = { gxsId, name, top };
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onmouseleave: () => {
|
|
||||||
ChatHubState.hoveredUser = null;
|
|
||||||
},
|
|
||||||
onclick: (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
ChatHubState.hoveredUser = null; // hide tooltip
|
|
||||||
|
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
|
||||||
const rightbar = document.querySelector('.chat-hub-rightbar');
|
|
||||||
if (rightbar) {
|
|
||||||
const parentRect = rightbar.getBoundingClientRect();
|
|
||||||
const top = rect.bottom - parentRect.top;
|
|
||||||
if (ChatHubState.activeMenu && ChatHubState.activeMenu.gxsId === gxsId) {
|
|
||||||
ChatHubState.activeMenu = null;
|
|
||||||
} else {
|
|
||||||
ChatHubState.activeMenu = { gxsId, name, top };
|
|
||||||
}
|
}
|
||||||
m.redraw();
|
});
|
||||||
}
|
|
||||||
},
|
|
||||||
oncontextmenu: (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
ChatHubState.hoveredUser = null; // hide tooltip
|
|
||||||
|
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
|
||||||
const rightbar = document.querySelector('.chat-hub-rightbar');
|
|
||||||
if (rightbar) {
|
|
||||||
const parentRect = rightbar.getBoundingClientRect();
|
|
||||||
const top = rect.bottom - parentRect.top;
|
|
||||||
ChatHubState.activeMenu = { gxsId, name, top };
|
|
||||||
m.redraw();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [
|
|
||||||
m(peopleUtil.UserAvatar, { avatar, firstLetter, identityId: gxsId, size: 32 }),
|
const details = ChatHubState.gxsDetails[gxsId];
|
||||||
m('span.user-name', name),
|
const avatar = getSafeAvatar(details);
|
||||||
statusColor !== '#22c55e' && m('i.fas.fa-circle', {
|
const firstLetter = (name || '?').slice(0, 1).toUpperCase();
|
||||||
style: {
|
|
||||||
color: statusColor,
|
const opinion = details && details.mReputation ? details.mReputation.mOwnOpinion : 1;
|
||||||
fontSize: '0.65rem',
|
const isBanned = opinion === 0;
|
||||||
marginLeft: 'auto',
|
if (isBanned) return null;
|
||||||
flexShrink: 0,
|
|
||||||
transition: 'color 0.3s ease',
|
// Calculate status color and tooltip
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const tLastAct = user.lastAct || 0;
|
||||||
|
const isOwn = gxsId === rs.idToHex(ChatLobbyModel.currentLobby.gxs_id || '');
|
||||||
|
const isMuted = ChatHubState.mutedUsers && ChatHubState.mutedUsers.has(gxsId);
|
||||||
|
|
||||||
|
let statusColor = '#22c55e'; // active (green)
|
||||||
|
let statusTooltip = 'Active';
|
||||||
|
|
||||||
|
if (isMuted) {
|
||||||
|
statusColor = '#ef4444'; // muted (red)
|
||||||
|
statusTooltip = 'Muted';
|
||||||
|
} else if (isOwn) {
|
||||||
|
statusColor = '#3ba4d7'; // own identity (blue)
|
||||||
|
statusTooltip = 'You';
|
||||||
|
} else if (tLastAct + 600 < now) {
|
||||||
|
statusColor = '#cbd5e1'; // inactive > 10 mins (grey)
|
||||||
|
statusTooltip = 'Inactive';
|
||||||
|
} else if (tLastAct + 300 < now) {
|
||||||
|
statusColor = '#eab308'; // away > 5 mins (yellow)
|
||||||
|
statusTooltip = 'Away';
|
||||||
|
}
|
||||||
|
|
||||||
|
return m('.user', {
|
||||||
|
onmouseenter: (e) => {
|
||||||
|
if (ChatHubState.activeMenu) return; // skip tooltip if menu is open
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
const rightbar = document.querySelector('.chat-hub-rightbar');
|
||||||
|
if (rightbar) {
|
||||||
|
const parentRect = rightbar.getBoundingClientRect();
|
||||||
|
const top = rect.top - parentRect.top + rect.height / 2;
|
||||||
|
ChatHubState.hoveredUser = { gxsId, name, top };
|
||||||
|
}
|
||||||
},
|
},
|
||||||
title: statusTooltip
|
onmouseleave: () => {
|
||||||
})
|
ChatHubState.hoveredUser = null;
|
||||||
]);
|
},
|
||||||
})),
|
onclick: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
ChatHubState.hoveredUser = null; // hide tooltip
|
||||||
|
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
const rightbar = document.querySelector('.chat-hub-rightbar');
|
||||||
|
if (rightbar) {
|
||||||
|
const parentRect = rightbar.getBoundingClientRect();
|
||||||
|
const itemBottom = rect.bottom - parentRect.top;
|
||||||
|
const estimatedMenuHeight = 310;
|
||||||
|
let top = itemBottom;
|
||||||
|
if (itemBottom + estimatedMenuHeight > parentRect.height) {
|
||||||
|
top = rect.top - parentRect.top - estimatedMenuHeight;
|
||||||
|
if (top < 10) top = 10;
|
||||||
|
}
|
||||||
|
if (ChatHubState.activeMenu && ChatHubState.activeMenu.gxsId === gxsId) {
|
||||||
|
ChatHubState.activeMenu = null;
|
||||||
|
} else {
|
||||||
|
ChatHubState.activeMenu = { gxsId, name, top };
|
||||||
|
}
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
oncontextmenu: (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
ChatHubState.hoveredUser = null; // hide tooltip
|
||||||
|
|
||||||
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
|
const rightbar = document.querySelector('.chat-hub-rightbar');
|
||||||
|
if (rightbar) {
|
||||||
|
const parentRect = rightbar.getBoundingClientRect();
|
||||||
|
const itemBottom = rect.bottom - parentRect.top;
|
||||||
|
const estimatedMenuHeight = 310;
|
||||||
|
let top = itemBottom;
|
||||||
|
if (itemBottom + estimatedMenuHeight > parentRect.height) {
|
||||||
|
top = rect.top - parentRect.top - estimatedMenuHeight;
|
||||||
|
if (top < 10) top = 10;
|
||||||
|
}
|
||||||
|
ChatHubState.activeMenu = { gxsId, name, top };
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
m(peopleUtil.UserAvatar, { avatar, firstLetter, identityId: gxsId, size: 32 }),
|
||||||
|
m('span.user-name', name),
|
||||||
|
(() => {
|
||||||
|
if (isBanned) {
|
||||||
|
return m('i.fas.fa-ban', {
|
||||||
|
style: {
|
||||||
|
color: '#ef4444',
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
title: 'Banned'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isMuted) {
|
||||||
|
return m('i.fas.fa-volume-mute', {
|
||||||
|
style: {
|
||||||
|
color: '#ef4444',
|
||||||
|
fontSize: '0.85rem',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
title: 'Muted'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (statusColor !== '#22c55e') {
|
||||||
|
return m('i.fas.fa-circle', {
|
||||||
|
style: {
|
||||||
|
color: statusColor,
|
||||||
|
fontSize: '0.65rem',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
flexShrink: 0,
|
||||||
|
transition: 'color 0.3s ease',
|
||||||
|
},
|
||||||
|
title: statusTooltip
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})()
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
})()),
|
||||||
ChatHubState.hoveredUser && (() => {
|
ChatHubState.hoveredUser && (() => {
|
||||||
const hUser = ChatHubState.hoveredUser;
|
const hUser = ChatHubState.hoveredUser;
|
||||||
const details = ChatHubState.gxsDetails[hUser.gxsId];
|
const details = ChatHubState.gxsDetails[hUser.gxsId];
|
||||||
@ -1392,6 +1454,64 @@ const ChatConversationView = () => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
|
m('.menu-item', {
|
||||||
|
onclick: () => {
|
||||||
|
ChatHubState.userSortMethod = 'activity';
|
||||||
|
ChatHubState.activeMenu = null;
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
m('i.fas.fa-circle', {
|
||||||
|
style: {
|
||||||
|
color: '#000',
|
||||||
|
marginRight: '0.5rem',
|
||||||
|
fontSize: '0.4rem',
|
||||||
|
width: '18px',
|
||||||
|
textAlign: 'center',
|
||||||
|
visibility: ChatHubState.userSortMethod === 'activity' ? 'visible' : 'hidden'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'Sort by Activity'
|
||||||
|
]),
|
||||||
|
m('.menu-item', {
|
||||||
|
onclick: () => {
|
||||||
|
ChatHubState.userSortMethod = 'name';
|
||||||
|
ChatHubState.activeMenu = null;
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
m('i.fas.fa-circle', {
|
||||||
|
style: {
|
||||||
|
color: '#000',
|
||||||
|
marginRight: '0.5rem',
|
||||||
|
fontSize: '0.4rem',
|
||||||
|
width: '18px',
|
||||||
|
textAlign: 'center',
|
||||||
|
visibility: ChatHubState.userSortMethod === 'name' ? 'visible' : 'hidden'
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'Sort by Name'
|
||||||
|
]),
|
||||||
|
m('hr', { style: 'margin: 0.25rem 0; border: none; border-top: 1px solid #e2e8f0;' }),
|
||||||
|
!isOwn && m('.menu-item', {
|
||||||
|
onclick: () => {
|
||||||
|
ChatHubState.activeMenu = null;
|
||||||
|
people.setSelectedId(menu.gxsId, 'chat');
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
m('i.fas.fa-comments', { style: 'color: #3b82f6; margin-right: 0.5rem; width: 18px; text-align: center;' }),
|
||||||
|
'Start private chat'
|
||||||
|
]),
|
||||||
|
!isOwn && m('.menu-item', {
|
||||||
|
onclick: () => {
|
||||||
|
ChatHubState.activeMenu = null;
|
||||||
|
people.setSelectedId(menu.gxsId, 'details', true);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
m('i.fas.fa-envelope', { style: 'color: #10b981; margin-right: 0.5rem; width: 18px; text-align: center;' }),
|
||||||
|
'Send Message'
|
||||||
|
]),
|
||||||
|
!isOwn && m('hr', { style: 'margin: 0.25rem 0; border: none; border-top: 1px solid #e2e8f0;' }),
|
||||||
!isOwn && m('.menu-item', {
|
!isOwn && m('.menu-item', {
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
if (isMuted) {
|
if (isMuted) {
|
||||||
@ -1403,26 +1523,83 @@ const ChatConversationView = () => {
|
|||||||
m.redraw();
|
m.redraw();
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
m('i.fas.fa-volume-mute', { style: 'color: #ef4444; margin-right: 0.5rem;' }),
|
m('i', {
|
||||||
|
class: isMuted ? 'fas fa-volume-up' : 'fas fa-volume-mute',
|
||||||
|
style: {
|
||||||
|
color: isMuted ? '#22c55e' : '#ef4444',
|
||||||
|
marginRight: '0.5rem',
|
||||||
|
fontSize: '0.95rem',
|
||||||
|
width: '18px',
|
||||||
|
textAlign: 'center'
|
||||||
|
}
|
||||||
|
}),
|
||||||
isMuted ? 'Unmute participant' : 'Mute participant'
|
isMuted ? 'Unmute participant' : 'Mute participant'
|
||||||
]),
|
]),
|
||||||
!isOwn && m('.menu-item', {
|
!isOwn && m('.menu-item', {
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
ChatHubState.activeMenu = null;
|
ChatHubState.activeMenu = null;
|
||||||
people.setSelectedId(menu.gxsId, 'chat');
|
rs.rsJsonApiRequest('/rsreputations/setOwnOpinion', { id: menu.gxsId, opinion: 2 }, (data, success) => {
|
||||||
|
if (success) {
|
||||||
|
if (!ChatHubState.gxsDetails[menu.gxsId]) ChatHubState.gxsDetails[menu.gxsId] = { mReputation: {} };
|
||||||
|
if (!ChatHubState.gxsDetails[menu.gxsId].mReputation) ChatHubState.gxsDetails[menu.gxsId].mReputation = {};
|
||||||
|
ChatHubState.gxsDetails[menu.gxsId].mReputation.mOwnOpinion = 2;
|
||||||
|
m.redraw();
|
||||||
|
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: menu.gxsId }, (d) => {
|
||||||
|
if (d && d.details) {
|
||||||
|
ChatHubState.gxsDetails[menu.gxsId] = d.details;
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
m('i.fas.fa-comments', { style: 'color: #3b82f6; margin-right: 0.5rem;' }),
|
m('span', { style: 'background-color: #22c55e; border-radius: 50%; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-right: 0.5rem; font-size: 0.7rem; color: #ffffff;' }, m('i.fas.fa-thumbs-up')),
|
||||||
'Start private chat'
|
'Give positive opinion'
|
||||||
]),
|
]),
|
||||||
!isOwn && m('.menu-item', {
|
!isOwn && m('.menu-item', {
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
ChatHubState.activeMenu = null;
|
ChatHubState.activeMenu = null;
|
||||||
people.setSelectedId(menu.gxsId, 'details', true);
|
rs.rsJsonApiRequest('/rsreputations/setOwnOpinion', { id: menu.gxsId, opinion: 1 }, (data, success) => {
|
||||||
|
if (success) {
|
||||||
|
if (!ChatHubState.gxsDetails[menu.gxsId]) ChatHubState.gxsDetails[menu.gxsId] = { mReputation: {} };
|
||||||
|
if (!ChatHubState.gxsDetails[menu.gxsId].mReputation) ChatHubState.gxsDetails[menu.gxsId].mReputation = {};
|
||||||
|
ChatHubState.gxsDetails[menu.gxsId].mReputation.mOwnOpinion = 1;
|
||||||
|
m.redraw();
|
||||||
|
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: menu.gxsId }, (d) => {
|
||||||
|
if (d && d.details) {
|
||||||
|
ChatHubState.gxsDetails[menu.gxsId] = d.details;
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
m('i.fas.fa-envelope', { style: 'color: #10b981; margin-right: 0.5rem;' }),
|
m('span', { style: 'background-color: #f59e0b; border-radius: 50%; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-right: 0.5rem; font-size: 0.7rem; color: #ffffff;' }, m('i.fas.fa-hand-paper')),
|
||||||
'Send Message'
|
'Give neutral opinion'
|
||||||
|
]),
|
||||||
|
!isOwn && m('.menu-item', {
|
||||||
|
onclick: () => {
|
||||||
|
ChatHubState.activeMenu = null;
|
||||||
|
rs.rsJsonApiRequest('/rsreputations/setOwnOpinion', { id: menu.gxsId, opinion: 0 }, (data, success) => {
|
||||||
|
if (success) {
|
||||||
|
if (!ChatHubState.gxsDetails[menu.gxsId]) ChatHubState.gxsDetails[menu.gxsId] = { mReputation: {} };
|
||||||
|
if (!ChatHubState.gxsDetails[menu.gxsId].mReputation) ChatHubState.gxsDetails[menu.gxsId].mReputation = {};
|
||||||
|
ChatHubState.gxsDetails[menu.gxsId].mReputation.mOwnOpinion = 0;
|
||||||
|
m.redraw();
|
||||||
|
rs.rsJsonApiRequest('/rsIdentity/getIdDetails', { id: menu.gxsId }, (d) => {
|
||||||
|
if (d && d.details) {
|
||||||
|
ChatHubState.gxsDetails[menu.gxsId] = d.details;
|
||||||
|
m.redraw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
m('span', { style: 'background-color: #ef4444; border-radius: 50%; width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; margin-right: 0.5rem; font-size: 0.7rem; color: #ffffff;' }, m('i.fas.fa-thumbs-down')),
|
||||||
|
'Ban this person (Sets negative opinion)'
|
||||||
]),
|
]),
|
||||||
m('.menu-item', {
|
m('.menu-item', {
|
||||||
onclick: () => {
|
onclick: () => {
|
||||||
@ -1430,7 +1607,7 @@ const ChatConversationView = () => {
|
|||||||
people.setSelectedId(menu.gxsId, 'details');
|
people.setSelectedId(menu.gxsId, 'details');
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
m('i.fas.fa-user', { style: 'color: #8b5cf6; margin-right: 0.5rem;' }),
|
m('i.fas.fa-user', { style: 'color: #8b5cf6; margin-right: 0.5rem; width: 18px; text-align: center;' }),
|
||||||
'Show author in people tab'
|
'Show author in people tab'
|
||||||
])
|
])
|
||||||
]);
|
]);
|
||||||
@ -1803,11 +1980,23 @@ const Layout = {
|
|||||||
]),
|
]),
|
||||||
|
|
||||||
m('.form-field', { style: 'display: flex; gap: 0.5rem; align-items: center; margin-top: 0.75rem;' }, [
|
m('.form-field', { style: 'display: flex; gap: 0.5rem; align-items: center; margin-top: 0.75rem;' }, [
|
||||||
m('input[type=checkbox]', {
|
m('label', { style: 'display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; color: #475569; cursor: pointer; user-select: none;' }, [
|
||||||
checked: ChatHubState.newRoomPublic,
|
m('input[type=checkbox]', {
|
||||||
onclick: (e) => { ChatHubState.newRoomPublic = e.target.checked; }
|
checked: ChatHubState.newRoomPublic,
|
||||||
}),
|
onclick: (e) => { ChatHubState.newRoomPublic = e.target.checked; }
|
||||||
m('label', { style: 'font-size: 0.9rem; color: #475569;' }, 'Public Room')
|
}),
|
||||||
|
'Public Room'
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
|
||||||
|
m('.form-field', { style: 'display: flex; gap: 0.5rem; align-items: center; margin-top: 0.5rem;' }, [
|
||||||
|
m('label', { style: 'display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; color: #475569; cursor: pointer; user-select: none;' }, [
|
||||||
|
m('input[type=checkbox]', {
|
||||||
|
checked: ChatHubState.newRoomSigned,
|
||||||
|
onclick: (e) => { ChatHubState.newRoomSigned = e.target.checked; }
|
||||||
|
}),
|
||||||
|
'PGP signed identities'
|
||||||
|
])
|
||||||
]),
|
]),
|
||||||
|
|
||||||
ChatHubState.createRoomError && m('p.error-text', { style: 'color: #ef4444; font-size: 0.85rem; margin: 0.5rem 0 0 0;' }, ChatHubState.createRoomError),
|
ChatHubState.createRoomError && m('p.error-text', { style: 'color: #ef4444; font-size: 0.85rem; margin: 0.5rem 0 0 0;' }, ChatHubState.createRoomError),
|
||||||
@ -1820,7 +2009,10 @@ const Layout = {
|
|||||||
const topic = ChatHubState.newRoomTopic.trim();
|
const topic = ChatHubState.newRoomTopic.trim();
|
||||||
const identity = ChatHubState.newRoomIdentity;
|
const identity = ChatHubState.newRoomIdentity;
|
||||||
const isPublic = ChatHubState.newRoomPublic;
|
const isPublic = ChatHubState.newRoomPublic;
|
||||||
const flags = isPublic ? 4 : 0; // RS_CHAT_LOBBY_FLAGS_PUBLIC
|
const isSigned = ChatHubState.newRoomSigned;
|
||||||
|
let flags = 0;
|
||||||
|
if (isPublic) flags |= 4; // RS_CHAT_LOBBY_FLAGS_PUBLIC
|
||||||
|
if (isSigned) flags |= 8; // RS_CHAT_LOBBY_FLAGS_SIGNED_ONLY
|
||||||
|
|
||||||
rs.rsJsonApiRequest('/rsChats/createChatLobby', {
|
rs.rsJsonApiRequest('/rsChats/createChatLobby', {
|
||||||
lobby_name: name,
|
lobby_name: name,
|
||||||
@ -1833,6 +2025,7 @@ const Layout = {
|
|||||||
ChatHubState.showCreateRoomModal = false;
|
ChatHubState.showCreateRoomModal = false;
|
||||||
ChatHubState.newRoomName = '';
|
ChatHubState.newRoomName = '';
|
||||||
ChatHubState.newRoomTopic = '';
|
ChatHubState.newRoomTopic = '';
|
||||||
|
ChatHubState.newRoomSigned = false;
|
||||||
ChatHubState.createRoomError = '';
|
ChatHubState.createRoomError = '';
|
||||||
// Refresh rooms list
|
// Refresh rooms list
|
||||||
ChatRoomsModel.loadSubscribedRooms();
|
ChatRoomsModel.loadSubscribedRooms();
|
||||||
@ -1849,6 +2042,7 @@ const Layout = {
|
|||||||
ChatHubState.showCreateRoomModal = false;
|
ChatHubState.showCreateRoomModal = false;
|
||||||
ChatHubState.newRoomName = '';
|
ChatHubState.newRoomName = '';
|
||||||
ChatHubState.newRoomTopic = '';
|
ChatHubState.newRoomTopic = '';
|
||||||
|
ChatHubState.newRoomSigned = false;
|
||||||
ChatHubState.createRoomError = '';
|
ChatHubState.createRoomError = '';
|
||||||
}
|
}
|
||||||
}, 'Cancel')
|
}, 'Cancel')
|
||||||
|
|||||||
@ -12,6 +12,7 @@ const channels = require('channels/channels');
|
|||||||
const forums = require('forums/forums');
|
const forums = require('forums/forums');
|
||||||
const boards = require('boards/boards');
|
const boards = require('boards/boards');
|
||||||
const config = require('config/config_resolver');
|
const config = require('config/config_resolver');
|
||||||
|
const statusbar = require('statusbar');
|
||||||
|
|
||||||
const navIcon = {
|
const navIcon = {
|
||||||
home: m('i.fas.fa-home.sidenav-icon'),
|
home: m('i.fas.fa-home.sidenav-icon'),
|
||||||
@ -127,7 +128,10 @@ const Layout = () => {
|
|||||||
config: '/config/network',
|
config: '/config/network',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
m('.tab-content', vnode.children),
|
m('.main-container', { style: { display: 'flex', flexDirection: 'column', width: '100%', height: '100%', overflow: 'hidden' } }, [
|
||||||
|
m('.tab-content', { style: { flex: '1', overflow: 'auto' } }, vnode.children),
|
||||||
|
m(statusbar)
|
||||||
|
]),
|
||||||
]),
|
]),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
162
webui-src/app/statusbar.js
Normal file
162
webui-src/app/statusbar.js
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
const m = require('mithril');
|
||||||
|
const rs = require('rswebui');
|
||||||
|
|
||||||
|
const State = {
|
||||||
|
friendCount: 0,
|
||||||
|
onlineCount: 0,
|
||||||
|
dhtActive: false,
|
||||||
|
dhtOk: false,
|
||||||
|
dhtRsNetSize: 0,
|
||||||
|
dhtNetSize: 0,
|
||||||
|
natState: 1, // BAD_UNKNOWN
|
||||||
|
firewalled: true,
|
||||||
|
forwardPort: false,
|
||||||
|
stunOk: false,
|
||||||
|
extAddressOk: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
function formatUnit(val) {
|
||||||
|
if (!val) return '0';
|
||||||
|
if (val >= 1000000) return (val / 1000000).toFixed(1) + 'M';
|
||||||
|
if (val >= 1000) return (val / 1000).toFixed(1) + 'k';
|
||||||
|
return val.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStatus() {
|
||||||
|
if (!rs.loginKey.isVerified) return;
|
||||||
|
|
||||||
|
// 1. Friends count
|
||||||
|
rs.rsJsonApiRequest('/rsPeers/getFriendList', {}, (data) => {
|
||||||
|
if (data && data.sslIds) {
|
||||||
|
State.friendCount = data.sslIds.length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rs.rsJsonApiRequest('/rsPeers/getOnlineList', {}, (data) => {
|
||||||
|
if (data && data.sslIds) {
|
||||||
|
State.onlineCount = data.sslIds.length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Net / DHT config status
|
||||||
|
rs.rsJsonApiRequest('/rsConfig/getConfigNetStatus', {}, (data) => {
|
||||||
|
if (data && data.status) {
|
||||||
|
State.dhtActive = data.status.DHTActive;
|
||||||
|
State.dhtOk = data.status.netDhtOk;
|
||||||
|
State.dhtRsNetSize = data.status.netDhtRsNetSize;
|
||||||
|
State.dhtNetSize = data.status.netDhtNetSize;
|
||||||
|
State.firewalled = data.status.firewalled;
|
||||||
|
State.forwardPort = data.status.forwardPort;
|
||||||
|
State.stunOk = data.status.netStunOk;
|
||||||
|
State.extAddressOk = data.status.netExtAddressOk;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. NAT netState
|
||||||
|
rs.rsJsonApiRequest('/rsConfig/getNetState', {}, (data) => {
|
||||||
|
if (data && data.retval !== undefined) {
|
||||||
|
State.natState = data.retval;
|
||||||
|
} else {
|
||||||
|
// Fallback calculation based on getConfigNetStatus
|
||||||
|
if (State.firewalled && !State.forwardPort) {
|
||||||
|
State.natState = 6; // WARNING_NATTED
|
||||||
|
} else {
|
||||||
|
State.natState = 8; // GOOD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let intervalId = null;
|
||||||
|
|
||||||
|
const StatusBar = {
|
||||||
|
oninit() {
|
||||||
|
updateStatus();
|
||||||
|
intervalId = setInterval(updateStatus, 10000); // update every 10s
|
||||||
|
},
|
||||||
|
onremove() {
|
||||||
|
if (intervalId) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
view() {
|
||||||
|
// DHT Status color & tooltip
|
||||||
|
let dhtColor = '#94a3b8'; // grey (off)
|
||||||
|
let dhtTooltip = 'DHT Off';
|
||||||
|
if (State.dhtActive) {
|
||||||
|
if (State.dhtOk) {
|
||||||
|
if (State.dhtRsNetSize < 10) {
|
||||||
|
dhtColor = '#eab308'; // yellow (searching)
|
||||||
|
dhtTooltip = 'DHT Searching for RetroShare Peers';
|
||||||
|
} else {
|
||||||
|
dhtColor = '#22c55e'; // green (good)
|
||||||
|
dhtTooltip = 'DHT Good';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dhtColor = '#ef4444'; // red (error)
|
||||||
|
dhtTooltip = 'No peer found in DHT';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NAT Status color & tooltip
|
||||||
|
let natColor = '#94a3b8';
|
||||||
|
let natTooltip = 'Offline';
|
||||||
|
switch (State.natState) {
|
||||||
|
case 1: // BAD_UNKNOWN
|
||||||
|
natColor = '#eab308';
|
||||||
|
natTooltip = 'Network Status Unknown';
|
||||||
|
break;
|
||||||
|
case 2: // BAD_OFFLINE
|
||||||
|
natColor = '#94a3b8';
|
||||||
|
natTooltip = 'Offline';
|
||||||
|
break;
|
||||||
|
case 3: // BAD_NATSYM
|
||||||
|
case 4: // BAD_NODHT_NAT
|
||||||
|
natColor = '#ef4444';
|
||||||
|
natTooltip = State.natState === 4 ? 'DHT Disabled and Firewalled' : 'Nasty Firewall';
|
||||||
|
break;
|
||||||
|
case 5: // WARNING_RESTART
|
||||||
|
natColor = '#eab308';
|
||||||
|
natTooltip = 'Network Restarting';
|
||||||
|
break;
|
||||||
|
case 6: // WARNING_NATTED
|
||||||
|
natColor = '#eab308';
|
||||||
|
natTooltip = 'Behind Firewall';
|
||||||
|
break;
|
||||||
|
case 7: // WARNING_NODHT
|
||||||
|
natColor = '#eab308';
|
||||||
|
natTooltip = 'DHT Disabled';
|
||||||
|
break;
|
||||||
|
case 8: // GOOD
|
||||||
|
natColor = '#22c55e';
|
||||||
|
natTooltip = 'RetroShare Server';
|
||||||
|
break;
|
||||||
|
case 9: // ADV_FORWARD
|
||||||
|
natColor = '#22c55e';
|
||||||
|
natTooltip = 'Forwarded Port';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m('.statusbar', [
|
||||||
|
m('.statusbar-left', { style: 'display: flex; align-items: center; gap: 0.75rem;' }, [
|
||||||
|
m('.statusbar-item', [
|
||||||
|
m('i.fas.fa-users', { style: 'margin-right: 0.5rem; color: #94a3b8;' }),
|
||||||
|
m('span', `Friends: ${State.onlineCount}/${State.friendCount}`),
|
||||||
|
]),
|
||||||
|
m('.statusbar-divider'),
|
||||||
|
m('.statusbar-item', { title: natTooltip, style: 'cursor: help;' }, [
|
||||||
|
m('span', { style: 'margin-right: 0.5rem;' }, 'NAT:'),
|
||||||
|
m('.status-bullet', { style: { backgroundColor: natColor } }),
|
||||||
|
]),
|
||||||
|
m('.statusbar-divider'),
|
||||||
|
m('.statusbar-item', { title: dhtTooltip, style: 'cursor: help;' }, [
|
||||||
|
m('span', { style: 'margin-right: 0.5rem;' }, 'DHT:'),
|
||||||
|
m('.status-bullet', { style: { backgroundColor: dhtColor } }),
|
||||||
|
State.dhtActive && State.dhtOk && m('span', { style: 'margin-left: 0.5rem;' }, `${formatUnit(State.dhtRsNetSize)} (${formatUnit(State.dhtNetSize)})`),
|
||||||
|
]),
|
||||||
|
]),
|
||||||
|
m('.statusbar-right'),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = StatusBar;
|
||||||
@ -1950,3 +1950,51 @@ table.mails th.sortable-th:hover {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Status Bar Styles */
|
||||||
|
.statusbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 28px;
|
||||||
|
background-color: #14141b;
|
||||||
|
border-top: 1px solid #2e2e38;
|
||||||
|
padding: 0 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
z-index: 100;
|
||||||
|
box-sizing: border-box;
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusbar-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 14px;
|
||||||
|
background-color: #2e2e38;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-bullet {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
box-shadow: 0 0 4px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user