Skip to content

Commit

Permalink
Chore: VideoConference UX/UI Refactor 1st Interaction (#26183)
Browse files Browse the repository at this point in the history
Co-authored-by: Douglas Fabris <devfabris@gmail.com>
Co-authored-by: Guilherme Gazzo <guilherme@gazzo.xyz>
  • Loading branch information
3 people authored Jul 19, 2022
1 parent 074d382 commit dfedda4
Show file tree
Hide file tree
Showing 53 changed files with 732 additions and 798 deletions.
7 changes: 5 additions & 2 deletions apps/meteor/app/api/server/v1/videoConference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {

import { API } from '../api';
import { canAccessRoomIdAsync } from '../../../authorization/server/functions/canAccessRoom';
import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission';
import { VideoConf } from '../../../../server/sdk';
import { videoConfProviders } from '../../../../server/lib/videoConfProviders';
import { availabilityErrors } from '../../../../lib/videoConference/constants';
Expand All @@ -18,7 +19,7 @@ API.v1.addRoute(
{ authRequired: true, validateParams: isVideoConfStartProps, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 60000 } },
{
async post() {
const { roomId, title, allowRinging } = this.bodyParams;
const { roomId, title, allowRinging: requestRinging } = this.bodyParams;
const { userId } = this;
if (!userId || !(await canAccessRoomIdAsync(roomId, userId))) {
return API.v1.failure('invalid-params');
Expand All @@ -31,9 +32,11 @@ API.v1.addRoute(
throw new Error(availabilityErrors.NOT_ACTIVE);
}

const allowRinging = Boolean(requestRinging) && (await hasPermissionAsync(userId, 'videoconf-ring-users'));

return API.v1.success({
data: {
...(await VideoConf.start(userId, roomId, { title, allowRinging: Boolean(allowRinging) })),
...(await VideoConf.start(userId, roomId, { title, allowRinging })),
providerName,
},
});
Expand Down
5 changes: 3 additions & 2 deletions apps/meteor/app/apps/server/bridges/videoConferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ export class AppVideoConferenceBridge extends VideoConferenceBridge {
}
}

protected async registerProvider(info: IVideoConfProvider): Promise<void> {
videoConfProviders.registerProvider(info.name, info.capabilities || {});
protected async registerProvider(info: IVideoConfProvider, appId: string): Promise<void> {
this.orch.debugLog(`The App ${appId} is registering a video conference provider.`);
videoConfProviders.registerProvider(info.name, info.capabilities || {}, appId);
}

protected async unRegisterProvider(info: IVideoConfProvider): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export const upsertPermissions = async (): Promise<void> => {
{ _id: 'remove-slackbridge-links', roles: ['admin'] },
{ _id: 'view-import-operations', roles: ['admin'] },
{ _id: 'clear-oembed-cache', roles: ['admin'] },
{ _id: 'videoconf-ring-users', roles: ['admin', 'owner', 'moderator', 'user'] },
];

for await (const permission of permissions) {
Expand Down
3 changes: 2 additions & 1 deletion apps/meteor/app/custom-sounds/client/lib/CustomSounds.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const getCustomSoundId = (sound) => `custom-sound-${sound}`;
class CustomSoundsClass {
constructor() {
this.list = new ReactiveVar({});
this.add({ _id: 'calling', name: 'Calling', extension: 'mp3', src: getURL('sounds/calling.mp3') });
this.add({ _id: 'chime', name: 'Chime', extension: 'mp3', src: getURL('sounds/chime.mp3') });
this.add({ _id: 'door', name: 'Door', extension: 'mp3', src: getURL('sounds/door.mp3') });
this.add({ _id: 'beep', name: 'Beep', extension: 'mp3', src: getURL('sounds/beep.mp3') });
Expand Down Expand Up @@ -52,6 +51,8 @@ class CustomSoundsClass {
extension: 'mp3',
src: getURL('sounds/call-ended.mp3'),
});
this.add({ _id: 'dialtone', name: 'Dialtone', extension: 'mp3', src: getURL('sounds/dialtone.mp3') });
this.add({ _id: 'ringtone', name: 'Ringtone', extension: 'mp3', src: getURL('sounds/ringtone.mp3') });
}

add(sound) {
Expand Down
2 changes: 1 addition & 1 deletion apps/meteor/app/models/server/models/Users.js
Original file line number Diff line number Diff line change
Expand Up @@ -642,7 +642,7 @@ export class Users extends Base {
return this.find(query, options);
}

findOneByAppId(appId, options) {
findOneByAppId(appId, options = {}) {
const query = { appId };

return this.findOne(query, options);
Expand Down
36 changes: 36 additions & 0 deletions apps/meteor/client/lib/VideoConfManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter<Vide
this.hookNotification('video-conference.rejected', (params: DirectCallParams) => this.onDirectCallRejected(params));
this.hookNotification('video-conference.confirmed', (params: DirectCallParams) => this.onDirectCallConfirmed(params));
this.hookNotification('video-conference.join', (params: DirectCallParams) => this.onDirectCallJoined(params));
this.hookNotification('video-conference.end', (params: DirectCallParams) => this.onDirectCallEnded(params));
}

private abortIncomingCall(callId: string): void {
Expand Down Expand Up @@ -658,6 +659,41 @@ export const VideoConfManager = new (class VideoConfManager extends Emitter<Vide
this.onDirectCallAccepted(params, true);
}

private onDirectCallEnded(params: DirectCallParams): void {
if (!params.callId) {
debug && console.log(`[VideoConf] Invalid 'video-conference.end' event received: ${params.callId}, ${params.uid}.`);
return;
}

const callData = this.incomingDirectCalls.get(params.callId);
if (callData) {
debug && console.log(`[VideoConf] Incoming call ended by the server: ${params.callId}.`);
if (callData.acceptTimeout) {
clearTimeout(callData.acceptTimeout);
this.setIncomingCallAttribute(params.callId, 'acceptTimeout', undefined);
}

this.loseIncomingCall(params.callId);
return;
}

if (this.currentCallData?.callId !== params.callId) {
debug && console.log(`[VideoConf] Server sent a call ended event for a call we're not aware of: ${params.callId}.`);
return;
}

debug && console.log(`[VideoConf] Outgoing call ended by the server: ${params.callId}.`);

// Stop ringing
this.currentCallData = undefined;
if (this.currentCallHandler) {
clearInterval(this.currentCallHandler);
this.currentCallHandler = undefined;
this.emit('calling/changed');
this.emit('direct/stopped', params);
}
}

private onDirectCallRejected(params: DirectCallParams): void {
if (!params.callId || params.callId !== this.currentCallData?.callId) {
debug && console.log(`[VideoConf] User ${params.uid} has rejected a call ${params.callId} from us, but we're not calling.`);
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IRoom } from '@rocket.chat/core-typings';
import { Box, Skeleton } from '@rocket.chat/fuselage';
import { Skeleton } from '@rocket.chat/fuselage';
import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
import { useTranslation, useUser } from '@rocket.chat/ui-contexts';
import { useTranslation } from '@rocket.chat/ui-contexts';
import {
VideoConfPopup,
VideoConfPopupContent,
Expand All @@ -12,36 +12,26 @@ import {
VideoConfPopupFooter,
VideoConfPopupFooterButtons,
VideoConfPopupTitle,
VideoConfPopupIndicators,
VideoConfPopupClose,
VideoConfPopupUsername,
VideoConfPopupHeader,
} from '@rocket.chat/ui-video-conf';
import React, { ReactElement, useMemo } from 'react';

import ReactiveUserStatus from '../../../../../../components/UserStatus/ReactiveUserStatus';
import RoomAvatar from '../../../../../../components/avatar/RoomAvatar';
import { useVideoConfSetPreferences } from '../../../../../../contexts/VideoConfContext';
import { AsyncStatePhase } from '../../../../../../hooks/useAsyncState';
import { useEndpointData } from '../../../../../../hooks/useEndpointData';
import VideoConfPopupRoomInfo from './VideoConfPopupRoomInfo';

type ReceivingPopupProps = {
type IncomingPopupProps = {
id: string;
room: IRoom;
position: number;
current: number;
total: number;
onClose: (id: string) => void;
onMute: (id: string) => void;
onConfirm: () => void;
};

const ReceivingPopup = ({ id, room, position, current, total, onClose, onMute, onConfirm }: ReceivingPopupProps): ReactElement => {
const IncomingPopup = ({ id, room, position, onClose, onMute, onConfirm }: IncomingPopupProps): ReactElement => {
const t = useTranslation();
const user = useUser();
const userId = user?._id;
const directUserId = room.uids?.filter((uid) => uid !== userId).shift();
const [directUsername] = room.usernames?.filter((username) => username !== user?.username) || [];

const { controllersConfig, handleToggleMic, handleToggleCam } = useVideoConfControllers();
const setPreferences = useVideoConfSetPreferences();

Expand All @@ -57,55 +47,48 @@ const ReceivingPopup = ({ id, room, position, current, total, onClose, onMute, o

return (
<VideoConfPopup position={position}>
<VideoConfPopupContent>
<VideoConfPopupClose title={t('Close')} onClick={(): void => onMute(id)} />
<RoomAvatar room={room} size='x40' />
{current && total ? <VideoConfPopupIndicators current={current} total={total} /> : null}
<VideoConfPopupTitle text='Incoming call from' icon='phone-in' />
{directUserId && (
<Box display='flex' alignItems='center' mbs='x8'>
<ReactiveUserStatus uid={directUserId} />
{directUsername && <VideoConfPopupUsername username={directUsername} />}
</Box>
)}
<VideoConfPopupHeader>
<VideoConfPopupTitle text={t('Incoming_call_from')} />
{phase === AsyncStatePhase.LOADING && <Skeleton />}
{phase === AsyncStatePhase.RESOLVED && (showMic || showCam) && (
<VideoConfPopupControllers>
{showMic && (
<VideoConfController
active={controllersConfig.mic}
text={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
onClick={handleToggleMic}
/>
)}
{showCam && (
<VideoConfController
active={controllersConfig.cam}
text={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
title={controllersConfig.cam ? t('Cam_on') : t('Cam_off')}
icon={controllersConfig.cam ? 'video' : 'video-off'}
onClick={handleToggleCam}
/>
)}
{showMic && (
<VideoConfController
active={controllersConfig.mic}
title={controllersConfig.mic ? t('Mic_on') : t('Mic_off')}
icon={controllersConfig.mic ? 'mic' : 'mic-off'}
onClick={handleToggleMic}
/>
)}
</VideoConfPopupControllers>
)}
</VideoConfPopupHeader>
<VideoConfPopupContent>
<VideoConfPopupRoomInfo room={room} />
</VideoConfPopupContent>
<VideoConfPopupFooter>
<VideoConfPopupFooterButtons>
<VideoConfButton primary onClick={handleJoinCall}>
{t('Accept')}
</VideoConfButton>
{onClose && (
<VideoConfButton danger onClick={(): void => onClose(id)}>
<VideoConfButton danger secondary onClick={(): void => onClose(id)}>
{t('Decline')}
</VideoConfButton>
)}
<VideoConfController small={false} secondary title={t('Mute_and_dismiss')} icon='cross' onClick={(): void => onMute(id)} />
</VideoConfPopupFooterButtons>
</VideoConfPopupFooter>
</VideoConfPopup>
);
};

export default ReceivingPopup;
export default IncomingPopup;
Loading

0 comments on commit dfedda4

Please sign in to comment.