Merge pull request 'feat(calls): minimise the call PiP to a small bar; phones start minimised (#323)' (#414) from fix/pip-minimise into lotus
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
This commit was merged in pull request #414.
This commit is contained in:
@@ -90,6 +90,9 @@ import { useDenoiseNotices } from '../hooks/useDenoiseNotices';
|
|||||||
|
|
||||||
const PIP_MIN_W = 200;
|
const PIP_MIN_W = 200;
|
||||||
const PIP_MIN_H = 112;
|
const PIP_MIN_H = 112;
|
||||||
|
/** [Gitea #323] Minimised PiP bar size. */
|
||||||
|
const PIP_MINI_W = 220;
|
||||||
|
const PIP_MINI_H = 40;
|
||||||
/** Default PiP top: just below the 64px-ish room header. */
|
/** Default PiP top: just below the 64px-ish room header. */
|
||||||
const PIP_DEFAULT_TOP = 72;
|
const PIP_DEFAULT_TOP = 72;
|
||||||
|
|
||||||
@@ -1003,6 +1006,28 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [pipIsFullscreen, setPipIsFullscreen] = useState(false);
|
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<boolean>(() => {
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
const onFsChange = () => setPipIsFullscreen(!!document.fullscreenElement);
|
const onFsChange = () => setPipIsFullscreen(!!document.fullscreenElement);
|
||||||
document.addEventListener('fullscreenchange', onFsChange);
|
document.addEventListener('fullscreenchange', onFsChange);
|
||||||
@@ -1103,6 +1128,26 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
}
|
}
|
||||||
}, [pipMode, callVisible]);
|
}, [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(() => {
|
React.useEffect(() => {
|
||||||
if (!pipMode) return;
|
if (!pipMode) return;
|
||||||
const onPipWindowResize = (): void => {
|
const onPipWindowResize = (): void => {
|
||||||
@@ -1421,19 +1466,50 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
background: 'transparent',
|
// Minimised: cover the frame so only the bar shows.
|
||||||
|
background: pipMinimized ? color.Surface.Container : 'transparent',
|
||||||
cursor: 'grab',
|
cursor: 'grab',
|
||||||
// [Gitea #43] The call's "Share your screen?" prompt is drawn
|
// [Gitea #43] The call's "Share your screen?" prompt is drawn
|
||||||
// inside the frame: let the click reach its Share button.
|
// inside the frame: let the click reach its Share button.
|
||||||
pointerEvents: pipPromptOpen ? 'none' : undefined,
|
pointerEvents: pipPromptOpen ? 'none' : undefined,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'flex-start',
|
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: pipMinimized ? 'center' : 'flex-start',
|
||||||
padding: '6px',
|
padding: '6px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', gap: config.space.S100, alignItems: 'center' }}>
|
<div
|
||||||
{document.fullscreenEnabled && (
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: config.space.S100,
|
||||||
|
alignItems: 'center',
|
||||||
|
...(pipMinimized ? { width: '100%' } : {}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Leftmost: the top-right corner is a resize handle. */}
|
||||||
|
<IconButton
|
||||||
|
type="button"
|
||||||
|
size="300"
|
||||||
|
radii="300"
|
||||||
|
variant="Surface"
|
||||||
|
fill="None"
|
||||||
|
aria-label={pipMinimized ? 'Expand call' : 'Minimise call'}
|
||||||
|
aria-pressed={pipMinimized}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
togglePipMinimized();
|
||||||
|
}}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
onTouchStart={(e) => e.stopPropagation()}
|
||||||
|
style={{
|
||||||
|
background: 'rgba(0,0,0,0.65)',
|
||||||
|
backdropFilter: 'blur(4px)',
|
||||||
|
color: '#fff',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Icon size="100" src={pipMinimized ? Icons.ChevronTop : Icons.Minus} />
|
||||||
|
</IconButton>
|
||||||
|
{!pipMinimized && document.fullscreenEnabled && (
|
||||||
<IconButton
|
<IconButton
|
||||||
type="button"
|
type="button"
|
||||||
size="300"
|
size="300"
|
||||||
@@ -1465,14 +1541,23 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) {
|
|||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
|
...(pipMinimized
|
||||||
|
? {
|
||||||
|
flexGrow: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
↗ Return to call
|
{pipMinimized ? `↗ ${callEmbed.room.name ?? 'Call'}` : '↗ Return to call'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PipMuteOverlay callEmbed={callEmbed} />
|
{!pipMinimized && <PipMuteOverlay callEmbed={callEmbed} />}
|
||||||
{(['se', 'sw', 'ne', 'nw'] as Corner[]).map((corner) => {
|
{(pipMinimized ? [] : (['se', 'sw', 'ne', 'nw'] as Corner[])).map((corner) => {
|
||||||
const s = corner.includes('s');
|
const s = corner.includes('s');
|
||||||
const e2 = corner.includes('e');
|
const e2 = corner.includes('e');
|
||||||
const dots = [
|
const dots = [
|
||||||
|
|||||||
Reference in New Issue
Block a user