g
All checks were successful
/ upload (release) Successful in 20s

This commit is contained in:
pavel 2026-02-24 01:18:00 +01:00
commit e7c60e7c47
3 changed files with 35 additions and 21 deletions

View file

@ -22,7 +22,7 @@ const state = {
muted: false, muted: false,
sharingVideo: false, sharingVideo: false,
sharingScreen: false, sharingScreen: false,
watchingVideo: true, viewMode: 'chat', // 'chat' or 'video'
iceServers: [{ urls: "stun:stun.l.google.com:19302" }], iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
}, },
voicePresencePollId: null, voicePresencePollId: null,
@ -66,10 +66,11 @@ const el = {
vcChannelName: document.getElementById("vc-channel-name"), vcChannelName: document.getElementById("vc-channel-name"),
voiceVideoBtn: document.getElementById("voice-video-btn"), voiceVideoBtn: document.getElementById("voice-video-btn"),
voiceScreenBtn: document.getElementById("voice-screen-btn"), voiceScreenBtn: document.getElementById("voice-screen-btn"),
voiceWatchBtn: document.getElementById("voice-watch-btn"),
voiceMuteBtn: document.getElementById("voice-mute-btn"), voiceMuteBtn: document.getElementById("voice-mute-btn"),
voiceLeaveBtn: document.getElementById("voice-leave-btn"), voiceLeaveBtn: document.getElementById("voice-leave-btn"),
videoGrid: document.getElementById("video-grid"), videoGrid: document.getElementById("video-grid"),
messageForm: document.getElementById("message-form"),
messageInputWrapper: document.querySelector(".chat-input-wrapper"),
// Members // Members
memberList: document.getElementById("member-list"), memberList: document.getElementById("member-list"),
@ -249,17 +250,20 @@ function renderChannels() {
state.selectedDmUserId = null; state.selectedDmUserId = null;
state.selectedDmDisplayName = null; state.selectedDmDisplayName = null;
state.selectedTextChannelId = channel.id; state.selectedTextChannelId = channel.id;
state.voice.viewMode = 'chat';
renderChannels(); renderChannels();
renderDMs(); renderDMs();
updateView();
updateHeaderLabels(); updateHeaderLabels();
const messages = await api(`/channels/${channel.id}/messages?limit=100`); const messages = await api(`/channels/${channel.id}/messages?limit=100`);
renderMessages(messages); renderMessages(messages);
} else { } else {
state.selectedDmUserId = null;
state.selectedDmDisplayName = null;
state.selectedVoiceChannelId = channel.id; state.selectedVoiceChannelId = channel.id;
state.voice.viewMode = 'video';
renderChannels(); renderChannels();
renderDMs(); renderDMs();
updateView();
updateHeaderLabels();
joinVoice(); joinVoice();
} }
if (window.innerWidth <= 768) closeMobileMenus(); if (window.innerWidth <= 768) closeMobileMenus();
@ -307,8 +311,10 @@ function renderDMs() {
state.selectedDmDisplayName = dm.display_name; state.selectedDmDisplayName = dm.display_name;
state.selectedTextChannelId = null; state.selectedTextChannelId = null;
state.selectedVoiceChannelId = null; state.selectedVoiceChannelId = null;
state.voice.viewMode = 'chat';
renderChannels(); renderChannels();
renderDMs(); renderDMs();
updateView();
updateHeaderLabels(); updateHeaderLabels();
const messages = await api(`/dms/${dm.user_id}/messages?limit=100`); const messages = await api(`/dms/${dm.user_id}/messages?limit=100`);
renderMessages(messages); renderMessages(messages);
@ -380,8 +386,10 @@ function renderMembers() {
state.selectedDmDisplayName = m.display_name; state.selectedDmDisplayName = m.display_name;
state.selectedTextChannelId = null; state.selectedTextChannelId = null;
state.selectedVoiceChannelId = null; state.selectedVoiceChannelId = null;
state.voice.viewMode = 'chat';
renderChannels(); renderChannels();
renderDMs(); renderDMs();
updateView();
updateHeaderLabels(); updateHeaderLabels();
const messages = await api(`/dms/${m.id}/messages?limit=100`); const messages = await api(`/dms/${m.id}/messages?limit=100`);
renderMessages(messages); renderMessages(messages);
@ -406,6 +414,12 @@ function updateHeaderLabels() {
return; return;
} }
if (state.voice.viewMode === 'video' && state.selectedVoiceChannelId) {
const channel = state.channels.find((c) => c.id === state.selectedVoiceChannelId);
el.channelTitle.textContent = channel ? channel.name : "Voice Arena";
return;
}
const channel = state.channels.find((c) => c.id === state.selectedTextChannelId); const channel = state.channels.find((c) => c.id === state.selectedTextChannelId);
el.channelTitle.textContent = channel ? channel.name : "Select a channel"; el.channelTitle.textContent = channel ? channel.name : "Select a channel";
el.messageBody.placeholder = channel ? `Message #${channel.name}` : "Select a channel"; el.messageBody.placeholder = channel ? `Message #${channel.name}` : "Select a channel";
@ -569,7 +583,6 @@ function stopAndClearVideoPipeline() {
state.voice.videoStream = null; state.voice.videoStream = null;
state.voice.sharingVideo = false; state.voice.sharingVideo = false;
document.getElementById(`video-${state.me.id}-camera`)?.parentElement?.remove(); document.getElementById(`video-${state.me.id}-camera`)?.parentElement?.remove();
updateVideoGridVisibility();
} }
function stopAndClearScreenPipeline() { function stopAndClearScreenPipeline() {
@ -579,15 +592,22 @@ function stopAndClearScreenPipeline() {
state.voice.screenStream = null; state.voice.screenStream = null;
state.voice.sharingScreen = false; state.voice.sharingScreen = false;
document.getElementById(`video-${state.me.id}-screen`)?.parentElement?.remove(); document.getElementById(`video-${state.me.id}-screen`)?.parentElement?.remove();
updateVideoGridVisibility();
} }
function updateVideoGridVisibility() { function updateVideoGridVisibility() {
const hasVideos = el.videoGrid.children.length > 0; // Persistence logic: we always update the grid visibility based on participants
if (hasVideos && state.voice.watchingVideo) { // however the actual container visibility in the UI is managed by updateView()
}
function updateView() {
if (state.voice.viewMode === 'video') {
el.videoGrid.classList.remove("hidden"); el.videoGrid.classList.remove("hidden");
el.messageList.classList.add("hidden");
el.messageInputWrapper.classList.add("hidden");
} else { } else {
el.videoGrid.classList.add("hidden"); el.videoGrid.classList.add("hidden");
el.messageList.classList.remove("hidden");
el.messageInputWrapper.classList.remove("hidden");
} }
} }
@ -606,7 +626,6 @@ function renderVideo(peerId, displayName, stream, source) {
videoEl = container.querySelector("video"); videoEl = container.querySelector("video");
} }
videoEl.srcObject = stream; videoEl.srcObject = stream;
updateVideoGridVisibility();
} }
async function buildAudioPipeline(rawStream) { async function buildAudioPipeline(rawStream) {
@ -815,12 +834,10 @@ async function joinVoice() {
} else if (msg.type === "video_status_changed") { } else if (msg.type === "video_status_changed") {
if (!msg.is_sharing_video) { if (!msg.is_sharing_video) {
document.getElementById(`video-${msg.user_id}-camera`)?.parentElement?.remove(); document.getElementById(`video-${msg.user_id}-camera`)?.parentElement?.remove();
updateVideoGridVisibility();
} }
} else if (msg.type === "screen_status_changed") { } else if (msg.type === "screen_status_changed") {
if (!msg.is_sharing_screen) { if (!msg.is_sharing_screen) {
document.getElementById(`video-${msg.user_id}-screen`)?.parentElement?.remove(); document.getElementById(`video-${msg.user_id}-screen`)?.parentElement?.remove();
updateVideoGridVisibility();
} }
} }
refreshVoicePresence().catch(() => { }); refreshVoicePresence().catch(() => { });
@ -1121,7 +1138,6 @@ async function init() {
el.voiceVideoBtn.onclick = toggleVideo; el.voiceVideoBtn.onclick = toggleVideo;
el.voiceScreenBtn.onclick = toggleScreenShare; el.voiceScreenBtn.onclick = toggleScreenShare;
el.voiceWatchBtn.onclick = toggleWatchVideo;
el.voiceMuteBtn.onclick = toggleMute; el.voiceMuteBtn.onclick = toggleMute;
el.voiceLeaveBtn.onclick = leaveVoice; el.voiceLeaveBtn.onclick = leaveVoice;

View file

@ -87,7 +87,6 @@
<div class="vc-actions"> <div class="vc-actions">
<button id="voice-video-btn" title="Turn on Camera"><i data-lucide="video-off"></i></button> <button id="voice-video-btn" title="Turn on Camera"><i data-lucide="video-off"></i></button>
<button id="voice-screen-btn" title="Share Screen"><i data-lucide="monitor-off"></i></button> <button id="voice-screen-btn" title="Share Screen"><i data-lucide="monitor-off"></i></button>
<button id="voice-watch-btn" title="Stop Watching"><i data-lucide="eye"></i></button>
<button id="voice-mute-btn" title="Mute"><i data-lucide="mic"></i></button> <button id="voice-mute-btn" title="Mute"><i data-lucide="mic"></i></button>
<button id="voice-leave-btn" title="Disconnect"><i data-lucide="phone-off"></i></button> <button id="voice-leave-btn" title="Disconnect"><i data-lucide="phone-off"></i></button>
</div> </div>

View file

@ -527,15 +527,13 @@ select {
/* Video Grid */ /* Video Grid */
.video-grid { .video-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 12px; gap: 16px;
padding: 12px; padding: 16px;
background: var(--bg-tertiary); background: var(--bg-tertiary);
flex-grow: 1; flex: 1;
min-height: 200px; min-height: 0;
max-height: 100%;
overflow-y: auto; overflow-y: auto;
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
} }
.video-grid.hidden { .video-grid.hidden {
@ -553,7 +551,8 @@ select {
.video-item video { .video-item video {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: contain;
background: #000;
} }
.video-item .video-label { .video-item .video-label {