blob: 43d6211e439991edde2b496fc947373374db3947 [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 (
Charlie461805e2022-11-09 10:40:15 -050045 //TODO: set contactID
46 <WebRTCProvider isVideoOn={video === 'true'} contactId={'contactIdToBeAdded'}>
simon1170c322022-10-31 14:51:31 -040047 <CallInterface />
simonce2c0c42022-11-02 17:39:31 -040048 </WebRTCProvider>
simon1170c322022-10-31 14:51:31 -040049 );
50};
51
simon33c06182022-11-02 17:39:31 -040052export enum SecondaryButtons {
53 Volume = 1,
54 Group,
55 Chat,
56 ScreenShare,
57 Record,
58 Extension,
59 FullScreen,
60}
61
62interface Props {
63 children?: ReactNode;
64}
65
simon1170c322022-10-31 14:51:31 -040066const CallInterface = () => {
simonce2c0c42022-11-02 17:39:31 -040067 const { localVideoRef, remoteVideoRef, isVideoOn } = useContext(WebRTCContext);
simon33c06182022-11-02 17:39:31 -040068 const gridItemRef = useRef(null);
simonce2c0c42022-11-02 17:39:31 -040069
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040070 return (
71 <>
simonce2c0c42022-11-02 17:39:31 -040072 <video
73 ref={remoteVideoRef}
74 autoPlay
75 style={{ backgroundColor: 'black', width: '100%', height: '100%', position: 'absolute' }}
76 />
Gabriel Rochone3ec0d22022-10-08 14:27:03 -040077 <Stack
78 position="absolute"
79 direction="column"
80 spacing={1}
81 margin={2}
82 sx={{ left: 0, right: 0, top: 0, bottom: 0 }}
83 >
84 {/* Top panel with guest information */}
85 <Box>
86 <CallInterfaceInformation />
87 </Box>
88 {/* Guest video, with empty space to be moved around and stickied to walls */}
89 <Box height="100%">
simonce2c0c42022-11-02 17:39:31 -040090 {isVideoOn && (
simon33c06182022-11-02 17:39:31 -040091 <Draggable bounds="parent">
92 <video
93 ref={localVideoRef}
94 autoPlay
95 style={{
96 position: 'absolute',
97 right: 0,
98 zIndex: 2,
99 borderRadius: '12px',
100 minWidth: '25%',
101 minHeight: '25%',
102 maxWidth: '50%',
103 maxHeight: '50%',
104 }}
105 />
106 </Draggable>
simonce2c0c42022-11-02 17:39:31 -0400107 )}
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400108 </Box>
109 {/* Bottom panel with calling buttons */}
simon33c06182022-11-02 17:39:31 -0400110 <Grid container>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400111 <Grid item xs />
simon33c06182022-11-02 17:39:31 -0400112 <Grid item sx={{ display: 'flex', justifyContent: 'center' }}>
113 <div>
114 <CallInterfacePrimaryButtons />
115 </div>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400116 </Grid>
simon33c06182022-11-02 17:39:31 -0400117 <Grid item xs sx={{ display: 'flex', justifyContent: 'flex-end' }} ref={gridItemRef}>
118 <CallInterfaceSecondaryButtons gridItemRef={gridItemRef} />
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400119 </Grid>
120 </Grid>
121 </Stack>
122 </>
123 );
simon1170c322022-10-31 14:51:31 -0400124};
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400125
126const CallInterfaceInformation = () => {
127 return (
128 <Stack direction="row" justifyContent="space-between" alignItems="center">
129 <Typography color="white" component="p">
130 Alain Thérieur
131 </Typography>
132 <Typography color="white" component="p">
133 01:23
134 </Typography>
135 </Stack>
136 );
137};
138
139const CallInterfacePrimaryButtons = () => {
simonce2c0c42022-11-02 17:39:31 -0400140 const { sendWebRTCOffer } = useContext(WebRTCContext);
141
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400142 return (
simon33c06182022-11-02 17:39:31 -0400143 <Card sx={{ backgroundColor: '#00000088', overflow: 'visible', textAlign: 'center' }}>
144 <Stack direction="row" justifyContent="flex-end" alignItems="flex-end">
145 <Button
146 variant="contained"
147 onClick={() => {
148 sendWebRTCOffer();
149 }}
150 >
151 {/* TODO: Remove this button and make calling automatic (https://git.jami.net/savoirfairelinux/jami-web/-/issues/91)*/}
152 Call
153 </Button>
154 <CallingMicButton />
155 <CallingEndButton />
156 <CallingVideoCameraButton />
157 </Stack>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400158 </Card>
159 );
160};
161
simon33c06182022-11-02 17:39:31 -0400162const SECONDARY_BUTTONS = [
163 CallingVolumeButton,
164 CallingGroupButton,
165 CallingChatButton,
166 CallingScreenShareButton,
167 CallingRecordButton,
168 CallingExtensionButton,
169 CallingFullScreenButton,
170];
171
172const CallInterfaceSecondaryButtons = (props: Props & { gridItemRef: React.RefObject<HTMLElement> }) => {
173 const stackRef = useRef<HTMLElement>(null);
174
175 const [hiddenStackCount, setHiddenStackCount] = useState(0);
176 const [hiddenMenuVisible, setHiddenMenuVisible] = useState(false);
177
178 useLayoutEffect(() => {
179 const onResize = () => {
180 if (stackRef?.current && props.gridItemRef?.current) {
181 const buttonWidth = stackRef.current.children[0].clientWidth;
182 const availableSpace = props.gridItemRef.current.clientWidth;
183 let availableButtons = Math.floor((availableSpace - 1) / buttonWidth);
184 if (availableButtons < SECONDARY_BUTTONS.length) {
185 availableButtons -= 1; // Leave room for CallingMoreVerticalButton
186 }
187 setHiddenStackCount(SECONDARY_BUTTONS.length - availableButtons);
188 }
189 };
190 window.addEventListener('resize', onResize);
191 return () => {
192 window.removeEventListener('resize', onResize);
193 };
194 }, [props.gridItemRef]);
195
196 const { displayedButtons, hiddenButtons } = useMemo(() => {
197 const displayedButtons: ComponentType<ExpandableButtonProps>[] = [];
198 const hiddenButtons: ComponentType<ExpandableButtonProps>[] = [];
199 SECONDARY_BUTTONS.forEach((button, i) => {
200 if (i < SECONDARY_BUTTONS.length - hiddenStackCount) {
201 displayedButtons.push(button);
202 } else {
203 hiddenButtons.push(button);
204 }
205 });
206
207 return {
208 displayedButtons,
209 hiddenButtons,
210 };
211 }, [hiddenStackCount]);
212
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400213 return (
simon33c06182022-11-02 17:39:31 -0400214 <Card sx={{ backgroundColor: '#00000088', overflow: 'visible' }}>
215 <Stack direction="row" justifyContent="flex-end" alignItems="flex-end" ref={stackRef}>
216 {displayedButtons.map((SecondaryButton, i) => (
217 <Fragment key={i}>
218 <SecondaryButton />
219 </Fragment>
220 ))}
221 {!!hiddenButtons.length && (
222 <Card sx={{ position: 'relative', backgroundColor: '#00000088', overflow: 'visible' }}>
223 <CallingMoreVerticalButton onClick={() => setHiddenMenuVisible(!hiddenMenuVisible)} />
224 <Stack
225 direction="column-reverse"
226 sx={{ bottom: 0, right: 0, height: '100%', position: 'absolute', top: '-40px' }}
227 >
228 {hiddenButtons.map((SecondaryButton, i) => (
229 <Fragment key={i}>
230 <SecondaryButton key={i} />
231 </Fragment>
232 ))}
233 </Stack>
234 </Card>
235 )}
236 </Stack>
Gabriel Rochone3ec0d22022-10-08 14:27:03 -0400237 </Card>
238 );
239};