Fix Meet room tile layout and show account avatars when camera is off.
Deploy Ladill Meet / deploy (push) Successful in 50s
Deploy Ladill Meet / deploy (push) Successful in 50s
Move the participant name strip below the speaker area instead of overlaying it, and use Ladill profile photos on no-video tiles before falling back to initials. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+32
-9
@@ -59,16 +59,46 @@ html.meet-room-page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.meet-room-page .meet-stage--plenary .meet-stage-spotlight {
|
.meet-room-page .meet-stage--plenary .meet-stage-spotlight {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 0;
|
||||||
min-height: min(70vh, 100%);
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meet-room-page .meet-stage--plenary .meet-stage-spotlight-tile {
|
.meet-room-page .meet-stage--plenary .meet-stage-spotlight-tile {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
aspect-ratio: auto;
|
aspect-ratio: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.meet-room-page [data-participant-tile] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meet-room-page [data-tile-body] {
|
||||||
|
position: relative;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meet-room-page [data-tile-footer] {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meet-room-page .meet-audience-strip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: stretch;
|
||||||
|
min-height: 2.75rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
padding: 0.5rem 0 0.25rem;
|
||||||
|
border-top: 1px solid rgb(30 41 59 / 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
.meet-room-page .meet-stage--panel #meet-stage-spotlight,
|
.meet-room-page .meet-stage--panel #meet-stage-spotlight,
|
||||||
.meet-room-page .meet-stage--panel #meet-audience-strip {
|
.meet-room-page .meet-stage--panel #meet-audience-strip {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -87,13 +117,6 @@ html.meet-room-page {
|
|||||||
min-height: 12rem;
|
min-height: 12rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.meet-room-page .meet-audience-strip {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
min-height: 2.75rem;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.meet-room-page .meet-audience-avatar {
|
.meet-room-page .meet-audience-avatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
+89
-12
@@ -540,6 +540,82 @@ function meetRoom() {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
participantForIdentity(identity) {
|
||||||
|
return this.sessionParticipants.find((entry) => entry.uuid === identity);
|
||||||
|
},
|
||||||
|
|
||||||
|
displayNameForParticipant(participant) {
|
||||||
|
const person = this.participantForIdentity(participant.identity);
|
||||||
|
|
||||||
|
return person?.display_name || participant.name || participant.identity;
|
||||||
|
},
|
||||||
|
|
||||||
|
avatarUrlForParticipant(participant) {
|
||||||
|
const person = this.participantForIdentity(participant.identity);
|
||||||
|
|
||||||
|
if (person?.avatar_url) {
|
||||||
|
return person.avatar_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (person && ['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) {
|
||||||
|
return this.roomHost.avatar_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
updateTileAvatarCircle(tile, participant) {
|
||||||
|
const circle = tile.querySelector('[data-tile-avatar-circle]');
|
||||||
|
if (!circle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayName = this.displayNameForParticipant(participant);
|
||||||
|
const avatarUrl = this.avatarUrlForParticipant(participant);
|
||||||
|
const isAudioTile = tile.classList.contains('meet-audio-tile');
|
||||||
|
const sizeClass = isAudioTile ? 'h-[4.5rem] w-[4.5rem] text-xl' : 'h-20 w-20 text-2xl';
|
||||||
|
|
||||||
|
circle.replaceChildren();
|
||||||
|
|
||||||
|
if (avatarUrl) {
|
||||||
|
circle.className = `flex ${sizeClass} shrink-0 items-center justify-center overflow-hidden rounded-full shadow-lg`;
|
||||||
|
circle.style.background = '';
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = avatarUrl;
|
||||||
|
img.alt = '';
|
||||||
|
img.className = 'h-full w-full object-cover';
|
||||||
|
circle.appendChild(img);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hue = this.avatarHue(displayName);
|
||||||
|
circle.className = `flex ${sizeClass} shrink-0 items-center justify-center rounded-full font-semibold text-white shadow-lg`;
|
||||||
|
circle.style.background = `linear-gradient(135deg, hsl(${hue} 45% 42%), hsl(${(hue + 40) % 360} 50% 28%))`;
|
||||||
|
circle.textContent = this.participantInitials(displayName);
|
||||||
|
},
|
||||||
|
|
||||||
|
refreshParticipantTileAvatars() {
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[room.localParticipant, ...room.remoteParticipants.values()].forEach((participant) => {
|
||||||
|
const tile = this.findParticipantTile(participant.identity);
|
||||||
|
if (tile) {
|
||||||
|
this.updateTileAvatarCircle(tile, participant);
|
||||||
|
tile.querySelector('[data-tile-name]')?.replaceChildren(
|
||||||
|
document.createTextNode(this.displayNameForParticipant(participant)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
isSpeakerPerson(person) {
|
isSpeakerPerson(person) {
|
||||||
if (!person) return false;
|
if (!person) return false;
|
||||||
|
|
||||||
@@ -1020,14 +1096,16 @@ function meetRoom() {
|
|||||||
tile = document.createElement('div');
|
tile = document.createElement('div');
|
||||||
tile.dataset.participantTile = identity;
|
tile.dataset.participantTile = identity;
|
||||||
tile.className = this.audioOnly
|
tile.className = this.audioOnly
|
||||||
? 'meet-audio-tile relative flex aspect-square w-full max-w-[9rem] flex-col items-center justify-center overflow-hidden rounded-2xl bg-slate-900'
|
? 'meet-audio-tile relative flex aspect-square w-full max-w-[9rem] flex-col overflow-hidden rounded-2xl bg-slate-900'
|
||||||
: 'relative aspect-video w-full overflow-hidden rounded-xl bg-slate-900';
|
: 'relative flex aspect-video w-full flex-col overflow-hidden rounded-xl bg-slate-900 min-h-0';
|
||||||
tile.innerHTML = `
|
tile.innerHTML = `
|
||||||
<div data-tile-stage class="absolute inset-0 flex items-center justify-center"></div>
|
<div data-tile-body class="relative min-h-0 flex-1">
|
||||||
<div data-tile-avatar class="absolute inset-0 flex flex-col items-center justify-center gap-3 px-4">
|
<div data-tile-stage class="absolute inset-0 flex items-center justify-center"></div>
|
||||||
<div data-tile-avatar-circle class="flex h-20 w-20 items-center justify-center rounded-full text-2xl font-semibold text-white shadow-lg"></div>
|
<div data-tile-avatar class="absolute inset-0 flex items-center justify-center">
|
||||||
|
<div data-tile-avatar-circle class="flex h-20 w-20 shrink-0 items-center justify-center rounded-full text-2xl font-semibold text-white shadow-lg"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute inset-x-0 bottom-0 flex items-center justify-between gap-2 bg-gradient-to-t from-black/80 via-black/40 to-transparent px-3 pb-2 pt-8">
|
<div data-tile-footer class="relative flex shrink-0 items-center justify-between gap-2 border-t border-slate-800/80 bg-slate-950/95 px-3 py-2">
|
||||||
<span data-tile-name class="truncate text-xs font-medium text-white"></span>
|
<span data-tile-name class="truncate text-xs font-medium text-white"></span>
|
||||||
<div class="flex shrink-0 items-center gap-1.5">
|
<div class="flex shrink-0 items-center gap-1.5">
|
||||||
<span data-tile-speaking class="hidden meet-speaking-badge rounded-full bg-emerald-500/90 p-1 text-white" title="Speaking">
|
<span data-tile-speaking class="hidden meet-speaking-badge rounded-full bg-emerald-500/90 p-1 text-white" title="Speaking">
|
||||||
@@ -1053,13 +1131,10 @@ function meetRoom() {
|
|||||||
|
|
||||||
tile.classList.toggle('meet-stage-spotlight-tile', container.id === 'meet-stage-spotlight');
|
tile.classList.toggle('meet-stage-spotlight-tile', container.id === 'meet-stage-spotlight');
|
||||||
tile.classList.toggle('meet-stage-panel-tile', container.id === 'video-grid' && this.stageLayout === 'panel');
|
tile.classList.toggle('meet-stage-panel-tile', container.id === 'video-grid' && this.stageLayout === 'panel');
|
||||||
|
tile.classList.toggle('aspect-video', container.id !== 'meet-stage-spotlight' && !this.audioOnly);
|
||||||
|
|
||||||
const displayName = participant.name || participant.identity;
|
this.updateTileAvatarCircle(tile, participant);
|
||||||
const hue = this.avatarHue(displayName);
|
tile.querySelector('[data-tile-name]').textContent = this.displayNameForParticipant(participant);
|
||||||
const circle = tile.querySelector('[data-tile-avatar-circle]');
|
|
||||||
circle.style.background = `linear-gradient(135deg, hsl(${hue} 45% 42%), hsl(${(hue + 40) % 360} 50% 28%))`;
|
|
||||||
circle.textContent = this.participantInitials(displayName);
|
|
||||||
tile.querySelector('[data-tile-name]').textContent = displayName;
|
|
||||||
|
|
||||||
const speaking = this.activeSpeakerIds.includes(identity);
|
const speaking = this.activeSpeakerIds.includes(identity);
|
||||||
tile.classList.toggle('meet-tile-speaking', speaking);
|
tile.classList.toggle('meet-tile-speaking', speaking);
|
||||||
@@ -1100,6 +1175,7 @@ function meetRoom() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
avatar.classList.remove('hidden');
|
avatar.classList.remove('hidden');
|
||||||
|
this.updateTileAvatarCircle(tile, participant);
|
||||||
stage.innerHTML = '';
|
stage.innerHTML = '';
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1727,6 +1803,7 @@ function meetRoom() {
|
|||||||
if (data.participants) {
|
if (data.participants) {
|
||||||
this.sessionParticipants = data.participants;
|
this.sessionParticipants = data.participants;
|
||||||
this.pruneTilesFromSession();
|
this.pruneTilesFromSession();
|
||||||
|
this.refreshParticipantTileAvatars();
|
||||||
}
|
}
|
||||||
if (typeof data.uses_stage_layout === 'boolean' || typeof data.stage_layout === 'string') {
|
if (typeof data.uses_stage_layout === 'boolean' || typeof data.stage_layout === 'string') {
|
||||||
this.mergeStageConfig(data);
|
this.mergeStageConfig(data);
|
||||||
|
|||||||
@@ -217,8 +217,8 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main class="relative min-h-0 min-w-0 flex-1 p-4">
|
<main class="relative flex min-h-0 min-w-0 flex-1 flex-col p-4 pb-2">
|
||||||
<div id="meet-stage" class="flex h-full min-h-0 flex-col gap-3" :class="usesStageLayout ? 'meet-stage meet-stage--' + stageLayout : ''">
|
<div id="meet-stage" class="flex h-full min-h-0 flex-1 flex-col gap-3" :class="usesStageLayout ? 'meet-stage meet-stage--' + stageLayout : ''">
|
||||||
<div id="meet-stage-spotlight" class="meet-stage-spotlight relative min-h-0 flex-1 overflow-hidden rounded-xl bg-slate-900"></div>
|
<div id="meet-stage-spotlight" class="meet-stage-spotlight relative min-h-0 flex-1 overflow-hidden rounded-xl bg-slate-900"></div>
|
||||||
<div id="video-grid" class="grid min-h-0 gap-2 sm:grid-cols-2 lg:grid-cols-3" :class="audioOnly ? 'meet-audio-grid' : (usesStageLayout ? 'meet-stage-panel-grid flex-1' : 'h-full')"></div>
|
<div id="video-grid" class="grid min-h-0 gap-2 sm:grid-cols-2 lg:grid-cols-3" :class="audioOnly ? 'meet-audio-grid' : (usesStageLayout ? 'meet-stage-panel-grid flex-1' : 'h-full')"></div>
|
||||||
<div id="meet-audience-strip" class="meet-audience-strip shrink-0"></div>
|
<div id="meet-audience-strip" class="meet-audience-strip shrink-0"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user