++
This commit is contained in:
189
client/public/renderer-ui.js
Normal file
189
client/public/renderer-ui.js
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
const DEFAULT_NETWORK_QUALITY = {
|
||||||
|
label: '\u672a\u77e5',
|
||||||
|
statusIconClass: 'fas fa-question-circle text-gray-400',
|
||||||
|
statusTextClass: 'text-gray-400',
|
||||||
|
headerIconClass: 'fas fa-signal text-gray-400',
|
||||||
|
indicatorClass: 'w-2 h-2 bg-green-500 rounded-full animate-pulse',
|
||||||
|
connectionTextClass: 'text-gray-400'
|
||||||
|
};
|
||||||
|
|
||||||
|
const NETWORK_QUALITY_DISPLAY = {
|
||||||
|
excellent: {
|
||||||
|
label: '\u4f18\u79c0',
|
||||||
|
statusIconClass: 'fas fa-check-circle text-green-400',
|
||||||
|
statusTextClass: 'text-green-400',
|
||||||
|
headerIconClass: 'fas fa-signal text-green-400',
|
||||||
|
indicatorClass: 'w-2 h-2 bg-green-500 rounded-full animate-pulse',
|
||||||
|
connectionTextClass: 'text-green-400'
|
||||||
|
},
|
||||||
|
good: {
|
||||||
|
label: '\u826f\u597d',
|
||||||
|
statusIconClass: 'fas fa-signal text-blue-400',
|
||||||
|
statusTextClass: 'text-blue-400',
|
||||||
|
headerIconClass: 'fas fa-signal text-green-500',
|
||||||
|
indicatorClass: 'w-2 h-2 bg-green-500 rounded-full animate-pulse',
|
||||||
|
connectionTextClass: 'text-blue-400'
|
||||||
|
},
|
||||||
|
fair: {
|
||||||
|
label: '\u4e00\u822c',
|
||||||
|
statusIconClass: 'fas fa-exclamation-circle text-yellow-500',
|
||||||
|
statusTextClass: 'text-yellow-500',
|
||||||
|
headerIconClass: 'fas fa-signal text-yellow-400',
|
||||||
|
indicatorClass: 'w-2 h-2 bg-green-500 rounded-full animate-pulse',
|
||||||
|
connectionTextClass: 'text-yellow-500'
|
||||||
|
},
|
||||||
|
poor: {
|
||||||
|
label: '\u8f83\u5dee',
|
||||||
|
statusIconClass: 'fas fa-exclamation-triangle text-red-500',
|
||||||
|
statusTextClass: 'text-red-500',
|
||||||
|
headerIconClass: 'fas fa-signal text-red-400',
|
||||||
|
indicatorClass: 'w-2 h-2 bg-green-500 rounded-full animate-pulse',
|
||||||
|
connectionTextClass: 'text-red-500'
|
||||||
|
},
|
||||||
|
no_signal: {
|
||||||
|
label: '\u65e0\u4fe1\u53f7',
|
||||||
|
statusIconClass: 'fas fa-times-circle text-gray-500',
|
||||||
|
statusTextClass: 'text-gray-500',
|
||||||
|
headerIconClass: 'fas fa-signal text-gray-400',
|
||||||
|
indicatorClass: 'w-2 h-2 bg-gray-500 rounded-full',
|
||||||
|
connectionTextClass: 'text-gray-500'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRoleTagMarkup(user, role) {
|
||||||
|
if (role === 'local') {
|
||||||
|
return user.isHost
|
||||||
|
? '<span class="text-xs bg-indigo-500 px-1.5 rounded ml-1">\u4e3b\u6301\u4eba</span>'
|
||||||
|
: '<span class="text-xs bg-purple-500 px-1.5 rounded ml-1">\u53c2\u4e0e\u8005</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role === 'participant') {
|
||||||
|
return '<span class="text-xs bg-purple-500 px-1.5 rounded ml-1">\u53c2\u4e0e\u8005</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '<span class="text-xs bg-indigo-500 px-1.5 rounded ml-1">\u4e3b\u6301\u4eba</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDatasetUserId(role, id) {
|
||||||
|
switch (role) {
|
||||||
|
case 'local':
|
||||||
|
return 'local';
|
||||||
|
case 'remote':
|
||||||
|
return 'remote';
|
||||||
|
case 'host':
|
||||||
|
return `host_${id}`;
|
||||||
|
case 'participant':
|
||||||
|
return `participant_${id}`;
|
||||||
|
default:
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAvatarMarkup(user, role) {
|
||||||
|
if (role === 'local') {
|
||||||
|
return `<img src="${user.avatar}" class="w-10 h-10 rounded-full object-cover">`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="relative">
|
||||||
|
<img src="${user.avatar}" class="w-10 h-10 rounded-full object-cover">
|
||||||
|
<div class="absolute -bottom-1 -right-1 w-3 h-3 bg-green-500 rounded-full border-2 border-slate-900"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRightMarkup(mediaState, role, muteIconMarkup) {
|
||||||
|
if (role !== 'participant') {
|
||||||
|
return muteIconMarkup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const speakingMarkup = (mediaState.isSpeaking && mediaState.audio)
|
||||||
|
? '<div class="audio-wave w-6"><span></span><span></span><span></span><span></span><span></span></div>'
|
||||||
|
: '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
${muteIconMarkup}
|
||||||
|
${speakingMarkup}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCallTitle(connectionId) {
|
||||||
|
return `\u901a\u8bdd (${connectionId || ''})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRemoteVideoPlaceholderText(isVideoEnabled) {
|
||||||
|
return isVideoEnabled
|
||||||
|
? {
|
||||||
|
title: '\u7b49\u5f85\u5bf9\u65b9\u8fde\u63a5...',
|
||||||
|
subtitle: '\u8bf7\u786e\u8ba4\u5bf9\u65b9\u5df2\u52a0\u5165\u901a\u8bdd'
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
title: '\u5bf9\u65b9\u6444\u50cf\u5934\u5df2\u5173\u95ed',
|
||||||
|
subtitle: '\u5bf9\u65b9\u6682\u65f6\u5173\u95ed\u4e86\u89c6\u9891'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNetworkQualityDisplay(quality) {
|
||||||
|
return NETWORK_QUALITY_DISPLAY[quality] || DEFAULT_NETWORK_QUALITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getMediaStatusMeta(mediaState) {
|
||||||
|
if (!mediaState.audio) {
|
||||||
|
return {
|
||||||
|
text: '\u9759\u97f3\u4e2d',
|
||||||
|
className: 'text-xs text-gray-500',
|
||||||
|
muteIconClass: 'fas fa-microphone-slash text-gray-500 text-xs',
|
||||||
|
showMuteIcon: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mediaState.video) {
|
||||||
|
return {
|
||||||
|
text: '\u89c6\u9891\u5173\u95ed',
|
||||||
|
className: 'text-xs text-gray-500',
|
||||||
|
muteIconClass: 'fas fa-microphone-slash text-gray-500 text-xs',
|
||||||
|
showMuteIcon: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
text: '\u5728\u7ebf',
|
||||||
|
className: 'text-xs text-green-400',
|
||||||
|
muteIconClass: 'fas fa-microphone-slash text-gray-500 text-xs',
|
||||||
|
showMuteIcon: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildUserCountLabel(userCount) {
|
||||||
|
return `\u901a\u8bdd\u6210\u5458 (${userCount})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUserEntryElement({ user, role, id }) {
|
||||||
|
const entry = document.createElement('div');
|
||||||
|
const mediaMeta = getMediaStatusMeta(user.mediaState);
|
||||||
|
const muteIconMarkup = mediaMeta.showMuteIcon
|
||||||
|
? `<i class="${mediaMeta.muteIconClass}"></i>`
|
||||||
|
: '';
|
||||||
|
const baseClass = 'flex items-center gap-3 p-2 rounded-lg';
|
||||||
|
const dataFieldAttr = role === 'local' ? ' data-field="localUser.mediaStatus"' : '';
|
||||||
|
|
||||||
|
entry.className = role === 'local'
|
||||||
|
? `${baseClass} hover:bg-white/5`
|
||||||
|
: `${baseClass} bg-white/5`;
|
||||||
|
entry.dataset.userId = getDatasetUserId(role, id);
|
||||||
|
entry.innerHTML = `
|
||||||
|
${getAvatarMarkup(user, role)}
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="text-sm font-medium">
|
||||||
|
${user.name}
|
||||||
|
${getRoleTagMarkup(user, role)}
|
||||||
|
</div>
|
||||||
|
<div class="${mediaMeta.className}"${dataFieldAttr}>${mediaMeta.text}</div>
|
||||||
|
</div>
|
||||||
|
${getRightMarkup(user.mediaState, role, muteIconMarkup)}
|
||||||
|
`;
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user