You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			150 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			150 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			TypeScript
		
	
| import { useEffect, useRef, useState } from 'react';
 | |
| import Draggable, { DraggableData, DraggableEvent } from 'react-draggable';
 | |
| import { useSelector } from 'react-redux';
 | |
| 
 | |
| import styled from 'styled-components';
 | |
| import { useVideoCallEventsListener } from '../../hooks/useVideoEventListener';
 | |
| import { openConversationWithMessages } from '../../state/ducks/conversations';
 | |
| import { SectionType } from '../../state/ducks/section';
 | |
| import { getHasOngoingCall, getHasOngoingCallWith } from '../../state/selectors/call';
 | |
| import { getSection } from '../../state/selectors/section';
 | |
| import { useSelectedConversationKey } from '../../state/selectors/selectedConversation';
 | |
| import { Avatar, AvatarSize } from '../avatar/Avatar';
 | |
| import { VideoLoadingSpinner } from './InConversationCallContainer';
 | |
| 
 | |
| export const DraggableCallWindow = styled.div`
 | |
|   position: absolute;
 | |
|   z-index: 9;
 | |
|   box-shadow: var(--modal-drop-shadow);
 | |
|   max-height: 300px;
 | |
|   width: 12vw;
 | |
|   display: flex;
 | |
|   flex-direction: column;
 | |
|   background-color: var(--modal-background-content-color);
 | |
|   border: 1px solid var(--border-color);
 | |
|   border-radius: var(--border-radius);
 | |
| `;
 | |
| 
 | |
| export const StyledVideoElement = styled.video<{ isVideoMuted: boolean }>`
 | |
|   padding: 0 1rem;
 | |
|   height: 100%;
 | |
|   width: 100%;
 | |
|   opacity: ${props => (props.isVideoMuted ? 0 : 1)};
 | |
| `;
 | |
| 
 | |
| const StyledDraggableVideoElement = styled(StyledVideoElement)`
 | |
|   padding: 0 0;
 | |
| `;
 | |
| 
 | |
| const DraggableCallWindowInner = styled.div`
 | |
|   cursor: pointer;
 | |
|   min-width: 85px;
 | |
|   min-height: 85px;
 | |
| `;
 | |
| 
 | |
| const CenteredAvatarInDraggable = styled.div`
 | |
|   position: absolute;
 | |
|   width: 100%;
 | |
|   top: 0;
 | |
|   bottom: 0;
 | |
|   left: 0;
 | |
|   right: 50%;
 | |
|   min-height: 85px;
 | |
|   min-width: 85px;
 | |
|   display: flex;
 | |
|   justify-content: center;
 | |
|   align-items: center;
 | |
| `;
 | |
| 
 | |
| export const DraggableCallContainer = () => {
 | |
|   const ongoingCallProps = useSelector(getHasOngoingCallWith);
 | |
|   const selectedConversationKey = useSelectedConversationKey();
 | |
|   const hasOngoingCall = useSelector(getHasOngoingCall);
 | |
|   const selectedSection = useSelector(getSection);
 | |
| 
 | |
|   // the draggable container has a width of 12vw, so we just set it's X to a bit more than this
 | |
|   const [positionX, setPositionX] = useState(window.innerWidth - (window.innerWidth * 1) / 6);
 | |
|   // 90 px is a bit below the conversation header height
 | |
|   const [positionY, setPositionY] = useState(90);
 | |
|   const [lastPositionX, setLastPositionX] = useState(0);
 | |
|   const [lastPositionY, setLastPositionY] = useState(0);
 | |
| 
 | |
|   const ongoingCallPubkey = ongoingCallProps?.id;
 | |
|   const { remoteStreamVideoIsMuted, remoteStream } = useVideoCallEventsListener(
 | |
|     'DraggableCallContainer',
 | |
|     false
 | |
|   );
 | |
|   const videoRefRemote = useRef<HTMLVideoElement>(null);
 | |
| 
 | |
|   useEffect(() => {
 | |
|     function onWindowResize() {
 | |
|       if (positionY + 50 > window.innerHeight || positionX + 50 > window.innerWidth) {
 | |
|         setPositionX(window.innerWidth / 2);
 | |
|         setPositionY(window.innerHeight / 2);
 | |
|       }
 | |
|     }
 | |
|     window.addEventListener('resize', onWindowResize);
 | |
| 
 | |
|     return () => {
 | |
|       window.removeEventListener('resize', onWindowResize);
 | |
|     };
 | |
|   }, [positionX, positionY]);
 | |
| 
 | |
|   if (videoRefRemote?.current && remoteStream) {
 | |
|     if (videoRefRemote.current.srcObject !== remoteStream) {
 | |
|       videoRefRemote.current.srcObject = remoteStream;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   const openCallingConversation = () => {
 | |
|     if (ongoingCallPubkey && ongoingCallPubkey !== selectedConversationKey) {
 | |
|       void openConversationWithMessages({ conversationKey: ongoingCallPubkey, messageId: null });
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   if (
 | |
|     !hasOngoingCall ||
 | |
|     !ongoingCallProps ||
 | |
|     (ongoingCallPubkey === selectedConversationKey &&
 | |
|       selectedSection.focusedSection !== SectionType.Settings)
 | |
|   ) {
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   return (
 | |
|     <Draggable
 | |
|       handle=".dragHandle"
 | |
|       position={{ x: positionX, y: positionY }}
 | |
|       onStart={(_e: DraggableEvent, data: DraggableData) => {
 | |
|         setLastPositionX(data.x);
 | |
|         setLastPositionY(data.y);
 | |
|       }}
 | |
|       onStop={(e: DraggableEvent, data: DraggableData) => {
 | |
|         e.stopPropagation();
 | |
|         if (data.x === lastPositionX && data.y === lastPositionY) {
 | |
|           // drag did not change anything. Consider this to be a click
 | |
|           openCallingConversation();
 | |
|         }
 | |
|         setPositionX(data.x);
 | |
|         setPositionY(data.y);
 | |
|       }}
 | |
|     >
 | |
|       <DraggableCallWindow className="dragHandle">
 | |
|         <DraggableCallWindowInner>
 | |
|           <VideoLoadingSpinner fullWidth={true} />
 | |
|           <StyledDraggableVideoElement
 | |
|             ref={videoRefRemote}
 | |
|             autoPlay={true}
 | |
|             isVideoMuted={remoteStreamVideoIsMuted}
 | |
|           />
 | |
|           {remoteStreamVideoIsMuted && ongoingCallPubkey && (
 | |
|             <CenteredAvatarInDraggable>
 | |
|               <Avatar size={AvatarSize.XL} pubkey={ongoingCallPubkey} />
 | |
|             </CenteredAvatarInDraggable>
 | |
|           )}
 | |
|         </DraggableCallWindowInner>
 | |
|       </DraggableCallWindow>
 | |
|     </Draggable>
 | |
|   );
 | |
| };
 |