160 lines
6.3 KiB
JavaScript
160 lines
6.3 KiB
JavaScript
const EMPTY_CONNECTION_IDS_HTML = '<p class="text-gray-500 text-sm">\u6682\u65e0\u53ef\u7528\u7684\u8fde\u63a5ID</p>';
|
|
const EMPTY_USERS_HTML = '<p class="text-gray-500 text-sm">\u6682\u65e0\u5728\u7ebf\u7528\u6237</p>';
|
|
const HALL_LABEL = '\u5927\u5385\uff08\u672a\u52a0\u5165\u623f\u95f4\uff09';
|
|
const HOST_LABEL = '\u623f\u4e3b';
|
|
const PARTICIPANT_LABEL = '\u6210\u5458';
|
|
const UNKNOWN_USER_LABEL = '\u533f\u540d\u7528\u6237';
|
|
const UNSET_USER_ID_LABEL = '\u672a\u8bbe\u7f6eID';
|
|
const SELF_LABEL = '\u81ea\u5df1';
|
|
const SELECT_LABEL = '\u9009\u62e9';
|
|
const USER_COUNT_SUFFIX = '\u4eba';
|
|
const ONLINE_USERS_SUMMARY_SUFFIX = ' \u4e2aWebSocket\u7528\u6237\u5728\u7ebf';
|
|
|
|
function escapeHtml(value) {
|
|
return String(value || '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
export async function fetchOnlineUsers() {
|
|
const response = await fetch('/signaling/users');
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch online users');
|
|
}
|
|
|
|
const data = await response.json();
|
|
return Array.isArray(data.users) ? data.users : [];
|
|
}
|
|
|
|
export async function fetchConnectionDirectory() {
|
|
const [connectionResponse, usersResponse] = await Promise.all([
|
|
fetch('/signaling/connection-ids'),
|
|
fetch('/signaling/users')
|
|
]);
|
|
|
|
if (!connectionResponse.ok) {
|
|
throw new Error('Failed to fetch connection IDs');
|
|
}
|
|
if (!usersResponse.ok) {
|
|
throw new Error('Failed to fetch online users');
|
|
}
|
|
|
|
const connectionData = await connectionResponse.json();
|
|
const usersData = await usersResponse.json();
|
|
|
|
return {
|
|
connectionIds: connectionData.connectionIds || [],
|
|
users: Array.isArray(usersData.users) ? usersData.users : []
|
|
};
|
|
}
|
|
|
|
export function renderConnectionIds({ connectionIds, idsContainer, connectionIdsList, onSelectConnectionId }) {
|
|
if (!idsContainer) {
|
|
return;
|
|
}
|
|
|
|
idsContainer.innerHTML = '';
|
|
|
|
if (connectionIds.length === 0) {
|
|
idsContainer.innerHTML = EMPTY_CONNECTION_IDS_HTML;
|
|
} else {
|
|
connectionIds.forEach((connectionId) => {
|
|
const item = document.createElement('div');
|
|
item.className = 'flex items-center justify-between p-2 bg-white/5 rounded-lg hover:bg-white/10 cursor-pointer transition-colors';
|
|
|
|
const label = document.createElement('span');
|
|
label.className = 'text-sm';
|
|
label.textContent = connectionId;
|
|
|
|
const button = document.createElement('button');
|
|
button.className = 'text-xs bg-indigo-600 hover:bg-indigo-700 px-2 py-1 rounded';
|
|
button.type = 'button';
|
|
button.textContent = SELECT_LABEL;
|
|
button.addEventListener('click', () => onSelectConnectionId(connectionId));
|
|
|
|
item.appendChild(label);
|
|
item.appendChild(button);
|
|
idsContainer.appendChild(item);
|
|
});
|
|
}
|
|
|
|
if (connectionIdsList) {
|
|
connectionIdsList.classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
export function renderOnlineUsers({ users, currentUserId, onlineUsersList, usersContainer, onlineUsersSummary }) {
|
|
if (!onlineUsersList || !usersContainer || !onlineUsersSummary) {
|
|
return;
|
|
}
|
|
|
|
onlineUsersSummary.textContent = `${users.length}${ONLINE_USERS_SUMMARY_SUFFIX}`;
|
|
usersContainer.innerHTML = '';
|
|
|
|
if (users.length === 0) {
|
|
usersContainer.innerHTML = EMPTY_USERS_HTML;
|
|
onlineUsersList.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
const groupedUsers = users.reduce((groups, user) => {
|
|
const groupName = user.connectionId ? `\u623f\u95f4 ${user.connectionId}` : HALL_LABEL;
|
|
if (!groups[groupName]) {
|
|
groups[groupName] = [];
|
|
}
|
|
groups[groupName].push(user);
|
|
return groups;
|
|
}, {});
|
|
|
|
Object.entries(groupedUsers).forEach(([groupName, roomUsers]) => {
|
|
const section = document.createElement('div');
|
|
section.className = 'rounded-lg border border-white/10 bg-white/5 p-3';
|
|
|
|
const roomTitle = document.createElement('div');
|
|
roomTitle.className = 'flex items-center justify-between mb-2';
|
|
roomTitle.innerHTML = `
|
|
<span class="text-sm font-medium text-white">${escapeHtml(groupName)}</span>
|
|
<span class="text-xs text-gray-400">${roomUsers.length} ${USER_COUNT_SUFFIX}</span>
|
|
`;
|
|
section.appendChild(roomTitle);
|
|
|
|
const roomList = document.createElement('div');
|
|
roomList.className = 'space-y-2';
|
|
|
|
roomUsers.forEach((user) => {
|
|
const userName = user.name || user.userId || UNKNOWN_USER_LABEL;
|
|
const avatar = user.avatar || '/images/p2.png';
|
|
const roleLabel = user.role === 'host'
|
|
? HOST_LABEL
|
|
: (user.role === 'participant' ? PARTICIPANT_LABEL : HALL_LABEL);
|
|
const isSelf = Boolean(user.userId) && user.userId === currentUserId;
|
|
const identity = user.userId || user.socketId || user.participantId || UNSET_USER_ID_LABEL;
|
|
|
|
const userItem = document.createElement('div');
|
|
userItem.className = 'flex items-center justify-between rounded-lg bg-black/20 px-3 py-2';
|
|
userItem.innerHTML = `
|
|
<div class="flex items-center gap-3 min-w-0">
|
|
<img src="${escapeHtml(avatar)}" alt="${escapeHtml(userName)}" class="w-8 h-8 rounded-full object-cover">
|
|
<div class="min-w-0">
|
|
<div class="text-sm text-white truncate">${escapeHtml(userName)}</div>
|
|
<div class="text-xs text-gray-400 truncate">${escapeHtml(identity)}</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<span class="text-xs px-2 py-1 rounded-full ${user.role === 'host' ? 'bg-indigo-500/20 text-indigo-300' : (user.role === 'participant' ? 'bg-white/10 text-gray-300' : 'bg-emerald-500/20 text-emerald-300')}">${roleLabel}</span>
|
|
${isSelf ? `<span class="text-xs text-gray-500">${SELF_LABEL}</span>` : ''}
|
|
</div>
|
|
`;
|
|
roomList.appendChild(userItem);
|
|
});
|
|
|
|
section.appendChild(roomList);
|
|
usersContainer.appendChild(section);
|
|
});
|
|
|
|
onlineUsersList.classList.remove('hidden');
|
|
}
|