blob: 29dd4e0c111eb934e2ee2502cb3bfddcccfdd4a7 [file] [log] [blame]
Gabriel Rochone3ec0d22022-10-08 14:27:03 -04001/*
2 * Copyright (C) 2022 Savoir-faire Linux Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License as
6 * published by the Free Software Foundation; either version 3 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Affero General Public License for more details.
13 *
14 * You should have received a copy of the GNU Affero General Public
15 * License along with this program. If not, see
16 * <https://www.gnu.org/licenses/>.
17 */
simonf929a362022-11-18 16:53:45 -050018import { Box, Card, Grid, Stack, Typography } from '@mui/material';
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050019import {
20 ComponentType,
21 Fragment,
22 ReactNode,
23 useCallback,
24 useContext,
simonf929a362022-11-18 16:53:45 -050025 useEffect,
Gabriel Rochon8321a0d2022-11-06 23:18:36 -050026 useLayoutEffect,
27 useMemo,
28 useRef,
29 useState,
30} from 'react';
simon33c06182022-11-02 17:39:31 -040031import Draggable from 'react-draggable';
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040032
simon33c06182022-11-02 17:39:31 -040033import { ExpandableButtonProps } from '../components/Button';
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040034import {
35 CallingChatButton,
36 CallingEndButton,
37 CallingExtensionButton,
simon33c06182022-11-02 17:39:31 -040038 CallingFullScreenButton,
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040039 CallingGroupButton,
40 CallingMicButton,
simon33c06182022-11-02 17:39:31 -040041 CallingMoreVerticalButton,
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040042 CallingRecordButton,
43 CallingScreenShareButton,
44 CallingVideoCameraButton,
45 CallingVolumeButton,
simon1170c322022-10-31 14:51:31 -040046} from '../components/CallButtons';
simonf9d78f22022-11-25 15:47:15 -050047import CallChatDrawer from '../components/CallChatDrawer';
simonf929a362022-11-18 16:53:45 -050048import { CallContext, CallStatus } from '../contexts/CallProvider';
49import { CallPending } from './CallPending';
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040050
simon1170c322022-10-31 14:51:31 -040051export default () => {
simonf9d78f22022-11-25 15:47:15 -050052 const { callRole, callStatus, isChatShown } = useContext(CallContext);
simonf929a362022-11-18 16:53:45 -050053
simon9f814a32022-11-22 21:40:53 -050054 if (callStatus !== CallStatus.InCall) {
55 return (
56 <CallPending
57 pending={callRole}
simonff1cb352022-11-24 15:15:26 -050058 caller={callStatus === CallStatus.Connecting ? 'connecting' : 'calling'}
simon9f814a32022-11-22 21:40:53 -050059 medium="audio"
60 />
61 );
simonf929a362022-11-18 16:53:45 -050062 }
simon9f814a32022-11-22 21:40:53 -050063
simonf9d78f22022-11-25 15:47:15 -050064 return (
65 <Box flexGrow={1} display="flex">
66 <CallInterface />
67 {isChatShown && <CallChatDrawer />}
68 </Box>
69 );
simon1170c322022-10-31 14:51:31 -040070};
71
simon33c06182022-11-02 17:39:31 -040072interface Props {
73 children?: ReactNode;
74}
75
simon1170c322022-10-31 14:51:31 -040076const CallInterface = () => {
simonf929a362022-11-18 16:53:45 -050077 const { isVideoOn, localStream, remoteStream } = useContext(CallContext);
simon33c06182022-11-02 17:39:31 -040078 const gridItemRef = useRef(null);
simonf929a362022-11-18 16:53:45 -050079 const remoteVideoRef = useRef<HTMLVideoElement | null>(null);
80 const localVideoRef = useRef<HTMLVideoElement | null>(null);
81
82 useEffect(() => {
83 if (localStream && localVideoRef.current) {
84 localVideoRef.current.srcObject = localStream;
85 }
86 }, [localStream]);
87
88 useEffect(() => {
89 if (remoteStream && remoteVideoRef.current) {
90 remoteVideoRef.current.srcObject = remoteStream;
91 }
92 }, [remoteStream]);
simonce2c0c42022-11-02 17:39:31 -040093
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040094 return (
simonf9d78f22022-11-25 15:47:15 -050095 <Box display="flex" flexGrow={1}>
simonce2c0c42022-11-02 17:39:31 -040096 <video
97 ref={remoteVideoRef}
98 autoPlay
simon9f814a32022-11-22 21:40:53 -050099 style={{ zIndex: -1, backgroundColor: 'black', position: 'absolute', height: '100%', width: '100%' }}
simonce2c0c42022-11-02 17:39:31 -0400100 />
simon9f814a32022-11-22 21:40:53 -0500101 <Box flexGrow={1} margin={2} display="flex" flexDirection="column">
102 {/* Guest video, takes the whole screen */}
103 <CallInterfaceInformation />
104 <Box flexGrow={1} marginY={2} position="relative">
simonf929a362022-11-18 16:53:45 -0500105 <Draggable bounds="parent" nodeRef={localVideoRef ?? undefined}>
106 <video
107 ref={localVideoRef}
108 autoPlay
109 style={{
110 position: 'absolute',
111 right: 0,
simonf929a362022-11-18 16:53:45 -0500112 borderRadius: '12px',
simonf929a362022-11-18 16:53:45 -0500113 maxHeight: '50%',
simon9f814a32022-11-22 21:40:53 -0500114 maxWidth: '50%',
simonf929a362022-11-18 16:53:45 -0500115 visibility: isVideoOn ? 'visible' : 'hidden',
116 }}
117 />
118 </Draggable>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400119 </Box>
simon33c06182022-11-02 17:39:31 -0400120 <Grid container>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400121 <Grid item xs />
simon33c06182022-11-02 17:39:31 -0400122 <Grid item sx={{ display: 'flex', justifyContent: 'center' }}>
123 <div>
124 <CallInterfacePrimaryButtons />
125 </div>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400126 </Grid>
simon33c06182022-11-02 17:39:31 -0400127 <Grid item xs sx={{ display: 'flex', justifyContent: 'flex-end' }} ref={gridItemRef}>
128 <CallInterfaceSecondaryButtons gridItemRef={gridItemRef} />
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400129 </Grid>
130 </Grid>
simon9f814a32022-11-22 21:40:53 -0500131 </Box>
simonf9d78f22022-11-25 15:47:15 -0500132 </Box>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400133 );
simon1170c322022-10-31 14:51:31 -0400134};
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400135
136const CallInterfaceInformation = () => {
137 return (
138 <Stack direction="row" justifyContent="space-between" alignItems="center">
139 <Typography color="white" component="p">
140 Alain Thérieur
141 </Typography>
142 <Typography color="white" component="p">
143 01:23
144 </Typography>
145 </Stack>
146 );
147};
148
149const CallInterfacePrimaryButtons = () => {
150 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500151 <Card sx={{ backgroundColor: '#00000088', overflow: 'visible' }}>
152 <Stack direction="row" justifyContent="center" alignItems="center">
simon33c06182022-11-02 17:39:31 -0400153 <CallingMicButton />
simon9a8fe202022-11-15 18:25:49 -0500154 <CallingEndButton />
simon33c06182022-11-02 17:39:31 -0400155 <CallingVideoCameraButton />
156 </Stack>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400157 </Card>
158 );
159};
160
simon33c06182022-11-02 17:39:31 -0400161const SECONDARY_BUTTONS = [
162 CallingVolumeButton,
163 CallingGroupButton,
164 CallingChatButton,
165 CallingScreenShareButton,
166 CallingRecordButton,
167 CallingExtensionButton,
168 CallingFullScreenButton,
169];
170
171const CallInterfaceSecondaryButtons = (props: Props & { gridItemRef: React.RefObject<HTMLElement> }) => {
172 const stackRef = useRef<HTMLElement>(null);
173
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500174 const [initialMeasurementDone, setInitialMeasurementDone] = useState(false);
simon33c06182022-11-02 17:39:31 -0400175 const [hiddenStackCount, setHiddenStackCount] = useState(0);
176 const [hiddenMenuVisible, setHiddenMenuVisible] = useState(false);
177
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500178 const calculateStackCount = useCallback(() => {
179 if (stackRef?.current && props.gridItemRef?.current) {
180 const buttonWidth = stackRef.current.children[0].clientWidth;
181 const availableSpace = props.gridItemRef.current.clientWidth;
182 let availableButtons = Math.floor((availableSpace - 1) / buttonWidth);
183 if (availableButtons < SECONDARY_BUTTONS.length) {
184 availableButtons -= 1; // Leave room for CallingMoreVerticalButton
simon33c06182022-11-02 17:39:31 -0400185 }
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500186 setHiddenStackCount(SECONDARY_BUTTONS.length - availableButtons);
187 }
188 }, [props.gridItemRef]);
189
190 useLayoutEffect(() => {
191 // Run once, at the beginning, for initial measurements
192 if (!initialMeasurementDone) {
193 calculateStackCount();
194 setInitialMeasurementDone(true);
195 }
196
197 const onResize = () => {
198 calculateStackCount();
simon33c06182022-11-02 17:39:31 -0400199 };
200 window.addEventListener('resize', onResize);
201 return () => {
202 window.removeEventListener('resize', onResize);
203 };
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500204 }, [calculateStackCount, initialMeasurementDone]);
simon33c06182022-11-02 17:39:31 -0400205
206 const { displayedButtons, hiddenButtons } = useMemo(() => {
207 const displayedButtons: ComponentType<ExpandableButtonProps>[] = [];
208 const hiddenButtons: ComponentType<ExpandableButtonProps>[] = [];
209 SECONDARY_BUTTONS.forEach((button, i) => {
210 if (i < SECONDARY_BUTTONS.length - hiddenStackCount) {
211 displayedButtons.push(button);
212 } else {
213 hiddenButtons.push(button);
214 }
215 });
216
217 return {
218 displayedButtons,
219 hiddenButtons,
220 };
221 }, [hiddenStackCount]);
222
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400223 return (
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500224 <Card sx={{ backgroundColor: '#00000088', overflow: 'visible', height: '100%' }}>
225 <Stack direction="row" justifyContent="center" alignItems="center" height="100%" ref={stackRef}>
226 {initialMeasurementDone &&
227 displayedButtons.map((SecondaryButton, i) => (
228 <Fragment key={i}>
simon9a8fe202022-11-15 18:25:49 -0500229 <SecondaryButton />
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500230 </Fragment>
231 ))}
232 {(!!hiddenButtons.length || !initialMeasurementDone) && (
simon9a8fe202022-11-15 18:25:49 -0500233 <CallingMoreVerticalButton isVertical onClick={() => setHiddenMenuVisible(!hiddenMenuVisible)} />
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500234 )}
235 </Stack>
236
237 {!!hiddenButtons.length && hiddenMenuVisible && (
238 <Box sx={{ position: 'absolute', right: 0, bottom: '50px' }}>
239 <Card sx={{ backgroundColor: '#00000088', overflow: 'visible', justifyContent: 'flex-end' }}>
simon33c06182022-11-02 17:39:31 -0400240 <Stack
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500241 direction="column"
242 justifyContent="flex-end"
243 alignItems="flex-end"
244 sx={{ bottom: 0, right: 0, height: '100%' }}
simon33c06182022-11-02 17:39:31 -0400245 >
246 {hiddenButtons.map((SecondaryButton, i) => (
247 <Fragment key={i}>
simon4e7445c2022-11-16 21:18:46 -0500248 <SecondaryButton isVertical />
simon33c06182022-11-02 17:39:31 -0400249 </Fragment>
250 ))}
251 </Stack>
252 </Card>
Gabriel Rochon8321a0d2022-11-06 23:18:36 -0500253 </Box>
254 )}
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400255 </Card>
256 );
257};