webui v161: chat room participants reachable on a phone

Below 900px the participants column of a room was simply display: none,
and the only way to a participant's menu -- Start private chat, Send
Message, mute, opinions -- was a right click, which a phone does not have.
The Details tab lists the room's known identities, but from the static
getChatLobbyInfo answer and without the menu.

Add a Participants button to the room header, shown below 900px only,
that opens the same column as a sheet over the messages, with a close
button in its title. Where the sheet applies, or on a device without
hover, a tap on a participant opens its menu; the right click keeps
working elsewhere. The sheet closes when a menu item leaves the page and
whenever another room is loaded.
This commit is contained in:
jolavillette 2026-08-29 12:17:34 +02:00
parent c2c2312dfb
commit e9d9db3021
5 changed files with 117 additions and 25 deletions

View File

@ -315,6 +315,22 @@ const ChatRoomHeader = () => {
)
]
: [
// Below 900px the participants column is not laid out; this
// opens it as a sheet. Desktop hides the button (see
// pages/_chat.scss), the column being always visible there.
m(
'button.participants-toggle',
{
title: 'Participants',
style: 'margin-right: 0.75rem;',
onclick: () => {
ChatHubState.showParticipants = !ChatHubState.showParticipants;
ChatHubState.activeMenu = null;
ChatHubState.hoveredUser = null;
}
},
[m('i.fas.fa-users'), ' ' + ChatLobbyModel.users.length]
),
m(
'button',
{
@ -395,7 +411,7 @@ const ChatConversationView = () => {
const isRoom = chatType === 3;
const isDistant = chatType === 2;
const canTalk = !isDistant || (ChatLobbyModel.distantChatStatus && ChatLobbyModel.distantChatStatus.status === 2);
return m('.chat-hub-conversation-layout', [
return m('.chat-hub-conversation-layout' + (ChatHubState.showParticipants ? '.show-participants' : ''), [
m('.chat-hub-conversation-main', [
m(
'.chat-hub-messages' + (isRoom ? '.compact-container' : ''),
@ -692,7 +708,18 @@ const ChatConversationView = () => {
m(HistoryBrowserModal, { isRoom: true }),
]),
m('.chat-hub-rightbar', [
m('.rightbar-title', 'Participants'),
m('.rightbar-title', [
'Participants',
m('button.rightbar-close', {
type: 'button',
title: 'Close',
'aria-label': 'Close participants',
onclick: () => {
ChatHubState.showParticipants = false;
ChatHubState.activeMenu = null;
},
}, m('i.fas.fa-times')),
]),
m('.rightbar-users-list', (() => {
const sortedUsers = [...ChatLobbyModel.users];
if (ChatHubState.userSortMethod === 'activity') {
@ -744,25 +771,7 @@ const ChatConversationView = () => {
statusTooltip = 'Away';
}
return m('.user', {
onmouseenter: (e) => {
if (ChatHubState.activeMenu) return;
const rect = e.currentTarget.getBoundingClientRect();
ChatHubState.hoveredUser = { gxsId, name, rect };
},
onmouseleave: () => {
ChatHubState.hoveredUser = null;
},
onclick: (e) => {
e.preventDefault();
e.stopPropagation();
ChatHubState.hoveredUser = null;
ChatHubState.activeMenu = null;
m.redraw();
},
oncontextmenu: (e) => {
e.preventDefault();
e.stopPropagation();
const openUserMenu = (e) => {
ChatHubState.hoveredUser = null;
const rect = e.currentTarget.getBoundingClientRect();
@ -779,7 +788,36 @@ const ChatConversationView = () => {
ChatHubState.activeMenu = { gxsId, name, top };
m.redraw();
}
}
};
return m('.user', {
onmouseenter: (e) => {
if (ChatHubState.activeMenu) return;
const rect = e.currentTarget.getBoundingClientRect();
ChatHubState.hoveredUser = { gxsId, name, rect };
},
onmouseleave: () => {
ChatHubState.hoveredUser = null;
},
onclick: (e) => {
e.preventDefault();
e.stopPropagation();
ChatHubState.hoveredUser = null;
// A phone has no right click: a tap on a participant is the
// only way to reach "Start private chat" and the rest of
// the menu. Same media query as the sheet in _chat.scss.
if (window.matchMedia('(max-width: 899px), (hover: none)').matches) {
openUserMenu(e);
return;
}
ChatHubState.activeMenu = null;
m.redraw();
},
oncontextmenu: (e) => {
e.preventDefault();
e.stopPropagation();
openUserMenu(e);
},
}, [
m(peopleUtil.UserAvatar, { avatar, firstLetter, identityId: gxsId, size: 32 }),
m('span.user-name', name),
@ -899,6 +937,7 @@ const ChatConversationView = () => {
!isOwn && m('.menu-item', {
onclick: () => {
ChatHubState.activeMenu = null;
ChatHubState.showParticipants = false;
people.setSelectedId(menu.gxsId, 'chat');
}
}, [
@ -908,6 +947,7 @@ const ChatConversationView = () => {
!isOwn && m('.menu-item', {
onclick: () => {
ChatHubState.activeMenu = null;
ChatHubState.showParticipants = false;
people.setSelectedId(menu.gxsId, 'details', true);
}
}, [

View File

@ -1078,6 +1078,7 @@ const ChatLobbyModel = {
this.stopParticipantPolling();
this.lastLobbyId = currentlobbyid;
ChatRoomsModel.unreadCount[currentlobbyid] = 0;
ChatHubState.showParticipants = false;
const finishLoad = (detail) => {
this.setupAction = this.setIdentity;
@ -1211,6 +1212,8 @@ const ChatHubState = {
hoveredUser: null,
mutedUsers: new Set(),
activeMenu: null,
// Phone only: the participants column is shown as a sheet over the messages.
showParticipants: false,
showAttachModal: false,
attachPath: '',
attachBrowseHint: false,

View File

@ -137,7 +137,7 @@ const navbar = () => {
? 'Connected to RetroShare Core'
: 'Connection Lost',
}),
m('span.webui-version', { style: { fontSize: '0.7em' } }, 'v160'),
m('span.webui-version', { style: { fontSize: '0.7em' } }, 'v161'),
m('i.fas.fa-sync-alt.refresh-icon', {
style: { cursor: 'pointer', fontSize: '0.8em' },
onclick: () => window.location.reload(true),
@ -260,7 +260,7 @@ const MobileStatus = () => {
m('small', statusbar.formatBytes(state.totalOut)),
]),
]),
m('.mobile-status-sheet__version', 'WebUI v160'),
m('.mobile-status-sheet__version', 'WebUI v161'),
])),
];
},

View File

@ -1112,10 +1112,55 @@ textarea.chatMsg {
flex-shrink: 0;
}
/* Below 900px the participants column is not laid out: the room header's
* .participants-toggle opens it as a sheet over the messages instead, and
* a tap on a participant opens its menu (chat.js). Above, the column is
* always there, so the toggle and the sheet's close button are hidden. */
.chat-hub-rightbar .rightbar-title {
display: flex;
align-items: center;
justify-content: space-between;
}
.chat-hub-rightbar .rightbar-close {
display: none;
border: none;
background: transparent;
color: #64748b;
font-size: 1rem;
padding: 0.25rem 0.5rem;
cursor: pointer;
}
@media (min-width: 900px) {
.chat-hub-header-bar .chat-header-actions .participants-toggle {
display: none;
}
}
@media (max-width: 899px) {
.chat-hub-conversation-layout {
position: relative;
}
.chat-hub-rightbar {
display: none;
}
.chat-hub-conversation-layout.show-participants .chat-hub-rightbar {
display: flex;
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: min(80vw, 320px);
z-index: 60;
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.15);
}
.chat-hub-conversation-layout.show-participants .rightbar-close {
display: inline-flex;
}
}
.chat-hub-messages {
@ -2238,6 +2283,10 @@ label.mobile-chat-attachment__option:hover {
position: absolute !important;
right: 10px !important;
min-width: 220px !important;
/* Above its own .menu-backdrop (position: fixed, z-index 9998), which
* otherwise sits over the menu inside the phone participants sheet --
* the sheet's z-index makes a stacking context of its own. */
z-index: 9999 !important;
}
.chat-msg-context-menu {

File diff suppressed because one or more lines are too long