From c4c54dad7afae0cdb8a2e84fdc77f37847367287 Mon Sep 17 00:00:00 2001 From: Lotus CI Date: Sun, 18 Oct 2026 01:08:36 -0400 Subject: [PATCH] feat(calls): minimise the call PiP to a small bar; phones start minimised (#323) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PiP had no way to get out of the way short of dragging it. Add a minimise / expand button: minimised it is a 220×40 bar with the call room's name (still draggable, tap to return to the call). The choice is remembered; with no saved choice, phones start minimised. Fixes #323 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5 --- src/app/components/CallEmbedProvider.tsx | 99 ++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 7 deletions(-) diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx index 17e054da5..119e8215d 100644 --- a/src/app/components/CallEmbedProvider.tsx +++ b/src/app/components/CallEmbedProvider.tsx @@ -89,6 +89,9 @@ import { zIndices } from '../styles/zIndex'; const PIP_MIN_W = 200; const PIP_MIN_H = 112; +/** [Gitea #323] Minimised PiP bar size. */ +const PIP_MINI_W = 220; +const PIP_MINI_H = 40; type Corner = 'se' | 'sw' | 'ne' | 'nw'; @@ -981,6 +984,28 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) { ); const [pipIsFullscreen, setPipIsFullscreen] = useState(false); + // [Gitea #323] Minimised PiP: a small draggable bar (room name + expand) + // instead of the video. Remembered; phones start minimised. + const [pipMinimized, setPipMinimized] = useState(() => { + try { + const v = localStorage.getItem('pip-minimized'); + if (v !== null) return v === '1'; + } catch { + /* storage unavailable */ + } + return window.innerWidth < 600; + }); + const pipFullSizeRef = React.useRef({ width: 280, height: 158 }); + const togglePipMinimized = useCallback(() => { + setPipMinimized((m) => { + try { + localStorage.setItem('pip-minimized', m ? '0' : '1'); + } catch { + /* storage unavailable */ + } + return !m; + }); + }, []); useEffect(() => { const onFsChange = () => setPipIsFullscreen(!!document.fullscreenElement); document.addEventListener('fullscreenchange', onFsChange); @@ -1078,6 +1103,26 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) { } }, [pipMode, callVisible]); + // [Gitea #323] Apply the minimised / full PiP size (after the entry effect + // below has positioned and sized it). Keeps the bar on screen. + React.useEffect(() => { + const el = callEmbedRef.current; + if (!el || !pipMode) return; + if (pipMinimized) { + if (el.offsetHeight > PIP_MINI_H) { + pipFullSizeRef.current = { width: el.offsetWidth, height: el.offsetHeight }; + } + el.style.width = `${PIP_MINI_W}px`; + el.style.height = `${PIP_MINI_H}px`; + } else { + el.style.width = `${pipFullSizeRef.current.width}px`; + el.style.height = `${pipFullSizeRef.current.height}px`; + } + const rect = el.getBoundingClientRect(); + el.style.left = `${Math.max(0, Math.min(rect.left, window.innerWidth - el.offsetWidth))}px`; + el.style.top = `${Math.max(0, Math.min(rect.top, window.innerHeight - el.offsetHeight))}px`; + }, [pipMode, pipMinimized, callEmbedRef]); + React.useEffect(() => { if (!pipMode) return; const onPipWindowResize = (): void => { @@ -1396,19 +1441,50 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) { position: 'absolute', inset: 0, zIndex: 1, - background: 'transparent', + // Minimised: cover the frame so only the bar shows. + background: pipMinimized ? color.Surface.Container : 'transparent', cursor: 'grab', // [Gitea #43] The call's "Share your screen?" prompt is drawn // inside the frame: let the click reach its Share button. pointerEvents: pipPromptOpen ? 'none' : undefined, display: 'flex', - alignItems: 'flex-start', justifyContent: 'flex-end', + alignItems: pipMinimized ? 'center' : 'flex-start', padding: '6px', }} > -
- {document.fullscreenEnabled && ( +
+ {/* Leftmost: the top-right corner is a resize handle. */} + { + e.stopPropagation(); + togglePipMinimized(); + }} + onMouseDown={(e) => e.stopPropagation()} + onTouchStart={(e) => e.stopPropagation()} + style={{ + background: 'rgba(0,0,0,0.65)', + backdropFilter: 'blur(4px)', + color: '#fff', + }} + > + + + {!pipMinimized && document.fullscreenEnabled && ( - ↗ Return to call + {pipMinimized ? `↗ ${callEmbed.room.name ?? 'Call'}` : '↗ Return to call'}
- - {(['se', 'sw', 'ne', 'nw'] as Corner[]).map((corner) => { + {!pipMinimized && } + {(pipMinimized ? [] : (['se', 'sw', 'ne', 'nw'] as Corner[])).map((corner) => { const s = corner.includes('s'); const e2 = corner.includes('e'); const dots = [