voice chat indicators
This commit is contained in:
parent
d618ee3134
commit
b12a75cfc4
8 changed files with 464 additions and 42 deletions
118
webroot/main.js
118
webroot/main.js
|
|
@ -2674,6 +2674,8 @@ async function renderDms(res) {
|
|||
let isRead = dmData.string3 === "true";
|
||||
let nameStyle = isRead ? "opacity: 0.8;" : "opacity: 1;";
|
||||
|
||||
seedCallStateFromList(dmIdRaw, dmData.call === true);
|
||||
|
||||
let safeTargetUsername = encodeURIComponent(targetUsername).replace(/'/g, "%27");
|
||||
|
||||
let unreadBadge = `<div class="unread-badge ${isRead ? "" : "show"}"></div>`;
|
||||
|
|
@ -2686,6 +2688,7 @@ async function renderDms(res) {
|
|||
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(targetUsername)} (${escapeHtml(targetId.split(';')[0])})</span>
|
||||
</div>
|
||||
${unreadBadge}
|
||||
${getCallIndicatorHtml()}
|
||||
</button>
|
||||
`;
|
||||
count++;
|
||||
|
|
@ -2694,6 +2697,7 @@ async function renderDms(res) {
|
|||
if (count > 0) {
|
||||
container.innerHTML = html;
|
||||
container.classList.remove("empty");
|
||||
updateVoicePresenceUI();
|
||||
} else {
|
||||
container.innerHTML = `<p style="opacity: 0.5;"><blah>desc.no.dms</blah></p>`;
|
||||
container.classList.add("empty");
|
||||
|
|
@ -2731,6 +2735,8 @@ async function renderGroups(res) {
|
|||
let isRead = groupData.string3 === "true";
|
||||
let nameStyle = isRead ? "opacity: 0.8;" : "opacity: 1;";
|
||||
|
||||
seedCallStateFromList(groupId, groupData.call === true);
|
||||
|
||||
let infoRes = await fetchEncrypted("group/info", groupId);
|
||||
if (infoRes && infoRes.startsWith("{")) {
|
||||
let info = JSON.parse(infoRes);
|
||||
|
|
@ -2750,6 +2756,7 @@ async function renderGroups(res) {
|
|||
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${escapeHtmlText(groupId.replace(';', ':'))}</span>
|
||||
</div>
|
||||
${unreadBadge}
|
||||
${getCallIndicatorHtml()}
|
||||
</button>
|
||||
`;
|
||||
count++;
|
||||
|
|
@ -2762,6 +2769,7 @@ async function renderGroups(res) {
|
|||
if (count > 0) {
|
||||
container.innerHTML = html;
|
||||
container.classList.remove("empty");
|
||||
updateVoicePresenceUI();
|
||||
} else {
|
||||
container.innerHTML = `<p style="opacity: 0.5;"><blah>desc.no.groups</blah></p>`;
|
||||
container.classList.add("empty");
|
||||
|
|
@ -2813,6 +2821,101 @@ function escapeHtmlText(value) {
|
|||
return escapeHtml(String(value ?? ""));
|
||||
}
|
||||
|
||||
// --- Wskaźniki aktywnej rozmowy głosowej (zielone słuchawki) ---
|
||||
|
||||
function getCallIndicatorHtml() {
|
||||
return `<div class="call-indicator">${ICONS.headphones({ width: "1rem", height: "1rem", stroke: "var(--big-green)" })}</div>`;
|
||||
}
|
||||
|
||||
function roomHasActiveCall(roomId) {
|
||||
if (!roomId || !window.activeCallsByRoom) return false;
|
||||
//Semantyka mapy: klucz ISTNIEJE = rozmowa trwa. Pusta tablica oznacza
|
||||
//"aktywna, ale skład jeszcze nieznany" (seed z listy DM/grup).
|
||||
return Array.isArray(window.activeCallsByRoom[sanitizeId(roomId)]);
|
||||
}
|
||||
|
||||
//Zapisuje flagę "trwa rozmowa" z listy DM/grup (nie nadpisuje znanej listy
|
||||
//uczestników z call_update - ta jest dokładniejsza).
|
||||
function ensureActiveCallsMap() {
|
||||
//Obiekt bez prototypu - klucze pochodzą z danych sieciowych (roomId), a
|
||||
//zapis np. pod "__proto__" na zwykłym obiekcie zanieczyściłby prototyp
|
||||
if (!window.activeCallsByRoom) window.activeCallsByRoom = Object.create(null);
|
||||
return window.activeCallsByRoom;
|
||||
}
|
||||
|
||||
function seedCallStateFromList(roomId, hasCall) {
|
||||
if (!hasCall) return;
|
||||
let map = ensureActiveCallsMap();
|
||||
let key = sanitizeId(roomId);
|
||||
if (!Array.isArray(map[key])) {
|
||||
map[key] = [];
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCallIndicator(rootEl, show) {
|
||||
if (!rootEl) return;
|
||||
let ind = rootEl.querySelector(".call-indicator");
|
||||
if (ind) ind.classList.toggle("show", !!show);
|
||||
}
|
||||
|
||||
//Odświeża wskaźniki rozmów: listy DM/grup, przyciski call w top-barach
|
||||
//oraz wiersze członków w panelu details.
|
||||
//roomIds = null -> pełna synchronizacja (po renderze list),
|
||||
//roomIds = [id] -> tylko wskazane pokoje (ścieżka gorąca: call_update),
|
||||
// żeby przy dużych listach nie skanować całego DOM.
|
||||
function updateVoicePresenceUI(roomIds = null) {
|
||||
let targets = Array.isArray(roomIds) ? roomIds.map(r => sanitizeId(r)) : null;
|
||||
|
||||
[["dms-list", "dm-btn-"], ["groups-list", "group-btn-"]].forEach(([listId, prefix]) => {
|
||||
let container = document.getElementById(listId);
|
||||
if (!container) return;
|
||||
container.querySelectorAll(".room-entry").forEach(btn => {
|
||||
if (!btn.id.startsWith(prefix)) return;
|
||||
let roomId = btn.id.substring(prefix.length);
|
||||
if (targets && !targets.includes(roomId)) return;
|
||||
toggleCallIndicator(btn, roomHasActiveCall(roomId));
|
||||
});
|
||||
});
|
||||
|
||||
let currentKey = window.currentDmId ? sanitizeId(window.currentDmId) : null;
|
||||
let currentAffected = targets === null || (currentKey !== null && targets.includes(currentKey));
|
||||
if (!currentAffected) return;
|
||||
|
||||
let inCurrent = roomHasActiveCall(window.currentDmId);
|
||||
document.querySelectorAll(".room-action-call svg").forEach(svg => {
|
||||
svg.style.fill = inCurrent ? "var(--big-green)" : "";
|
||||
});
|
||||
|
||||
if (currentKey) {
|
||||
let users = (window.activeCallsByRoom && window.activeCallsByRoom[currentKey]) || [];
|
||||
let detailsBar = document.getElementById("roomdetailsbar");
|
||||
if (detailsBar) {
|
||||
detailsBar.querySelectorAll(".room-entry[data-member-id]").forEach(btn => {
|
||||
toggleCallIndicator(btn, users.includes(btn.getAttribute("data-member-id")));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Pobiera aktualny skład rozmowy dla otwartego pokoju (snapshot po otwarciu -
|
||||
//call_update przychodzi dopiero przy join/leave).
|
||||
async function refreshRoomCallState() {
|
||||
try {
|
||||
if (!window.currentDmId || !window.currentRoomType) return;
|
||||
let res = await fetchEncrypted("room/voice/state", JSON.stringify({
|
||||
string1: window.currentRoomType,
|
||||
string2: window.currentDmId
|
||||
}));
|
||||
if (!res || !res.startsWith("[") || !window.currentDmId) return;
|
||||
let participants = JSON.parse(res).map(u => String(u).split(";")[0]);
|
||||
let map = ensureActiveCallsMap();
|
||||
let key = sanitizeId(window.currentDmId);
|
||||
if (participants.length > 0) map[key] = participants;
|
||||
else delete map[key];
|
||||
updateVoicePresenceUI([key]);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function generateDmMembersHtml(targetId, targetUsername) {
|
||||
let myIdStr = id + ";" + host;
|
||||
let myUsername = await fetchAsync(`${url}/idtoname?id=${myIdStr}`);
|
||||
|
|
@ -2831,19 +2934,21 @@ async function generateDmMembersHtml(targetId, targetUsername) {
|
|||
<div class="sidebar-section-header" style="margin-top: 0.5rem; margin-bottom: 0.5rem; width: 100%;">
|
||||
<span style="padding-left: 0.6rem; font-size: 0.8rem; font-weight: bold; opacity: 0.5; text-transform: uppercase;"><blah>title.members</blah></span>
|
||||
</div>
|
||||
<button class="room-entry" onclick="openProfile('${safeTargetId}')" style="margin-bottom: 0;">
|
||||
<button class="room-entry" data-member-id="${sanitizeId(targetId.split(';')[0])}" onclick="openProfile('${safeTargetId}')" style="margin-bottom: 0;">
|
||||
<img src="${targetPfp}" class="room-pfp">
|
||||
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
|
||||
<span class="room-name">${targetDisplayName}</span>
|
||||
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${escapeHtmlText(targetUsername)}</span>
|
||||
</div>
|
||||
${getCallIndicatorHtml()}
|
||||
</button>
|
||||
<button class="room-entry" onclick="openProfile('${safeMyId}')" style="margin-bottom: 0;">
|
||||
<button class="room-entry" data-member-id="${sanitizeId(String(id))}" onclick="openProfile('${safeMyId}')" style="margin-bottom: 0;">
|
||||
<img src="${myPfp}" class="room-pfp">
|
||||
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
|
||||
<span class="room-name">${myDisplaySafe}</span>
|
||||
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${escapeHtmlText(myUsername)}</span>
|
||||
</div>
|
||||
${getCallIndicatorHtml()}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -2972,14 +3077,16 @@ async function generateGroupMembersHtml(groupId, groupName) {
|
|||
let memberDisplayName = escapeHtml(memberUsername.split(':')[0]);
|
||||
let safeMemberId = sanitizeId(memberId);
|
||||
let safeGroupId = sanitizeId(groupId);
|
||||
|
||||
let memberNumericId = sanitizeId(String(memberId).split(';')[0]);
|
||||
|
||||
html += `
|
||||
<button class="room-entry" onclick="openProfile('${safeMemberId}')" oncontextmenu="handleMemberContextMenu(event, '${safeGroupId}', '${safeMemberId}')" style="margin-bottom: 0;">
|
||||
<button class="room-entry" data-member-id="${memberNumericId}" onclick="openProfile('${safeMemberId}')" oncontextmenu="handleMemberContextMenu(event, '${safeGroupId}', '${safeMemberId}')" style="margin-bottom: 0;">
|
||||
<img src="${memberPfp}" class="room-pfp">
|
||||
<div style="display: flex; flex-direction: column; text-align: left; overflow: hidden; width: 100%; margin-left: 0.37rem;">
|
||||
<span class="room-name">${memberDisplayName}</span>
|
||||
<span class="room-id" style="font-size: 0.8rem; opacity: 0.6; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(memberUsername)}</span>
|
||||
</div>
|
||||
${getCallIndicatorHtml()}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
|
|
@ -3067,6 +3174,9 @@ async function openDm(dmId, username, targetId, type = 'dms') {
|
|||
}
|
||||
await switchDetailsContent("title.details", membersHtml);
|
||||
|
||||
updateVoicePresenceUI();
|
||||
refreshRoomCallState();
|
||||
|
||||
let msgContainer = document.getElementById("chat-messages");
|
||||
if (msgContainer) {
|
||||
msgContainer.innerHTML = `<div style="text-align: center; opacity: 0.5; padding: 1rem;"><blah>desc.messages.loading</blah></div>`;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue