blob: 14feb43ad51c9b4297991bcaa759399e556dc06c [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 */
simonce2c0c42022-11-02 17:39:31 -040018import { Box, Button, Card, Grid, Stack, Typography } from '@mui/material';
simon33c06182022-11-02 17:39:31 -040019import { ComponentType, Fragment, ReactNode, useContext, useLayoutEffect, useMemo, useRef, useState } from 'react';
20import Draggable from 'react-draggable';
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040021
simon33c06182022-11-02 17:39:31 -040022import { ExpandableButtonProps } from '../components/Button';
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040023import {
24 CallingChatButton,
25 CallingEndButton,
26 CallingExtensionButton,
simon33c06182022-11-02 17:39:31 -040027 CallingFullScreenButton,
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040028 CallingGroupButton,
29 CallingMicButton,
simon33c06182022-11-02 17:39:31 -040030 CallingMoreVerticalButton,
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040031 CallingRecordButton,
32 CallingScreenShareButton,
33 CallingVideoCameraButton,
34 CallingVolumeButton,
simon1170c322022-10-31 14:51:31 -040035} from '../components/CallButtons';
simonce2c0c42022-11-02 17:39:31 -040036import WebRTCProvider, { WebRTCContext } from '../contexts/WebRTCProvider';
simon1170c322022-10-31 14:51:31 -040037import { useUrlParams } from '../utils/hooks';
38import { CallRouteParams } from './JamiMessenger';
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040039
simon1170c322022-10-31 14:51:31 -040040export default () => {
41 const {
42 queryParams: { video },
43 } = useUrlParams<CallRouteParams>();
simon1170c322022-10-31 14:51:31 -040044 return (
simonce2c0c42022-11-02 17:39:31 -040045 <WebRTCProvider isVideoOn={video === 'true'}>
simon1170c322022-10-31 14:51:31 -040046 <CallInterface />
simonce2c0c42022-11-02 17:39:31 -040047 </WebRTCProvider>
simon1170c322022-10-31 14:51:31 -040048 );
49};
50
simon33c06182022-11-02 17:39:31 -040051export enum SecondaryButtons {
52 Volume = 1,
53 Group,
54 Chat,
55 ScreenShare,
56 Record,
57 Extension,
58 FullScreen,
59}
60
61interface Props {
62 children?: ReactNode;
63}
64
simon1170c322022-10-31 14:51:31 -040065const CallInterface = () => {
simonce2c0c42022-11-02 17:39:31 -040066 const { localVideoRef, remoteVideoRef, isVideoOn } = useContext(WebRTCContext);
simon33c06182022-11-02 17:39:31 -040067 const gridItemRef = useRef(null);
simonce2c0c42022-11-02 17:39:31 -040068
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040069 return (
70 <>
simonce2c0c42022-11-02 17:39:31 -040071 <video
72 ref={remoteVideoRef}
73 autoPlay
74 style={{ backgroundColor: 'black', width: '100%', height: '100%', position: 'absolute' }}
75 />
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040076 <Stack
77 position="absolute"
78 direction="column"
79 spacing={1}
80 margin={2}
81 sx={{ left: 0, right: 0, top: 0, bottom: 0 }}
82 >
83 {/* Top panel with guest information */}
84 <Box>
85 <CallInterfaceInformation />
86 </Box>
87 {/* Guest video, with empty space to be moved around and stickied to walls */}
88 <Box height="100%">
simonce2c0c42022-11-02 17:39:31 -040089 {isVideoOn && (
simon33c06182022-11-02 17:39:31 -040090 <Draggable bounds="parent">
91 <video
92 ref={localVideoRef}
93 autoPlay
94 style={{
95 position: 'absolute',
96 right: 0,
97 zIndex: 2,
98 borderRadius: '12px',
99 minWidth: '25%',
100 minHeight: '25%',
101 maxWidth: '50%',
102 maxHeight: '50%',
103 }}
104 />
105 </Draggable>
simonce2c0c42022-11-02 17:39:31 -0400106 )}
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400107 </Box>
108 {/* Bottom panel with calling buttons */}
simon33c06182022-11-02 17:39:31 -0400109 <Grid container>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400110 <Grid item xs />
simon33c06182022-11-02 17:39:31 -0400111 <Grid item sx={{ display: 'flex', justifyContent: 'center' }}>
112 <div>
113 <CallInterfacePrimaryButtons />
114 </div>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400115 </Grid>
simon33c06182022-11-02 17:39:31 -0400116 <Grid item xs sx={{ display: 'flex', justifyContent: 'flex-end' }} ref={gridItemRef}>
117 <CallInterfaceSecondaryButtons gridItemRef={gridItemRef} />
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400118 </Grid>
119 </Grid>
120 </Stack>
121 </>
122 );
simon1170c322022-10-31 14:51:31 -0400123};
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400124
125const CallInterfaceInformation = () => {
126 return (
127 <Stack direction="row" justifyContent="space-between" alignItems="center">
128 <Typography color="white" component="p">
129 Alain Thérieur
130 </Typography>
131 <Typography color="white" component="p">
132 01:23
133 </Typography>
134 </Stack>
135 );
136};
137
138const CallInterfacePrimaryButtons = () => {
simonce2c0c42022-11-02 17:39:31 -0400139 const { sendWebRTCOffer } = useContext(WebRTCContext);
140
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400141 return (
simon33c06182022-11-02 17:39:31 -0400142 <Card sx={{ backgroundColor: '#00000088', overflow: 'visible', textAlign: 'center' }}>
143 <Stack direction="row" justifyContent="flex-end" alignItems="flex-end">
144 <Button
145 variant="contained"
146 onClick={() => {
147 sendWebRTCOffer();
148 }}
149 >
150 {/* TODO: Remove this button and make calling automatic (https://git.jami.net/savoirfairelinux/jami-web/-/issues/91)*/}
151 Call
152 </Button>
153 <CallingMicButton />
154 <CallingEndButton />
155 <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
174 const [hiddenStackCount, setHiddenStackCount] = useState(0);
175 const [hiddenMenuVisible, setHiddenMenuVisible] = useState(false);
176
177 useLayoutEffect(() => {
178 const onResize = () => {
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
185 }
186 setHiddenStackCount(SECONDARY_BUTTONS.length - availableButtons);
187 }
188 };
189 window.addEventListener('resize', onResize);
190 return () => {
191 window.removeEventListener('resize', onResize);
192 };
193 }, [props.gridItemRef]);
194
195 const { displayedButtons, hiddenButtons } = useMemo(() => {
196 const displayedButtons: ComponentType<ExpandableButtonProps>[] = [];
197 const hiddenButtons: ComponentType<ExpandableButtonProps>[] = [];
198 SECONDARY_BUTTONS.forEach((button, i) => {
199 if (i < SECONDARY_BUTTONS.length - hiddenStackCount) {
200 displayedButtons.push(button);
201 } else {
202 hiddenButtons.push(button);
203 }
204 });
205
206 return {
207 displayedButtons,
208 hiddenButtons,
209 };
210 }, [hiddenStackCount]);
211
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400212 return (
simon33c06182022-11-02 17:39:31 -0400213 <Card sx={{ backgroundColor: '#00000088', overflow: 'visible' }}>
214 <Stack direction="row" justifyContent="flex-end" alignItems="flex-end" ref={stackRef}>
215 {displayedButtons.map((SecondaryButton, i) => (
216 <Fragment key={i}>
217 <SecondaryButton />
218 </Fragment>
219 ))}
220 {!!hiddenButtons.length && (
221 <Card sx={{ position: 'relative', backgroundColor: '#00000088', overflow: 'visible' }}>
222 <CallingMoreVerticalButton onClick={() => setHiddenMenuVisible(!hiddenMenuVisible)} />
223 <Stack
224 direction="column-reverse"
225 sx={{ bottom: 0, right: 0, height: '100%', position: 'absolute', top: '-40px' }}
226 >
227 {hiddenButtons.map((SecondaryButton, i) => (
228 <Fragment key={i}>
229 <SecondaryButton key={i} />
230 </Fragment>
231 ))}
232 </Stack>
233 </Card>
234 )}
235 </Stack>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400236 </Card>
237 );
238};