Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
354 changes: 354 additions & 0 deletions .yarn/patches/@react-aria-overlays-npm-3.25.0-2628866e6e.patch

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
"zod@npm:^3.25.0 || ^4.0.0": "patch:zod@npm%3A4.3.6#~/.yarn/patches/zod-npm-4.3.6-a096e305e6.patch",
"zod@npm:~4.3.6": "patch:zod@npm%3A4.3.6#~/.yarn/patches/zod-npm-4.3.6-a096e305e6.patch",
"@react-aria/i18n@npm:^3.0.0-nightly-fb28ab3b4-241024": "patch:@react-aria/i18n@npm%3A3.12.5#~/.yarn/patches/@react-aria-i18n-npm-3.12.5-435edff786.patch",
"@react-aria/i18n@npm:^3.12.5": "patch:@react-aria/i18n@npm%3A3.12.5#~/.yarn/patches/@react-aria-i18n-npm-3.12.5-435edff786.patch"
"@react-aria/i18n@npm:^3.12.5": "patch:@react-aria/i18n@npm%3A3.12.5#~/.yarn/patches/@react-aria-i18n-npm-3.12.5-435edff786.patch",
"@react-aria/overlays@npm:^3.25.0": "patch:@react-aria/overlays@npm%3A3.25.0#~/.yarn/patches/@react-aria-overlays-npm-3.25.0-2628866e6e.patch"
},
"dependencies": {
"@types/stream-buffers": "^3.0.8",
Expand Down
106 changes: 106 additions & 0 deletions packages/ui-voip/src/views/MediaCallPopoutWindow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { StyleOptions as FuselageStyleOptions } from '@rocket.chat/fuselage';
import { useStableCallback } from '@rocket.chat/fuselage-hooks';
import { StyledOptions } from '@rocket.chat/styled';
import type { ReactNode } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';

type MediaCallPopoutWindowProps = {
children: ReactNode;
onClose: () => void;
};

const createRootElement = (externalWindow: Window) => {
const newRoot = externalWindow.document.createElement('div');
newRoot.style.width = '100%';
newRoot.style.height = '100%';
externalWindow.document.body.appendChild(newRoot);
return newRoot;
};

const copyStylesheets = (externalWindow: Window) => {
Array.from(document.styleSheets).forEach((stylesheet) => {
if (stylesheet.href) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = stylesheet.href;
externalWindow.document.head.appendChild(link);
} else if (stylesheet?.cssRules?.length > 0) {
const style = document.createElement('style');
Array.from(stylesheet.cssRules).forEach((rule) => {
style.appendChild(document.createTextNode(rule.cssText));
});
externalWindow.document.head.appendChild(style);
}
});
};

const openExternalWindow = (title: string) => {
try {
const externalWindow = window.open('', title, 'width=800,height=500,popup');
if (!externalWindow) {
throw new Error('No window was opened');
}
copyStylesheets(externalWindow);
return externalWindow;
} catch (error) {
console.error('Failed to open external window', error);
return null;
}
};

const MediaCallPopoutWindow = ({ children, onClose }: MediaCallPopoutWindowProps) => {
const [container, setContainer] = useState<{ root: HTMLDivElement; externalWindow: Window } | null>(null);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
const triggerClose = useStableCallback(onClose);

useEffect(() => {
setContainer((prev) => {
if (prev?.externalWindow && prev?.root) {
return prev;
}

const externalWindow = !prev?.externalWindow ? openExternalWindow('Call with Peer X') : prev?.externalWindow;

if (!externalWindow) {
triggerClose();
return null;
}

const root = createRootElement(externalWindow);

return { root, externalWindow };
});
}, [triggerClose]);

useEffect(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}

const handleUnload = () => triggerClose();
container?.externalWindow.addEventListener('beforeunload', handleUnload);

return () => {
timeoutRef.current = setTimeout(() => {
container?.externalWindow.removeEventListener('beforeunload', handleUnload);
container?.externalWindow.close();
triggerClose();
}, 400);
};
}, [container?.externalWindow, triggerClose]);

const contextValue = useMemo(() => ({ document: container?.externalWindow.document }), [container?.externalWindow.document]);

if (!container) {
return null;
}

return (
<FuselageStyleOptions.Provider value={contextValue}>
<StyledOptions.Provider value={contextValue}>{createPortal(children, container.root)}</StyledOptions.Provider>
</FuselageStyleOptions.Provider>
);
};

export default MediaCallPopoutWindow;
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import { useMemo, useState } from 'react';

import MediaCallRoomSection from './MediaCallRoomSection';
import MediaCallViewProvider from '../../providers/MediaCallViewProvider';
import MediaCallPopoutWindow from '../MediaCallPopoutWindow';

type MediaCallRoomActivityProps = {
children: ReactNode;
};

const MediaCallRoomActivity = ({ children }: MediaCallRoomActivityProps) => {
const [showChat, setShowChat] = useState(true);
const user = useUser();
const [isPopout, setIsPopout] = useState(false);

const user = useUser();
const displayName = useUserDisplayName({ name: user?.name, username: user?.username });
const getUserAvatarPath = useUserAvatarPath();

Expand All @@ -28,20 +30,37 @@ const MediaCallRoomActivity = ({ children }: MediaCallRoomActivityProps) => {
};
}, [displayName, getUserAvatarPath, user?._id]);

const onClickToggleChat = () => {
setShowChat((prev) => !prev);
};
const mediaCallRoomSection = (
<MediaCallViewProvider>
<MediaCallRoomSection
showChat={showChat}
onToggleChat={() => setShowChat((prev) => !prev)}
user={ownUser}
containerHeight={borderBoxSize?.blockSize || 0}
onPopout={() => setIsPopout((prev) => !prev)}
isPopout={isPopout}
key={isPopout ? 'popout' : 'normal'}
/>
</MediaCallViewProvider>
);

// TODO: this shouldn't be inside here, since the popout has to be always rendered.
if (isPopout) {
return (
<>
<MediaCallPopoutWindow onClose={() => setIsPopout(false)}>
<Box w='full' h='full' display='flex' flexDirection='column' justifyContent='space-between' ref={ref}>
{mediaCallRoomSection}
</Box>
</MediaCallPopoutWindow>
{children}
</>
);
}

return (
<Box w='full' h='full' display='flex' flexDirection='column' justifyContent='space-between' ref={ref}>
<MediaCallViewProvider>
<MediaCallRoomSection
showChat={showChat}
onToggleChat={onClickToggleChat}
user={ownUser}
containerHeight={borderBoxSize?.blockSize || 0}
/>
</MediaCallViewProvider>

{mediaCallRoomSection}
{showChat && (
<Box w='full' flexGrow={2} flexShrink={0}>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ type MediaCallRoomSectionProps = {
avatarUrl: string;
};
containerHeight: number;
onPopout: () => void;
isPopout?: boolean;
};

const getSplitStyles = (showChat?: boolean) => {
if (showChat) {
const getSplitStyles = (showChat?: boolean, isPopout?: boolean) => {
if (showChat && !isPopout) {
return {
maxHeight: `${CARD_LIST_SECTION_MAX_HEIGHT}vh`,
};
Expand All @@ -44,7 +46,7 @@ const getSplitStyles = (showChat?: boolean) => {
};
};

const MediaCallRoomSection = ({ showChat, onToggleChat, user, containerHeight }: MediaCallRoomSectionProps) => {
const MediaCallRoomSection = ({ showChat, onToggleChat, user, containerHeight, onPopout, isPopout }: MediaCallRoomSectionProps) => {
const { t } = useTranslation();

const [focusedCard, setFocusedCard] = useState<'remote' | 'local' | null>('remote');
Expand Down Expand Up @@ -84,7 +86,14 @@ const MediaCallRoomSection = ({ showChat, onToggleChat, user, containerHeight }:

const remoteStreamCard = remoteScreen?.active ? (
<StreamCard onClickFocusStream={onClickFocusRemoteCard} focused={focusedCard === 'remote'}>
<video preload='metadata' style={{ objectFit: 'contain', height: '100%', width: '100%' }} ref={remoteStreamRefCallback}>
<video
preload='metadata'
style={{ objectFit: 'contain', height: '100%', width: '100%' }}
ref={remoteStreamRefCallback}
autoPlay={true}
muted={true}
playsInline={true}
>
<track kind='captions' />
</video>
</StreamCard>
Expand All @@ -98,7 +107,14 @@ const MediaCallRoomSection = ({ showChat, onToggleChat, user, containerHeight }:
focused={focusedCard === 'local'}
showStopSharingOnHover
>
<video preload='metadata' style={{ objectFit: 'contain', height: '100%', width: '100%' }} ref={localStreamRefCallback}>
<video
preload='metadata'
style={{ objectFit: 'contain', height: '100%', width: '100%' }}
ref={localStreamRefCallback}
autoPlay={true}
playsInline={true}
muted={true}
>
<track kind='captions' />
</video>
</StreamCard>
Expand All @@ -114,7 +130,7 @@ const MediaCallRoomSection = ({ showChat, onToggleChat, user, containerHeight }:
overflow='hidden'
display='flex'
flexDirection='column'
{...getSplitStyles(showChat)}
{...getSplitStyles(showChat, isPopout)}
>
<CardListSection>
<CardListContainer focusedCard={focusedCard ? focusedCardElement : undefined} shouldWrapCards={shouldWrapCards}>
Expand Down Expand Up @@ -152,6 +168,7 @@ const MediaCallRoomSection = ({ showChat, onToggleChat, user, containerHeight }:
pressed={localScreen?.active ?? false}
onToggle={onToggleScreenSharing}
/>
<ActionButton label={t('Popout')} icon='arrow-expand' onClick={onPopout} />
<ActionButton disabled={connecting || reconnecting} label={t('Forward')} icon='arrow-forward' onClick={onForward} />
<ActionButton label={t('Voice_call__user__hangup', { user: peerInfo.displayName })} icon='phone-off' danger onClick={onEndCall} />
</ActionStrip>
Expand Down
24 changes: 23 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7146,7 +7146,7 @@ __metadata:
languageName: node
linkType: hard

"@react-aria/overlays@npm:^3.25.0":
"@react-aria/overlays@npm:3.25.0":
version: 3.25.0
resolution: "@react-aria/overlays@npm:3.25.0"
dependencies:
Expand All @@ -7168,6 +7168,28 @@ __metadata:
languageName: node
linkType: hard

"@react-aria/overlays@patch:@react-aria/overlays@npm%3A3.25.0#~/.yarn/patches/@react-aria-overlays-npm-3.25.0-2628866e6e.patch":
version: 3.25.0
resolution: "@react-aria/overlays@patch:@react-aria/overlays@npm%3A3.25.0#~/.yarn/patches/@react-aria-overlays-npm-3.25.0-2628866e6e.patch::version=3.25.0&hash=5cb6f6"
dependencies:
"@react-aria/focus": "npm:^3.19.1"
"@react-aria/i18n": "npm:^3.12.5"
"@react-aria/interactions": "npm:^3.23.0"
"@react-aria/ssr": "npm:^3.9.7"
"@react-aria/utils": "npm:^3.27.0"
"@react-aria/visually-hidden": "npm:^3.8.19"
"@react-stately/overlays": "npm:^3.6.13"
"@react-types/button": "npm:^3.10.2"
"@react-types/overlays": "npm:^3.8.12"
"@react-types/shared": "npm:^3.27.0"
"@swc/helpers": "npm:^0.5.0"
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
checksum: 10/dd5b420d0cda39dbd9c60a5257e2783d22196b77ec98b3982a2176abb25d117f3593f49da7ee2e6df8799e0a9dc43b7d88b503d419f3d57310b041bb3f781f7b
languageName: node
linkType: hard

"@react-aria/progress@npm:^3.4.19":
version: 3.4.19
resolution: "@react-aria/progress@npm:3.4.19"
Expand Down
Loading