From 815f277cc37da5d8717bd31f4dd80cc0ce6fe8b0 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Wed, 1 Jul 2026 23:23:12 +0000 Subject: [PATCH] Show non-spotlight participants in the plenary audience strip. Include speakers and attendees below the stage, resolve avatars with the same fallbacks as video tiles, and refresh the strip when poll data updates. Co-authored-by: Cursor --- resources/css/app.css | 4 ++ resources/js/meet-room.js | 105 ++++++++++++++++++++++++++++---------- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/resources/css/app.css b/resources/css/app.css index 3d2f3b0..674431b 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -201,6 +201,10 @@ html.meet-room-page { white-space: nowrap; } +.meet-room-page .meet-audience-avatar--speaker { + border-color: rgb(139 92 246 / 0.55); +} + .meet-room-page .meet-audio-grid { place-content: center; place-items: center; diff --git a/resources/js/meet-room.js b/resources/js/meet-room.js index 5d26ee1..04c0ef6 100644 --- a/resources/js/meet-room.js +++ b/resources/js/meet-room.js @@ -550,24 +550,30 @@ function meetRoom() { return person?.display_name || participant.name || participant.identity; }, - avatarUrlForParticipant(participant) { - const person = this.participantForIdentity(participant.identity); + avatarUrlForPerson(person) { + if (!person) { + return null; + } - if (person?.avatar_url) { + if (person.avatar_url) { return person.avatar_url; } - if (person && ['host', 'co_host'].includes(person.role) && this.roomHost?.avatar_url) { + if (['host', 'co_host'].includes(person.role) && this.roomHost?.avatar_url) { return this.roomHost.avatar_url; } - if (this.roomHost?.user_ref && person?.user_ref === this.roomHost.user_ref && this.roomHost.avatar_url) { + if (this.roomHost?.user_ref && person.user_ref === this.roomHost.user_ref && this.roomHost.avatar_url) { return this.roomHost.avatar_url; } return null; }, + avatarUrlForParticipant(participant) { + return this.avatarUrlForPerson(this.participantForIdentity(participant.identity)); + }, + updateTileAvatarCircle(tile, participant) { const circle = tile.querySelector('[data-tile-avatar-circle]'); if (!circle) { @@ -662,6 +668,71 @@ function meetRoom() { return [...this.audienceParticipants()].sort((a, b) => a.display_name.localeCompare(b.display_name)); }, + resolveSpotlightParticipantUuid() { + const spotlightRef = this.resolveSpotlightSpeakerRef(); + if (spotlightRef) { + const match = this.speakerParticipants().find((person) => person.user_ref === spotlightRef); + if (match) { + return match.uuid; + } + } + + return this.speakerParticipantsSorted()[0]?.uuid || null; + }, + + plenaryStripParticipants() { + const spotlightUuid = this.resolveSpotlightParticipantUuid(); + + return this.inCallParticipants() + .filter((person) => person.uuid !== spotlightUuid) + .sort((a, b) => { + const aSpeaker = this.isSpeakerPerson(a); + const bSpeaker = this.isSpeakerPerson(b); + + if (aSpeaker !== bSpeaker) { + return aSpeaker ? -1 : 1; + } + + return a.display_name.localeCompare(b.display_name); + }); + }, + + createStripInitials(person) { + const initials = document.createElement('span'); + initials.className = 'meet-audience-avatar__initials'; + initials.style.background = this.avatarStyle(person.display_name).background; + initials.textContent = this.participantInitials(person.display_name); + + return initials; + }, + + appendStripAvatarItem(strip, person, index, total) { + const item = document.createElement('div'); + item.className = 'meet-audience-avatar'; + if (this.isSpeakerPerson(person)) { + item.classList.add('meet-audience-avatar--speaker'); + } + item.style.zIndex = String(total - index); + item.title = person.display_name; + + const avatarUrl = this.avatarUrlForPerson(person); + + if (avatarUrl) { + const img = document.createElement('img'); + img.src = avatarUrl; + img.alt = ''; + img.className = 'meet-audience-avatar__image'; + img.addEventListener('error', () => { + img.replaceWith(this.createStripInitials(person)); + }, { once: true }); + item.appendChild(img); + } else { + item.appendChild(this.createStripInitials(person)); + } + + strip.appendChild(item); + }, + resolveSpotlightSpeakerRef() { const speakers = this.speakerParticipants(); @@ -773,7 +844,7 @@ function meetRoom() { return; } - const audience = this.audienceParticipantsSorted(); + const audience = this.plenaryStripParticipants(); if (audience.length === 0) { return; } @@ -783,26 +854,7 @@ function meetRoom() { const overflow = audience.length - visible.length; visible.forEach((person, index) => { - const item = document.createElement('div'); - item.className = 'meet-audience-avatar'; - item.style.zIndex = String(visible.length - index); - item.title = person.display_name; - - if (person.avatar_url) { - const img = document.createElement('img'); - img.src = person.avatar_url; - img.alt = ''; - img.className = 'meet-audience-avatar__image'; - item.appendChild(img); - } else { - const initials = document.createElement('span'); - initials.className = 'meet-audience-avatar__initials'; - initials.style.background = this.avatarStyle(person.display_name).background; - initials.textContent = this.participantInitials(person.display_name); - item.appendChild(initials); - } - - strip.appendChild(item); + this.appendStripAvatarItem(strip, person, index, visible.length); }); if (overflow > 0) { @@ -1811,6 +1863,7 @@ function meetRoom() { this.sessionParticipants = data.participants; this.pruneTilesFromSession(); this.refreshParticipantTileAvatars(); + this.renderAudienceStrip(); } if (typeof data.uses_stage_layout === 'boolean' || typeof data.stage_layout === 'string') { this.mergeStageConfig(data);