Skip to content

Commit 0bc171d

Browse files
committed
feat: fullscreen
1 parent 88d62ad commit 0bc171d

3 files changed

Lines changed: 68 additions & 18 deletions

File tree

frontend/src/components/MeetingToolbar.vue

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22
<div
3-
class="w-full overflow-hidden shrink-0 transition-[height,margin] duration-300 ease-in-out"
3+
class="w-full overflow-visible shrink-0 transition-[height,margin] duration-300 ease-in-out relative z-40"
44
:style="{ height: toolbarHeight }"
55
>
66
<div
@@ -151,7 +151,11 @@
151151
ref="dropdownContainer"
152152
@click="handleDropdownClick"
153153
>
154-
<Dropdown :options="moreOptions" placement="top">
154+
<Dropdown
155+
:options="moreOptions"
156+
placement="top"
157+
:portal-to="dropdownPortalTarget"
158+
>
155159
<template #default>
156160
<Button
157161
variant="solid"
@@ -259,6 +263,10 @@ const props = defineProps({
259263
type: Object,
260264
default: null,
261265
},
266+
isFullscreen: {
267+
type: Boolean,
268+
default: false,
269+
},
262270
cameraPermissionGranted: {
263271
type: Boolean,
264272
default: false,
@@ -282,6 +290,7 @@ const emit = defineEmits([
282290
"toggle-microphone",
283291
"toggle-camera",
284292
"toggle-screen-share",
293+
"toggle-fullscreen",
285294
"toggle-raise-hand",
286295
"end-call",
287296
"device-changed",
@@ -290,6 +299,9 @@ const emit = defineEmits([
290299
291300
const { windowWidth } = useResponsiveGrid();
292301
const isMobile = computed(() => windowWidth.value < 768);
302+
const dropdownPortalTarget = computed(() =>
303+
props.isFullscreen ? "#meetingContainer" : "body",
304+
);
293305
294306
const moreOptions = computed(() => [
295307
{
@@ -308,6 +320,14 @@ const moreOptions = computed(() => [
308320
resetHideTimer();
309321
},
310322
},
323+
{
324+
icon: props.isFullscreen ? "minimize" : "maximize",
325+
label: props.isFullscreen ? "Exit full screen" : "Enter full screen",
326+
onClick: () => {
327+
emit("toggle-fullscreen");
328+
resetHideTimer();
329+
},
330+
},
311331
...(isMobile.value
312332
? [
313333
{

frontend/src/components/ReactionPicker.vue

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
<slot name="trigger" />
55
</PopoverTrigger>
66

7-
<PopoverPortal>
8-
<PopoverContent
9-
:side="'top'"
10-
:align="'center'"
11-
:side-offset="12"
12-
class="bg-black/90 rounded-2xl p-3 shadow-xl border border-white/10 max-w-sm w-full z-50"
13-
>
7+
<PopoverContent
8+
:side="'top'"
9+
:align="'center'"
10+
:side-offset="12"
11+
class="bg-black/90 rounded-2xl p-3 shadow-xl border border-white/10 max-w-sm w-full z-[70]"
12+
>
1413
<div class="text-center">
1514
<div class="grid grid-cols-5 gap-3 mb-4">
1615
<button
@@ -32,18 +31,12 @@
3231
{{ isHandRaised ? "Lower Hand" : "Raise Hand" }}
3332
</button>
3433
</div>
35-
</PopoverContent>
36-
</PopoverPortal>
34+
</PopoverContent>
3735
</PopoverRoot>
3836
</template>
3937

4038
<script setup>
41-
import {
42-
PopoverContent,
43-
PopoverPortal,
44-
PopoverRoot,
45-
PopoverTrigger,
46-
} from "reka-ui";
39+
import { PopoverContent, PopoverRoot, PopoverTrigger } from "reka-ui";
4740
4841
const props = defineProps({
4942
isOpen: {

frontend/src/pages/Meeting.vue

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
<template>
2-
<div class="h-[100dvh] bg-gray-900 flex flex-col" data-meeting-component>
2+
<div
3+
ref="meetingContainer"
4+
class="h-[100dvh] bg-gray-900 flex flex-col"
5+
data-meeting-component
6+
id="meetingContainer"
7+
>
38
<!-- Loading state -->
49
<div v-if="isConnecting" class="flex-1 flex items-center justify-center">
510
<div class="flex items-center justify-center text-white space-x-4">
@@ -65,6 +70,7 @@
6570
:isMicOn="meetingState.isMicOn.value"
6671
:isCameraOn="meetingState.isCameraOn.value"
6772
:isScreenSharing="meetingState.isScreenSharing.value"
73+
:isFullscreen="isFullscreen"
6874
:isHandRaised="isHandRaised"
6975
:isReactionPickerOpen="isReactionPickerOpen"
7076
@update:isReactionPickerOpen="isReactionPickerOpen = $event"
@@ -79,6 +85,7 @@
7985
@toggle-microphone="toggleMicrophone"
8086
@toggle-camera="toggleCamera"
8187
@toggle-screen-share="toggleScreenShare"
88+
@toggle-fullscreen="toggleFullscreen"
8289
@toggle-raise-hand="toggleRaiseHand"
8390
@end-call="endCall"
8491
@device-changed="handleDeviceChanged"
@@ -356,6 +363,8 @@ const lobbyUsersForNotifications = computed(() => {
356363
// Refs
357364
const chatNotificationQueue = ref(null);
358365
const isReactionPickerOpen = ref(false);
366+
const meetingContainer = ref(null);
367+
const isFullscreen = ref(false);
359368
360369
// Methods
361370
const resetToPreview = () => {
@@ -574,6 +583,31 @@ const handleNotificationClick = () => {
574583
}
575584
};
576585
586+
const syncFullscreenState = () => {
587+
isFullscreen.value = !!document.fullscreenElement;
588+
};
589+
590+
const toggleFullscreen = async () => {
591+
try {
592+
if (!document.fullscreenElement) {
593+
const targetElement = meetingContainer.value;
594+
595+
if (targetElement?.requestFullscreen) {
596+
await targetElement.requestFullscreen();
597+
}
598+
return;
599+
}
600+
601+
if (document.exitFullscreen) {
602+
await document.exitFullscreen();
603+
}
604+
} catch (error) {
605+
console.error("Failed to toggle fullscreen:", error);
606+
} finally {
607+
syncFullscreenState();
608+
}
609+
};
610+
577611
const setSinkIdOnVideoElements = async (sinkId) => {
578612
// Set speaker output on all video elements
579613
const videoElements = document.querySelectorAll("video");
@@ -663,6 +697,8 @@ const handleDeviceChanged = async (event) => {
663697
onMounted(async () => {
664698
window.addEventListener("keydown", handleKeyDown);
665699
window.addEventListener("keyup", handleKeyUp);
700+
document.addEventListener("fullscreenchange", syncFullscreenState);
701+
syncFullscreenState();
666702
667703
// Clear any stale error/connection state from previous navigations
668704
if (typeof meetingState.resetConnectionState === "function") {
@@ -743,6 +779,7 @@ onMounted(async () => {
743779
onUnmounted(() => {
744780
window.removeEventListener("keydown", handleKeyDown);
745781
window.removeEventListener("keyup", handleKeyUp);
782+
document.removeEventListener("fullscreenchange", syncFullscreenState);
746783
747784
// Cleanup will be handled by the meeting logic composable
748785
});

0 commit comments

Comments
 (0)