Make plenary vs panel layout visible and explicit in Meet.
Deploy Ladill Meet / deploy (push) Successful in 45s

Show the current stage mode to everyone, redesign the layout picker with clear descriptions, and apply layout changes only when the host saves.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-01 23:28:47 +00:00
co-authored by Cursor
parent 815f277cc3
commit f812a92cf9
2 changed files with 152 additions and 26 deletions
+98 -2
View File
@@ -52,6 +52,7 @@ function meetRoom() {
spotlightSpeakerRef: '',
panelSetupOpen: false,
savingStageLayout: false,
_stageSnapshot: null,
qaItems: [],
activePolls: [],
breakoutBroadcast: '',
@@ -932,8 +933,9 @@ function meetRoom() {
}
const data = await res.json();
this.mergeStageConfig(data);
this._stageSnapshot = null;
this.panelSetupOpen = false;
this.mergeStageConfig(data);
this.applyStageLayout();
} finally {
this.savingStageLayout = false;
@@ -981,7 +983,7 @@ function meetRoom() {
setActivePanel(panelId) {
this.activePanelId = panelId;
this.stageLayout = 'panel';
this.applyStageLayout();
this.panelDiscussions = true;
},
togglePanelSpeaker(panel, userRef) {
@@ -1891,6 +1893,100 @@ function meetRoom() {
return this.isConference ? 'conference' : 'meeting';
},
stageLayoutLabel() {
return this.stageLayout === 'panel' ? 'Panel discussion' : 'Plenary';
},
conferenceStatusLabel() {
if (this.isGreenRoom) {
if (this.usesStageLayout) {
return `Green room · ${this.stageLayoutLabel()}`;
}
return 'Green room';
}
if (!this.usesStageLayout) {
return 'Conference live';
}
return this.stageLayout === 'panel' ? 'Panel discussion live' : 'Plenary live';
},
conferenceStatusClass() {
if (this.isGreenRoom) {
return 'text-violet-400/90';
}
if (this.stageLayout === 'panel') {
return 'text-violet-400/90';
}
return 'text-sky-400/90';
},
stageLayoutDescription() {
if (this.stageLayout === 'panel') {
const panelName = this.activePanelName();
if (panelName) {
return `Showing ${panelName} — multiple speakers in a split grid.`;
}
return 'Multiple speakers shown together in a split-screen grid.';
}
return 'One main speaker on stage. Others appear in the strip below.';
},
activePanelName() {
if (!this.activePanelId) {
return '';
}
const panel = this.panels.find((entry) => entry.id === this.activePanelId);
return panel?.name || '';
},
openPanelSetup() {
this._stageSnapshot = {
stageLayout: this.stageLayout,
panels: JSON.parse(JSON.stringify(this.panels)),
activePanelId: this.activePanelId,
panelDiscussions: this.panelDiscussions,
};
this.panelSetupOpen = true;
},
cancelPanelSetup() {
if (this._stageSnapshot) {
this.stageLayout = this._stageSnapshot.stageLayout;
this.panels = JSON.parse(JSON.stringify(this._stageSnapshot.panels));
this.activePanelId = this._stageSnapshot.activePanelId;
this.panelDiscussions = this._stageSnapshot.panelDiscussions;
this.applyStageLayout();
}
this._stageSnapshot = null;
this.panelSetupOpen = false;
},
selectStageLayout(layout) {
this.stageLayout = layout;
if (layout === 'panel') {
this.panelDiscussions = true;
if (this.panels.length === 0) {
this.addPanel();
}
if (!this.activePanelId && this.panels[0]) {
this.activePanelId = this.panels[0].id;
}
}
},
sessionNounTitle() {
return this.isConference ? 'Conference' : 'Meeting';
},
+54 -24
View File
@@ -102,8 +102,8 @@
<p class="text-[10px] font-medium uppercase tracking-wide text-emerald-400/90">Audio room</p>
@elseif ($isConference ?? false)
<p class="text-[10px] font-medium uppercase tracking-wide"
:class="isGreenRoom ? 'text-violet-400/90' : 'text-sky-400/90'"
x-text="isGreenRoom ? 'Green room' : 'Conference live'"></p>
:class="conferenceStatusClass()"
x-text="conferenceStatusLabel()"></p>
@endif
<p class="text-xs text-slate-400" x-text="connectionStatus"></p>
<p class="min-h-4 truncate text-xs font-medium leading-4"
@@ -147,12 +147,16 @@
<div class="min-w-0 text-sm text-violet-100">
<p class="font-medium">Green room</p>
<p class="truncate text-xs text-violet-200/90">Only hosts and speakers are visible. Attendees join after you go live.</p>
<p x-show="usesStageLayout" class="mt-1 text-xs text-violet-200/90">
Stage layout:
<span class="font-semibold text-violet-50" x-text="stageLayoutLabel()"></span>
</p>
</div>
<div class="flex shrink-0 flex-wrap items-center gap-2">
<button x-show="isHost && usesStageLayout" @click="panelSetupOpen = true" type="button"
<button x-show="isHost && usesStageLayout" @click="openPanelSetup()" type="button"
class="shrink-0 rounded-lg border border-violet-400/40 bg-violet-500/20 px-3 py-1.5 text-xs font-semibold text-violet-100 transition-colors hover:bg-violet-500/30">
@include('meet.room.partials.meet-icon', ['icon' => 'panel', 'class' => 'mr-1 inline h-3.5 w-3.5'])
Panels
Stage layout
</button>
<button x-show="isHost" @click="goLive()" type="button" :disabled="goingLive"
class="shrink-0 rounded-lg bg-violet-500 px-3 py-1.5 text-xs font-semibold text-white transition-colors hover:bg-violet-400 disabled:opacity-60">
@@ -161,12 +165,28 @@
</div>
</div>
<div x-show="isHost && usesStageLayout && panelDiscussions && !isGreenRoom" x-cloak
class="flex shrink-0 items-center justify-end gap-2 border-b border-violet-500/20 bg-violet-500/10 px-4 py-2">
<button @click="panelSetupOpen = true" type="button"
class="inline-flex items-center gap-1.5 rounded-lg border border-violet-400/40 bg-violet-500/20 px-3 py-1.5 text-xs font-semibold text-violet-100 hover:bg-violet-500/30">
<div x-show="usesStageLayout && !isGreenRoom" x-cloak
class="flex shrink-0 flex-col gap-2 border-b px-4 py-2.5 sm:flex-row sm:items-center sm:justify-between sm:gap-3"
:class="stageLayout === 'panel' ? 'border-violet-500/25 bg-violet-500/10' : 'border-sky-500/25 bg-sky-500/10'">
<div class="flex min-w-0 items-start gap-2.5">
<span class="mt-1.5 flex h-2.5 w-2.5 shrink-0 rounded-full"
:class="stageLayout === 'panel' ? 'bg-violet-400' : 'bg-sky-400'"></span>
<div class="min-w-0">
<p class="text-sm font-semibold"
:class="stageLayout === 'panel' ? 'text-violet-100' : 'text-sky-100'"
x-text="stageLayoutLabel()"></p>
<p class="truncate text-xs"
:class="stageLayout === 'panel' ? 'text-violet-200/85' : 'text-sky-200/85'"
x-text="stageLayoutDescription()"></p>
</div>
</div>
<button x-show="isHost" @click="openPanelSetup()" type="button"
class="inline-flex shrink-0 items-center gap-1.5 self-start rounded-lg border px-3 py-1.5 text-xs font-semibold transition-colors sm:self-center"
:class="stageLayout === 'panel'
? 'border-violet-400/40 bg-violet-500/20 text-violet-100 hover:bg-violet-500/30'
: 'border-sky-400/40 bg-sky-500/20 text-sky-100 hover:bg-sky-500/30'">
@include('meet.room.partials.meet-icon', ['icon' => 'panel', 'class' => 'h-3.5 w-3.5'])
<span x-text="stageLayout === 'panel' ? 'Panel layout' : 'Plenary layout'"></span>
Change layout
</button>
</div>
@@ -506,31 +526,41 @@
<div x-show="panelSetupOpen" x-cloak
class="fixed inset-0 z-50 flex items-end justify-center bg-black/60 p-4 backdrop-blur-sm sm:items-center"
@keydown.escape.window="panelSetupOpen = false">
<div class="w-full max-w-lg rounded-2xl bg-slate-900 p-5 shadow-2xl" @click.outside="panelSetupOpen = false">
@keydown.escape.window="cancelPanelSetup()">
<div class="w-full max-w-lg rounded-2xl bg-slate-900 p-5 shadow-2xl" @click.outside="cancelPanelSetup()">
<div class="flex items-start justify-between gap-3">
<div>
<h3 class="text-base font-semibold text-white">Stage & panels</h3>
<p class="mt-1 text-sm text-slate-400">Switch between plenary and panel layouts. Assign speakers to each panel before going live.</p>
<h3 class="text-base font-semibold text-white">Stage layout</h3>
<p class="mt-1 text-sm text-slate-400">Choose how speakers appear on stage. Changes apply when you save.</p>
</div>
<button @click="panelSetupOpen = false" type="button" class="rounded-lg p-1 text-slate-400 hover:bg-slate-800 hover:text-white">
<button @click="cancelPanelSetup()" type="button" class="rounded-lg p-1 text-slate-400 hover:bg-slate-800 hover:text-white">
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
</button>
</div>
<div class="mt-5 flex gap-2">
<button type="button" @click="stageLayout = 'plenary'; applyStageLayout()"
class="flex-1 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors"
:class="stageLayout === 'plenary' ? 'bg-violet-600 text-white' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'">
Plenary
<div class="mt-5 grid gap-3 sm:grid-cols-2">
<button type="button" @click="selectStageLayout('plenary')"
class="rounded-xl border p-4 text-left transition-colors"
:class="stageLayout === 'plenary'
? 'border-sky-500/60 bg-sky-500/10 ring-1 ring-sky-500/40'
: 'border-slate-700/60 bg-slate-950/60 hover:border-slate-600'">
<p class="text-sm font-semibold text-white">Plenary</p>
<p class="mt-1 text-xs leading-relaxed text-slate-400">One main speaker fills the stage. Other speakers and attendees appear in the strip below.</p>
</button>
<button type="button" @click="panelDiscussions = true; stageLayout = 'panel'; applyStageLayout()"
class="flex-1 rounded-xl px-3 py-2.5 text-sm font-medium transition-colors"
:class="stageLayout === 'panel' ? 'bg-violet-600 text-white' : 'bg-slate-800 text-slate-300 hover:bg-slate-700'">
Panel discussion
<button type="button" @click="selectStageLayout('panel')"
class="rounded-xl border p-4 text-left transition-colors"
:class="stageLayout === 'panel'
? 'border-violet-500/60 bg-violet-500/10 ring-1 ring-violet-500/40'
: 'border-slate-700/60 bg-slate-950/60 hover:border-slate-600'">
<p class="text-sm font-semibold text-white">Panel discussion</p>
<p class="mt-1 text-xs leading-relaxed text-slate-400">Multiple speakers shown together in a split-screen grid. Assign speakers to each panel below.</p>
</button>
</div>
<p x-show="stageLayout === 'panel'" x-cloak class="mt-3 rounded-lg border border-violet-500/30 bg-violet-500/10 px-3 py-2 text-xs text-violet-100">
Switching to panel discussion changes the live stage to a multi-speaker grid for everyone in the room.
</p>
<div x-show="stageLayout === 'panel'" x-cloak class="mt-5 space-y-3">
<template x-for="(panel, index) in panels" :key="panel.id">
<div class="rounded-xl border border-slate-700/60 bg-slate-950/60 p-3">
@@ -566,7 +596,7 @@
</div>
<div class="mt-6 flex justify-end gap-2">
<button type="button" @click="panelSetupOpen = false"
<button type="button" @click="cancelPanelSetup()"
class="rounded-xl px-4 py-2 text-sm font-medium text-slate-300 hover:bg-slate-800">Cancel</button>
<button type="button" @click="saveStageLayout()" :disabled="savingStageLayout"
class="rounded-xl bg-violet-600 px-4 py-2 text-sm font-medium text-white hover:bg-violet-500 disabled:opacity-60">