Files
cinny/src/app/features/room/CallView.tsx
T

147 lines
5.0 KiB
TypeScript
Raw Normal View History

2025-05-08 17:57:57 -05:00
import { Room } from 'matrix-js-sdk';
import React, { useMemo } from 'react';
2025-05-08 17:57:57 -05:00
import { useCallback, useEffect, useRef } from 'react';
import { useOutletContext } from 'react-router-dom';
import { Box } from 'folds';
import { RoomViewHeader } from './RoomViewHeader';
import { useCallState } from '../../pages/client/CallProvider';
2025-05-08 17:57:57 -05:00
function debounce<F extends (...args: any[]) => any>(func: F, waitFor: number) {
let timeoutId: ReturnType<typeof setTimeout> | null = null;
return (...args: Parameters<F>): void => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => func(...args), waitFor);
};
}
type OriginalStyles = {
position?: string;
top?: string;
left?: string;
width?: string;
height?: string;
zIndex?: string;
display?: string;
visibility?: string;
pointerEvents?: string;
border?: string;
};
interface CallViewOutletContext {
iframeRef: React.RefObject<HTMLIFrameElement | null>;
backupIframeRef: React.RefObject<HTMLIFrameElement | null>;
}
2025-05-08 17:57:57 -05:00
export function CallView({ room, eventId }: { room: Room; eventId?: string }) {
const { iframeRef, backupIframeRef } = useOutletContext<CallViewOutletContext>();
2025-05-08 17:57:57 -05:00
const iframeHostRef = useRef<HTMLDivElement>(null);
const originalIframeStylesRef = useRef<OriginalStyles | null>(null);
const { activeCallRoomId, isPrimaryIframe } = useCallState();
const isViewingActiveCall = useMemo(
() => activeCallRoomId !== null && activeCallRoomId === room.roomId,
[activeCallRoomId]
);
2025-05-08 17:57:57 -05:00
const activeIframeDisplayRef =
!isPrimaryIframe || isViewingActiveCall ? iframeRef : backupIframeRef;
2025-05-08 17:57:57 -05:00
const applyFixedPositioningToIframe = useCallback(() => {
const iframeElement = activeIframeDisplayRef?.current;
2025-05-08 17:57:57 -05:00
const hostElement = iframeHostRef?.current;
if (iframeElement && hostElement) {
if (!originalIframeStylesRef.current) {
const computed = window.getComputedStyle(iframeElement);
originalIframeStylesRef.current = {
position: iframeElement.style.position || computed.position,
top: iframeElement.style.top || computed.top,
left: iframeElement.style.left || computed.left,
width: iframeElement.style.width || computed.width,
height: iframeElement.style.height || computed.height,
zIndex: iframeElement.style.zIndex || computed.zIndex,
display: iframeElement.style.display || computed.display,
visibility: iframeElement.style.visibility || computed.visibility,
pointerEvents: iframeElement.style.pointerEvents || computed.pointerEvents,
border: iframeElement.style.border || computed.border,
};
}
const hostRect = hostElement.getBoundingClientRect();
iframeElement.style.position = 'fixed';
iframeElement.style.top = `${hostRect.top}px`;
iframeElement.style.left = `${hostRect.left}px`;
iframeElement.style.width = `${hostRect.width}px`;
iframeElement.style.height = `${hostRect.height}px`;
iframeElement.style.border = 'none';
iframeElement.style.zIndex = '1000';
2025-05-08 17:57:57 -05:00
iframeElement.style.display = 'block';
iframeElement.style.visibility = 'visible';
iframeElement.style.pointerEvents = 'auto';
}
}, [activeIframeDisplayRef, iframeHostRef]);
2025-05-08 17:57:57 -05:00
const debouncedApplyFixedPositioning = useCallback(debounce(applyFixedPositioningToIframe, 50), [
applyFixedPositioningToIframe,
]);
useEffect(() => {
const iframeElement = activeIframeDisplayRef?.current;
2025-05-08 17:57:57 -05:00
const hostElement = iframeHostRef?.current;
if (isPrimaryIframe || (isViewingActiveCall && iframeElement && hostElement)) {
2025-05-08 17:57:57 -05:00
applyFixedPositioningToIframe();
const resizeObserver = new ResizeObserver(debouncedApplyFixedPositioning);
resizeObserver.observe(hostElement);
window.addEventListener('scroll', debouncedApplyFixedPositioning, true);
return () => {
resizeObserver.disconnect();
window.removeEventListener('scroll', debouncedApplyFixedPositioning, true);
if (iframeElement && originalIframeStylesRef.current) {
const originalStyles = originalIframeStylesRef.current;
(Object.keys(originalStyles) as Array<keyof OriginalStyles>).forEach((key) => {
if (key in iframeElement.style) {
iframeElement.style[key as any] = originalStyles[key] || '';
}
2025-05-08 17:57:57 -05:00
});
}
originalIframeStylesRef.current = null;
};
}
}, [
activeIframeDisplayRef,
2025-05-08 17:57:57 -05:00
applyFixedPositioningToIframe,
debouncedApplyFixedPositioning,
isPrimaryIframe,
isViewingActiveCall,
2025-05-08 17:57:57 -05:00
]);
const isCallViewVisible = room.isCallRoom();
2025-05-08 17:57:57 -05:00
return (
<Box
direction="Column"
style={{
width: '60%',
display: isCallViewVisible ? 'flex' : 'none',
2025-05-08 17:57:57 -05:00
}}
>
<RoomViewHeader />
<div
ref={iframeHostRef}
style={{
width: '100%',
height: '100%',
position: 'relative',
pointerEvents: 'none',
}}
/>
</Box>
);
}