Redesign Chat rooms

This commit is contained in:
defnax 2026-07-05 23:49:52 +02:00
parent 320886a10d
commit 4ddbf1ee2a
3 changed files with 2262 additions and 494 deletions

View File

@ -192,7 +192,7 @@ const Message = () => {
.replaceAll('<br/>', '\n')
.replace(new RegExp('<style[^<]*</style>|<[^>]*>', 'gm'), '');
return m(
'.message',
'.message' + (msg.incoming ? '.incoming' : '.outgoing'),
m('span.datetime', datetime),
m('span.username', username),
m('span.messagetext', text)
@ -395,20 +395,23 @@ const ChatLobbyModel = {
}
};
// Lookup for chat-user names (Only for lobbies for now)
// Lookup for chat-user names
if (detail.gxs_ids) {
let names = [];
let list = [];
if (Array.isArray(detail.gxs_ids)) {
names = detail.gxs_ids.reduce((a, u) => a.concat(rs.userList.username(u.key)), []);
list = detail.gxs_ids.map((u) => {
const key = u.key;
return { key, name: rs.userList.username(key) };
});
} else if (typeof detail.gxs_ids === 'object') {
names = Object.keys(detail.gxs_ids).map(key => rs.userList.username(key));
list = Object.keys(detail.gxs_ids).map((key) => {
return { key, name: rs.userList.username(key) };
});
}
names.sort((a, b) => a.localeCompare(b));
this.users = [];
names.forEach((name) => (this.users = this.users.concat([m('.user', name)])));
list.sort((a, b) => a.name.localeCompare(b.name));
this.users = list;
} else {
this.users = [m('.user', detail.lobby_name)];
this.users = [{ key: detail.gxs_id || '', name: detail.lobby_name }];
}
m.redraw();
};
@ -484,6 +487,38 @@ const ChatLobbyModel = {
},
};
// ************************* Chat Hub State ****************************
function getSafeAvatar(details) {
if (
details &&
details.mAvatar &&
details.mAvatar.mData &&
details.mAvatar.mData.base64 !== ''
) {
return details.mAvatar;
}
return undefined;
}
const ChatHubState = {
selectedRoomId: null,
selectedRoom: null,
selectedRoomType: null,
searchString: '',
ownProfile: { name: 'Loading...' },
gxsDetails: {},
};
function loadOwnChatProfile() {
rs.rsJsonApiRequest('/rsConfig/getConfigNetStatus', {}, (data) => {
if (data && data.status) {
ChatHubState.ownProfile.name = data.status.ownName || 'Unknown';
m.redraw();
}
});
}
// ************************* views ****************************
const Lobby = () => {
@ -523,20 +558,6 @@ const LobbyList = {
},
};
const SubscribedLeftLobbies = {
view() {
return [
m('h5.lefttitle', 'subscribed:'),
m(LobbyList, {
rooms: sortLobbies(Object.values(ChatRoomsModel.subscribedRooms)),
tagname: '.leftlobby.subscribed',
lobbytagname: 'leftname',
onclick: ChatLobbyModel.switchToEvent,
}),
];
},
};
const SubscribedLobbies = {
view() {
return m('.widget', [
@ -552,22 +573,6 @@ const SubscribedLobbies = {
},
};
const PublicLeftLobbies = {
view() {
return [
m('h5.lefttitle', 'public:'),
m(LobbyList, {
rooms: Object.values(ChatRoomsModel.allRooms || {}).filter(
(info) => !ChatRoomsModel.subscribed(info)
),
tagname: '.leftlobby.public',
lobbytagname: 'leftname',
onclick: ChatLobbyModel.setupEvent,
}),
];
},
};
const PublicLobbies = {
view() {
return m('.widget', [
@ -583,78 +588,438 @@ const PublicLobbies = {
},
};
const LobbyName = () => {
return m(
'h3.lobbyName',
m('.mobile-menu-icons', [
m('i.fas.fa-bars', { onclick: () => MobileState.toggleLobbies() }),
]),
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' && ChatLobbyModel.currentLobby.chatType === 3
? [
m('i.fas.fa-cog.setupicon', {
title: 'configure lobby',
onclick: () =>
m.route.set(
'/chat/:lobby/:subaction',
{
lobby: m.route.param('lobby'),
subaction: 'setup',
// ************************* Chat Hub Sub-Components ****************************
const ChatRoomHeader = () => {
return {
view: (vnode) => {
const room = vnode.attrs.room;
const lobbyHexId = rs.idToHex(room.lobby_id);
return m('.chat-hub-header-bar', [
m('.chat-header-info', [
m('.chat-header-name', room.lobby_name || '<unnamed>'),
m('.chat-header-topic', room.lobby_topic || 'No topic'),
]),
m('.chat-header-actions', [
m(
'button.red',
{
title: 'Leave Room',
onclick: () => {
ChatLobbyModel.unsubscribeChatLobby(lobbyHexId, () => {
ChatHubState.selectedRoom = null;
ChatHubState.selectedRoomId = null;
ChatHubState.selectedRoomType = null;
m.route.set('/chat');
});
},
{ replace: true }
),
}),
]
: [],
ChatLobbyModel.isSubscribed
? [
m('i.fas.fa-sign-out-alt.leaveicon', {
title: 'leaving lobby',
onclick: () =>
ChatLobbyModel.unsubscribeChatLobby(m.route.param('lobby'), () => {
m.route.set('/chat', null, { replace: true });
}),
}),
]
: []
);
},
[m('i.fas.fa-sign-out-alt'), ' Leave']
),
]),
]);
},
};
};
function scrollChatToBottom() {
setTimeout(() => {
const element = document.querySelector('.chat-hub-messages');
if (element) {
element.scrollTop = element.scrollHeight;
}
}, 50);
}
const ChatConversationView = () => {
return {
oninit: () => {
scrollChatToBottom();
},
view: () => {
return m('.chat-hub-conversation-layout', [
m('.chat-hub-conversation-main', [
m(
'.chat-hub-messages',
{
oncreate: () => scrollChatToBottom(),
onupdate: () => scrollChatToBottom(),
},
ChatLobbyModel.messages
),
m(
'.chat-hub-input-area',
[
m('textarea.chat-hub-textarea', {
placeholder: 'Type a message... Press Enter to send',
enterkeyhint: 'send',
onkeydown: (e) => {
if ((e.key === 'Enter' || e.keyCode === 13) && !e.shiftKey) {
const msg = e.target.value;
if (msg.trim() === '') return false;
e.target.value = ' sending ... ';
ChatLobbyModel.sendMessage(msg, () => {
e.target.value = '';
scrollChatToBottom();
});
return false;
}
},
}),
m(
'button.chat-hub-send-btn',
{
onclick: (e) => {
const textarea = e.target.closest('.chat-hub-input-area').querySelector('textarea');
const msg = textarea.value;
if (msg.trim() === '') return;
textarea.value = ' sending ... ';
ChatLobbyModel.sendMessage(msg, () => {
textarea.value = '';
scrollChatToBottom();
});
},
},
m('i.fas.fa-paper-plane')
),
]
),
]),
m('.chat-hub-rightbar', [
m('.rightbar-title', 'Participants'),
m('.rightbar-users-list', ChatLobbyModel.users.map((user) => {
const gxsId = user.key;
const name = user.name;
// Load details for avatar if not cached
if (gxsId && ChatHubState.gxsDetails[gxsId] === undefined) {
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();
}
});
}
const avatar = getSafeAvatar(ChatHubState.gxsDetails[gxsId]);
const firstLetter = (name || '?').slice(0, 1).toUpperCase();
return m('.user', [
m(peopleUtil.UserAvatar, { avatar, firstLetter }),
m('span.user-name', name),
]);
}))
])
]);
},
};
};
// ***************************** Page Layouts ******************************
const ChatRoomDetailView = () => {
return {
view: () => {
const room = ChatHubState.selectedRoom;
if (!room) return null;
let participantCount = 0;
let participantNames = [];
if (room.gxs_ids) {
if (Array.isArray(room.gxs_ids)) {
participantCount = room.gxs_ids.length;
participantNames = room.gxs_ids.map((u) => rs.userList.username(u.key) || u.key);
} else if (typeof room.gxs_ids === 'object') {
const keys = Object.keys(room.gxs_ids);
participantCount = keys.length;
participantNames = keys.map((key) => rs.userList.username(key) || key);
}
}
participantNames.sort((a, b) => a.localeCompare(b));
const lobbyHexId = rs.idToHex(room.lobby_id);
return m('.chat-room-detail-view', [
m('.detail-section', [
m('h3', 'Room Info'),
m('.info-grid', [
m('.info-label', 'Room Name'),
m('.info-value', room.lobby_name || '<unnamed>'),
m('.info-label', 'Topic'),
m('.info-value', room.lobby_topic || 'None'),
m('.info-label', 'Participants'),
m('.info-value', participantCount + ' users'),
m('.info-label', 'Your Identity'),
m('.info-value', rs.userList.username(room.gxs_id) || room.gxs_id || '???'),
m('.info-label', 'Lobby ID'),
m('.info-value', lobbyHexId),
]),
]),
m('.detail-section', [
m('h3', 'Participants (' + participantCount + ')'),
participantNames.length > 0
? m(
'.participants-grid',
participantNames.map((name) =>
m('.participant-card', m('.participant-name', name))
)
)
: m('p.no-participants', 'No participant information available'),
]),
]);
},
};
};
const ChatRoomJoinView = () => {
let ownIds = [];
return {
oninit: () => peopleUtil.ownIds((data) => (ownIds = data)),
view: () => {
const room = ChatHubState.selectedRoom;
if (!room) return null;
const lobbyHexId = rs.idToHex(room.lobby_id);
const participantCount = room.total_number_of_peers || 0;
return m('.chat-room-detail-view', [
m('.detail-section', [
m('h3', 'Room Info'),
m('.info-grid', [
m('.info-label', 'Room Name'),
m('.info-value', room.lobby_name || '<unnamed>'),
m('.info-label', 'Topic'),
m('.info-value', room.lobby_topic || 'None'),
m('.info-label', 'Participants'),
m('.info-value', participantCount + ' users'),
]),
]),
m('.detail-section', [
m('h3', 'Join Room'),
m('p.join-description', 'Select an identity to join this chat room:'),
m(
'.identities-grid',
ownIds.map((nick) =>
m(
'.identity-card',
{ onclick: () => ChatLobbyModel.enterPublicLobby(lobbyHexId, nick) },
[
m('.identity-name', rs.userList.username(nick) || nick),
m('i.fas.fa-sign-in-alt'),
]
)
)
),
]),
]);
},
};
};
const Layout = {
view: () => m('.node-panel.chat-panel.chat-hub', [m(SubscribedLobbies), m(PublicLobbies)]),
oninit: () => {
ChatHubState.activeTab = 'chat';
const lobbyId = m.route.param('lobby');
if (lobbyId) {
ChatHubState.selectedRoomId = lobbyId;
ChatLobbyModel.loadLobby(lobbyId);
}
},
onupdate: () => {
const lobbyId = m.route.param('lobby');
if (lobbyId && ChatHubState.selectedRoomId !== lobbyId) {
ChatHubState.selectedRoomId = lobbyId;
ChatLobbyModel.loadLobby(lobbyId);
}
},
view: () => {
const search = ChatHubState.searchString.toLowerCase();
const subscribedRooms = sortLobbies(
Object.values(ChatRoomsModel.subscribedRooms)
).filter((info) => (info.lobby_name || '').toLowerCase().includes(search));
const publicRooms = (ChatRoomsModel.allRooms || [])
.filter((info) => !ChatRoomsModel.subscribed(info))
.filter((info) => (info.lobby_name || '').toLowerCase().includes(search));
const isSelected = (info, type) =>
ChatHubState.selectedRoomId === rs.idToHex(info.lobby_id);
const lobbyId = ChatHubState.selectedRoomId;
let selectedRoom = null;
let selectedRoomType = null;
if (lobbyId) {
if (ChatRoomsModel.subscribedRooms[lobbyId]) {
selectedRoom = ChatRoomsModel.subscribedRooms[lobbyId];
selectedRoomType = 'subscribed';
} else {
selectedRoom = ChatRoomsModel.allRooms.find(
(r) => rs.idToHex(r.lobby_id) === lobbyId
);
if (selectedRoom) {
selectedRoomType = 'public';
}
}
}
if (selectedRoom) {
ChatHubState.selectedRoom = selectedRoom;
ChatHubState.selectedRoomType = selectedRoomType;
} else if (!m.route.param('lobby')) {
ChatHubState.selectedRoom = null;
ChatHubState.selectedRoomId = null;
ChatHubState.selectedRoomType = null;
}
return m('.chat-hub-container', [
m('.chat-hub-left-pane', [
m('.chat-own-profile-card', [
m('.profile-header', [
m('i.fas.fa-comments', { style: { fontSize: '1.5rem', color: '#3ba4d7' } }),
m('.profile-info', [
m('.profile-name', 'Chat rooms'),
]),
]),
]),
m('.chat-rooms-list-container', [
m('.searchbar-container', [
m('input.searchbar', {
type: 'text',
placeholder: 'Search chat rooms...',
value: ChatHubState.searchString,
oninput: (e) => {
ChatHubState.searchString = e.target.value;
},
}),
]),
m('.rooms-scroll', [
subscribedRooms.length > 0 && [
m('.rooms-section-title', [
m('i.fas.fa-bookmark'),
m('span', 'Subscribed (' + subscribedRooms.length + ')'),
]),
subscribedRooms.map((info) => {
const hexId = rs.idToHex(info.lobby_id);
let count = 0;
if (info.gxs_ids) {
if (Array.isArray(info.gxs_ids)) count = info.gxs_ids.length;
else if (typeof info.gxs_ids === 'object')
count = Object.keys(info.gxs_ids).length;
}
return m(
'.chat-room-list-item' +
(isSelected(info, 'subscribed') ? '.selected' : ''),
{
key: hexId,
onclick: () => {
m.route.set('/chat/:lobby', { lobby: hexId });
},
},
[
m('.room-icon', m('i.fas.fa-comments')),
m('.room-meta', [
m('.room-name', info.lobby_name || '<unnamed>'),
m('.room-topic', info.lobby_topic || 'No topic'),
]),
count > 0 && m('.room-badge', count),
]
);
}),
],
publicRooms.length > 0 && [
m('.rooms-section-title', [
m('i.fas.fa-globe'),
m('span', 'Public (' + publicRooms.length + ')'),
]),
publicRooms.map((info) => {
const hexId = rs.idToHex(info.lobby_id);
const count = info.total_number_of_peers || 0;
return m(
'.chat-room-list-item.public-room' +
(isSelected(info, 'public') ? '.selected' : ''),
{
key: hexId,
onclick: () => {
m.route.set('/chat/:lobby', { lobby: hexId });
},
},
[
m('.room-icon', m('i.fas.fa-globe')),
m('.room-meta', [
m('.room-name', info.lobby_name || '<unnamed>'),
m('.room-topic', info.lobby_topic || 'No topic'),
]),
count > 0 && m('.room-badge', count),
]
);
}),
],
subscribedRooms.length === 0 &&
publicRooms.length === 0 &&
m('p.no-rooms', 'No chat rooms found'),
]),
]),
]),
m('.chat-hub-right-pane', [
ChatHubState.selectedRoom
? [
ChatHubState.selectedRoomType === 'subscribed'
? [
m(ChatRoomHeader, { room: ChatHubState.selectedRoom }),
m('.chat-hub-tabs-container', [
m('.chat-hub-tabs', [
m(
'button.tab-btn' +
(ChatHubState.activeTab === 'chat' ? '.active' : ''),
{
onclick: () => {
ChatHubState.activeTab = 'chat';
scrollChatToBottom();
},
},
[m('i.fas.fa-comments'), ' Chat']
),
m(
'button.tab-btn' +
(ChatHubState.activeTab === 'details' ? '.active' : ''),
{
onclick: () => {
ChatHubState.activeTab = 'details';
},
},
[m('i.fas.fa-info-circle'), ' Details']
),
]),
]),
m('.chat-hub-tab-content', { style: { padding: ChatHubState.activeTab === 'chat' ? '0' : '1.5rem' } }, [
ChatHubState.activeTab === 'chat'
? m(ChatConversationView)
: m(ChatRoomDetailView),
]),
]
: [
m('.chat-hub-tab-content', m(ChatRoomJoinView)),
],
]
: m('.chat-pane-placeholder', [
m('i.fas.fa-comments'),
m(
'p',
'Select a chat room from the left panel to view details or join a conversation.'
),
]),
]),
]);
},
};
const LayoutSingle = () => {
@ -681,10 +1046,7 @@ const LayoutSingle = () => {
},
[
m('.chat-overlay', { onclick: () => MobileState.closeAll() }),
LobbyName(),
!isPrivate && m('.lobbies', m(SubscribedLeftLobbies), m(PublicLeftLobbies)),
m('.messages', { onclick: () => MobileState.closeAll() }, ChatLobbyModel.messages),
m('.rightbar', ChatLobbyModel.users),
m(
'.chatMessage',
{},
@ -723,40 +1085,6 @@ const LayoutSingle = () => {
};
};
const LayoutSetup = () => {
let ownIds = [];
return {
oninit: () => peopleUtil.ownIds((data) => (ownIds = data)),
view: (vnode) =>
m(
'.node-panel.chat-panel.chat-room.chat-setup',
{
class:
(MobileState.showLobbies ? 'show-lobbies ' : '') +
(MobileState.showUsers ? 'show-users' : ''),
},
[
m('.chat-overlay', { onclick: () => MobileState.closeAll() }),
LobbyName(),
m('.lobbies', m(SubscribedLeftLobbies), m(PublicLeftLobbies)),
m('.setup', [
m('h5.selectidentity', 'Select identity to use'),
ownIds.map((nick) =>
m(
'.identity' +
(ChatLobbyModel.currentLobby.gxs_id === nick ? '.selectedidentity' : ''),
{
onclick: () => ChatLobbyModel.setupAction(m.route.param('lobby'), nick),
},
rs.userList.username(nick)
)
),
]),
]
),
};
};
/*
/rsChats/initiateDistantChatConnexion
* @param[in] to_pid RsGxsId to start the connection
@ -802,16 +1130,13 @@ const LayoutCreateDistant = () => {
module.exports = {
oninit: () => {
ChatRoomsModel.loadSubscribedRooms();
loadOwnChatProfile();
},
view: (vnode) => {
if (m.route.param('lobby') === undefined) {
return m(Layout);
} else if (m.route.param('subaction') === 'setup') {
return m(LayoutSetup);
} else if (m.route.param('subaction') === 'createdistantchat') {
if (m.route.param('subaction') === 'createdistantchat') {
return m(LayoutCreateDistant);
} else {
return m(LayoutSingle);
return m(Layout);
}
},
};

File diff suppressed because it is too large Load Diff

View File

@ -623,3 +623,725 @@ h1{font-size:3rem}h2{font-size:2.25rem}h3{font-size:1.875rem}h4{font-size:1.5rem
font-weight: 600;
font-size: 0.9rem;
}
/* =====================================================
CHAT HUB - Two-Pane Layout
===================================================== */
.chat-hub-container {
display: flex;
height: 100%;
width: 100%;
overflow: hidden;
background-color: #f1f5f9;
}
.chat-hub-left-pane {
width: 320px;
min-width: 300px;
max-width: 350px;
border-right: 1px solid #cbd5e1;
display: flex;
flex-direction: column;
background: #ffffff;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05);
}
.chat-own-profile-card {
padding: 1.25rem;
border-bottom: 1px solid #e2e8f0;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
}
.chat-own-profile-card .profile-header {
display: flex;
align-items: center;
gap: 1rem;
}
.chat-own-profile-card .profile-info {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
.chat-own-profile-card .profile-info .profile-name {
font-weight: 700;
color: #1e293b;
font-size: 1.1rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-own-profile-card .profile-info .profile-status {
font-size: 0.85rem;
color: #10b981;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.35rem;
}
.chat-own-profile-card .profile-info .profile-status::before {
content: '';
display: inline-block;
width: 8px;
height: 8px;
background-color: #10b981;
border-radius: 50%;
}
.chat-rooms-list-container {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.chat-rooms-list-container .searchbar-container {
padding: 0.75rem 1rem;
border-bottom: 1px solid #e2e8f0;
}
.chat-rooms-list-container .searchbar-container input.searchbar {
width: 100%;
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
border: 1px solid #cbd5e1;
border-radius: 0.375rem;
background-color: #f8fafc;
outline: none;
transition: all 0.2s;
}
.chat-rooms-list-container .searchbar-container input.searchbar:focus {
background-color: #ffffff;
border-color: #3ba4d7;
box-shadow: 0 0 0 3px rgba(59, 164, 215, 0.15);
}
.chat-rooms-list-container .rooms-scroll {
flex: 1;
overflow-y: auto;
padding: 0.5rem 0;
}
.rooms-section-title {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem 0.375rem;
font-size: 0.75rem;
font-weight: 700;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.rooms-section-title i {
font-size: 0.7rem;
color: #94a3b8;
}
.chat-room-list-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
margin: 0.125rem 0.5rem;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.2s;
}
.chat-room-list-item:hover {
background-color: #f1f5f9;
}
.chat-room-list-item.selected {
background-color: #e0f2fe;
}
.chat-room-list-item.selected .room-meta .room-name {
color: #0369a1;
font-weight: 600;
}
.chat-room-list-item .room-icon {
flex-shrink: 0;
width: 36px;
height: 36px;
border-radius: 0.5rem;
background: linear-gradient(135deg, #3ba4d7, #0ea5e9);
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
font-size: 0.85rem;
}
.chat-room-list-item.public-room .room-icon {
background: linear-gradient(135deg, #10b981, #059669);
}
.chat-room-list-item .room-meta {
flex: 1;
min-width: 0;
}
.chat-room-list-item .room-meta .room-name {
font-size: 0.95rem;
color: #334155;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: color 0.2s;
}
.chat-room-list-item .room-meta .room-topic {
font-size: 0.8rem;
color: #94a3b8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-room-list-item .room-badge {
flex-shrink: 0;
min-width: 24px;
height: 24px;
border-radius: 12px;
background-color: #e2e8f0;
color: #475569;
font-size: 0.75rem;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
padding: 0 0.375rem;
}
.chat-hub-right-pane {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #f8fafc;
}
.chat-pane-placeholder {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #94a3b8;
gap: 1rem;
padding: 2rem;
text-align: center;
}
.chat-pane-placeholder i {
font-size: 4rem;
color: #cbd5e1;
}
.chat-pane-placeholder p {
font-size: 1.1rem;
max-width: 400px;
}
.chat-hub-tab-content {
flex: 1;
overflow-y: auto;
padding: 1.5rem;
}
.chat-room-detail-view {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.chat-room-detail-view .detail-header {
display: flex;
align-items: flex-start;
gap: 1.5rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid #e2e8f0;
flex-wrap: wrap;
}
.chat-room-detail-view .detail-header .detail-title {
flex: 1;
min-width: 200px;
}
.chat-room-detail-view .detail-header .detail-title h2 {
font-size: 1.75rem;
font-weight: 800;
color: #1e293b;
margin-bottom: 0.25rem;
}
.chat-room-detail-view .detail-header .detail-title .detail-subtitle {
font-size: 0.9rem;
color: #64748b;
display: flex;
align-items: center;
gap: 0.5rem;
}
.chat-room-detail-view .detail-header .detail-actions {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
.chat-room-detail-view .detail-header .detail-actions button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.9rem;
}
.chat-room-detail-view .detail-section {
background-color: #ffffff;
border-radius: 0.5rem;
border: 1px solid #e2e8f0;
padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02);
}
.chat-room-detail-view .detail-section h3 {
font-size: 1.1rem;
font-weight: 700;
color: #334155;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid #f1f5f9;
}
.chat-room-detail-view .detail-section .info-grid {
display: grid;
grid-template-columns: 130px 1fr;
row-gap: 0.75rem;
font-size: 0.9rem;
}
.chat-room-detail-view .detail-section .info-grid .info-label {
font-weight: 600;
color: #64748b;
}
.chat-room-detail-view .detail-section .info-grid .info-value {
color: #1e293b;
word-break: break-all;
}
.participants-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 0.5rem;
}
.participant-card {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.375rem;
}
.participant-card .participant-name {
font-size: 0.875rem;
color: #334155;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.no-participants {
color: #94a3b8;
font-size: 0.9rem;
font-style: italic;
}
.detail-actions-footer {
display: flex;
gap: 0.75rem;
}
.detail-actions-footer button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
font-size: 0.9rem;
}
.join-description {
color: #64748b;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.identities-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 0.75rem;
}
.identity-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
background-color: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.2s;
}
.identity-card:hover {
background-color: #e0f2fe;
border-color: #3ba4d7;
}
.identity-card .identity-name {
font-size: 0.95rem;
font-weight: 600;
color: #334155;
}
.identity-card i {
color: #3ba4d7;
font-size: 0.9rem;
}
.no-rooms {
padding: 1rem;
color: #94a3b8;
text-align: center;
font-style: italic;
}
/* Chat Hub Responsive - Mobile */
@media (max-width: 899px) {
.chat-hub-container {
flex-direction: column;
}
.chat-hub-left-pane {
width: 100%;
min-width: 0;
max-width: none;
max-height: 45%;
border-right: none;
border-bottom: 1px solid #cbd5e1;
}
.chat-hub-right-pane {
flex: 1;
min-height: 0;
}
}
/* =====================================================
CHAT HUB - Right Pane Conversation & Tabs Styling
===================================================== */
.chat-hub-header-bar {
padding: 0.75rem 1.5rem;
background-color: #ffffff;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: space-between;
height: 65px;
flex-shrink: 0;
}
.chat-hub-header-bar .chat-header-info {
display: flex;
flex-direction: column;
overflow: hidden;
}
.chat-hub-header-bar .chat-header-info .chat-header-name {
font-size: 1.15rem;
font-weight: 800;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-hub-header-bar .chat-header-info .chat-header-topic {
font-size: 0.85rem;
color: #64748b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 0.125rem;
}
.chat-hub-header-bar .chat-header-actions {
display: flex;
gap: 0.5rem;
}
.chat-hub-header-bar .chat-header-actions button {
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.375rem 0.75rem;
font-size: 0.85rem;
}
.chat-hub-tabs-container {
background-color: #ffffff;
border-bottom: 1px solid #cbd5e1;
padding: 0.5rem 1.5rem 0;
}
.chat-hub-tabs {
display: flex;
gap: 0.5rem;
}
.chat-hub-tabs .tab-btn {
padding: 0.625rem 1.25rem;
font-size: 0.95rem;
font-weight: 600;
color: #64748b;
background: transparent;
border: none;
border-radius: 0.375rem 0.375rem 0 0;
border-bottom: 3px solid transparent;
cursor: pointer;
box-shadow: none;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.chat-hub-tabs .tab-btn:hover {
color: #334155;
background-color: #f1f5f9;
}
.chat-hub-tabs .tab-btn.active {
color: #3ba4d7;
border-bottom-color: #3ba4d7;
background-color: transparent;
}
.chat-hub-tab-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background-color: #f8fafc;
}
.chat-hub-conversation-layout {
display: flex;
flex-direction: row;
height: 100%;
width: 100%;
overflow: hidden;
}
.chat-hub-conversation-main {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
overflow: hidden;
}
.chat-hub-rightbar {
width: 200px;
border-left: 1px solid #cbd5e1;
background-color: #ffffff;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.chat-hub-rightbar .rightbar-title {
padding: 0.75rem 1rem;
font-size: 0.85rem;
font-weight: 700;
color: #64748b;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid #e2e8f0;
}
.chat-hub-rightbar .rightbar-users-list {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
}
.chat-hub-rightbar .user {
padding: 0.5rem 0.75rem;
font-size: 0.9rem;
color: #334155;
border-radius: 0.375rem;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.chat-hub-rightbar .user:hover {
background-color: #f1f5f9;
color: #0f172a;
}
.chat-hub-rightbar .user .defaultAvatar {
width: 2rem;
height: 2rem;
font-size: 0.9rem;
flex-shrink: 0;
}
.chat-hub-rightbar .user img.avatar {
width: 2rem;
height: 2rem;
flex-shrink: 0;
}
@media (max-width: 899px) {
.chat-hub-rightbar {
display: none;
}
}
.chat-hub-messages {
flex: 1;
overflow-y: auto;
padding: 1.25rem 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
/* Chat bubble overrides for two-pane layout */
.chat-hub-messages .message {
display: flex;
flex-direction: column;
max-width: 70%;
padding: 0.625rem 0.875rem;
border-radius: 0.75rem;
font-size: 0.925rem;
line-height: 1.4;
word-break: break-word;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.chat-hub-messages .message.incoming {
align-self: flex-start;
align-items: flex-start;
background-color: #ffffff;
color: #1e293b;
border: 1px solid #e2e8f0;
border-bottom-left-radius: 0.125rem;
}
.chat-hub-messages .message.outgoing {
align-self: flex-end;
align-items: flex-end;
background-color: #3ba4d7;
color: #ffffff;
border-bottom-right-radius: 0.125rem;
}
.chat-hub-messages .message .username {
font-size: 0.75rem;
margin-bottom: 0.25rem;
padding: 0 0.125rem;
font-weight: 700;
}
.chat-hub-messages .message.incoming .username {
color: #0369a1;
}
.chat-hub-messages .message.outgoing .username {
color: #e0f2fe;
}
.chat-hub-messages .message .messagetext {
white-space: break-spaces;
margin: 0;
}
.chat-hub-messages .message .datetime {
font-size: 0.7rem;
margin-top: 0.25rem;
padding: 0 0.125rem;
opacity: 0.8;
}
.chat-hub-messages .message.incoming .datetime {
color: #64748b;
}
.chat-hub-messages .message.outgoing .datetime {
color: #f1f5f9;
}
.chat-hub-input-area {
padding: 1rem 1.5rem;
background-color: #ffffff;
border-top: 1px solid #cbd5e1;
display: flex;
gap: 0.75rem;
align-items: center;
flex-shrink: 0;
}
.chat-hub-input-area textarea.chat-hub-textarea {
flex: 1;
resize: none;
height: 40px;
padding: 0.5rem 0.75rem;
border: 1px solid #cbd5e1;
border-radius: 0.375rem;
font-size: 0.9rem;
outline: none;
transition: all 0.2s;
background-color: #f8fafc;
}
.chat-hub-input-area textarea.chat-hub-textarea:focus {
background-color: #ffffff;
border-color: #3ba4d7;
box-shadow: 0 0 0 3px rgba(59, 164, 215, 0.15);
}
.chat-hub-input-area button.chat-hub-send-btn {
padding: 0.5rem 1.25rem;
font-size: 0.9rem;
height: 40px;
display: flex;
align-items: center;
gap: 0.5rem;
border-radius: 0.375rem;
}