From 28a3d72fad91de3c4f29ae12e853e8905f244c3b Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 8 Jun 2026 13:41:36 +0200 Subject: [PATCH] (feat): remove homepage dev buttons (fix): some call related bugs (qol): update todo --- apps/web/src/components/navbar.tsx | 29 +++--- apps/web/src/routes/app/home.tsx | 11 -- packages/call/src/components/actions.tsx | 7 +- packages/call/src/components/top.tsx | 2 +- packages/call/src/screen.tsx | 122 ++++++++++++++++++++++- packages/call/src/store.tsx | 1 + packages/call/src/views/main/layout.tsx | 31 +++++- packages/call/todo.md | 1 + 8 files changed, 175 insertions(+), 29 deletions(-) diff --git a/apps/web/src/components/navbar.tsx b/apps/web/src/components/navbar.tsx index cb83cb6..7550b28 100644 --- a/apps/web/src/components/navbar.tsx +++ b/apps/web/src/components/navbar.tsx @@ -33,8 +33,6 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { const isMobile = useIsMobile(); - const [selectOpen, setSelectOpen] = useState(false); - const [userInfoOpen, setUserInfoOpen] = useState(false); return ( @@ -86,7 +84,9 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { textDecorationLine: "none", }} > -

{user?.display}

+

+ {user?.display} +

} /> @@ -131,15 +131,20 @@ export default function Navbar({ forMobile }: { forMobile: boolean }) { ) : ( <> - - + ( + + )} + /> + {currentCalls.map((call) => ( - - ); } diff --git a/packages/call/src/components/actions.tsx b/packages/call/src/components/actions.tsx index 0aa7220..ad990c3 100644 --- a/packages/call/src/components/actions.tsx +++ b/packages/call/src/components/actions.tsx @@ -53,10 +53,13 @@ export default function Actions() { }, [screenRef]); const toggleFullscreen = async () => { + const screen = screenRef?.current; + const fullscreenDocument = screen?.ownerDocument ?? document; + if (callIsFullscreen) { - await document.exitFullscreen().catch(() => undefined); + await fullscreenDocument.exitFullscreen().catch(() => undefined); } else { - await screenRef?.current?.requestFullscreen().catch(() => undefined); + await screen?.requestFullscreen().catch(() => undefined); } triggerCallLayoutCalculation(); diff --git a/packages/call/src/components/top.tsx b/packages/call/src/components/top.tsx index 610493a..4da11db 100644 --- a/packages/call/src/components/top.tsx +++ b/packages/call/src/components/top.tsx @@ -75,7 +75,7 @@ export default function TopBar() { - + {user.display.slice(0, 2).toUpperCase()} diff --git a/packages/call/src/screen.tsx b/packages/call/src/screen.tsx index 49c6a11..433f5ec 100644 --- a/packages/call/src/screen.tsx +++ b/packages/call/src/screen.tsx @@ -1,11 +1,18 @@ -import { useCall } from "./store"; +import { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { + setCallIsFullscreen, + setCallIsPopout, + triggerCallLayoutCalculation, + useCall, +} from "./store"; import MainLayout from "./views/main/layout"; import MainGrid from "./views/main/grid"; import MainFocused from "./views/main/focused"; import Preview from "./views/preview"; -export default function Screen() { +function ScreenContent() { const view = useCall((state) => state.view); return view === "preview" ? ( @@ -14,3 +21,114 @@ export default function Screen() { {view === "grid" ? : } ); } + +// Popout Window +function copyDocumentStyles(targetDocument: Document) { + for (const node of document.querySelectorAll( + 'link[rel="stylesheet"], style', + )) { + targetDocument.head.appendChild(node.cloneNode(true)); + } +} + +function syncDocumentClasses(targetDocument: Document) { + targetDocument.documentElement.className = document.documentElement.className; + targetDocument.body.className = document.body.className; +} + +function PopoutScreen() { + const closeCheckRef = useRef | null>( + null, + ); + const [container, setContainer] = useState(null); + + useEffect(() => { + const popoutWindow = window.open( + "", + "tensamin-call-popout", + "popup,width=1280,height=720", + ); + + if (!popoutWindow) { + setCallIsFullscreen(false); + setCallIsPopout(false); + return; + } + + let isMounted = true; + + popoutWindow.document.title = document.title; + popoutWindow.document.body.innerHTML = ""; + popoutWindow.document.body.style.margin = "0"; + popoutWindow.document.documentElement.style.height = "100%"; + popoutWindow.document.body.style.height = "100%"; + + copyDocumentStyles(popoutWindow.document); + syncDocumentClasses(popoutWindow.document); + + const containerElement = popoutWindow.document.createElement("div"); + containerElement.style.width = "100%"; + containerElement.style.height = "100%"; + popoutWindow.document.body.appendChild(containerElement); + queueMicrotask(() => { + if (isMounted) { + setContainer(containerElement); + } + }); + + const closePopout = () => { + setCallIsFullscreen(false); + setCallIsPopout(false); + }; + const syncLayout = () => triggerCallLayoutCalculation(); + + popoutWindow.addEventListener("beforeunload", closePopout); + popoutWindow.addEventListener("resize", syncLayout); + popoutWindow.focus(); + + closeCheckRef.current = window.setInterval(() => { + if (popoutWindow.closed) { + closePopout(); + } + }, 500); + + triggerCallLayoutCalculation(); + + return () => { + isMounted = false; + popoutWindow.removeEventListener("beforeunload", closePopout); + popoutWindow.removeEventListener("resize", syncLayout); + + if (closeCheckRef.current) { + window.clearInterval(closeCheckRef.current); + closeCheckRef.current = null; + } + + if (!popoutWindow.closed) { + popoutWindow.close(); + } + + setCallIsFullscreen(false); + triggerCallLayoutCalculation(); + }; + }, []); + + if (!container) { + return null; + } + + return createPortal(, container); +} + +export default function Screen() { + const callIsPopout = useCall((state) => state.callIsPopout); + + return callIsPopout ? ( +
+

The window is popped out.

+ +
+ ) : ( + + ); +} diff --git a/packages/call/src/store.tsx b/packages/call/src/store.tsx index efa3802..a5b4b83 100644 --- a/packages/call/src/store.tsx +++ b/packages/call/src/store.tsx @@ -857,6 +857,7 @@ export async function disconnect() { watchedStreamParticipantIds: [], pendingWatchedParticipantIds: [], activeScreenShareParticipantIds: [], + callIsFullscreen: false, lastFocusedParticipantId: null, }); diff --git a/packages/call/src/views/main/layout.tsx b/packages/call/src/views/main/layout.tsx index b3710da..f20e233 100644 --- a/packages/call/src/views/main/layout.tsx +++ b/packages/call/src/views/main/layout.tsx @@ -1,7 +1,12 @@ import { useEffect, useLayoutEffect, useRef, useState } from "react"; import Actions from "../../components/actions"; import TopBar from "../../components/top"; -import { setScreenRef, useCall } from "../../store"; +import { + setCallIsFullscreen, + setScreenRef, + triggerCallLayoutCalculation, + useCall, +} from "../../store"; export default function Layout({ children }: { children: React.ReactNode }) { const screenRef = useRef(null); @@ -16,6 +21,30 @@ export default function Layout({ children }: { children: React.ReactNode }) { useEffect(() => { setScreenRef(screenRef); + + const screen = screenRef.current; + const fullscreenDocument = screen?.ownerDocument; + + if (!screen || !fullscreenDocument) { + return; + } + + const handleFullscreenChange = () => { + setCallIsFullscreen(fullscreenDocument.fullscreenElement === screen); + triggerCallLayoutCalculation(); + }; + + fullscreenDocument.addEventListener( + "fullscreenchange", + handleFullscreenChange, + ); + + return () => { + fullscreenDocument.removeEventListener( + "fullscreenchange", + handleFullscreenChange, + ); + }; }, []); const usersInFocusedViewHidden = useCall( diff --git a/packages/call/todo.md b/packages/call/todo.md index 2d8db1b..f44c6fd 100644 --- a/packages/call/todo.md +++ b/packages/call/todo.md @@ -11,3 +11,4 @@ - Context menus - Popout Window - Add quality selection +- Good preview page