Make plenary vs panel layout visible and explicit in Meet.
Deploy Ladill Meet / deploy (push) Successful in 45s
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:
@@ -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';
|
||||
},
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user