Skip to content

Commit e827c58

Browse files
committed
add video conference and small fixes
1 parent 5aaa40f commit e827c58

File tree

14 files changed

+284
-26
lines changed

14 files changed

+284
-26
lines changed

services/app/apps/codebattle/assets/js/widgets/App.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ const {
2727
const { gameUI: gameUIReducer, ...otherReducers } = reducers;
2828

2929
const gameUIPersistWhitelist = [
30+
'audioMute',
31+
'videoMute',
32+
'showVideoConferencePanel',
3033
'editorMode',
3134
'editorTheme',
3235
'streamMode',

services/app/apps/codebattle/assets/js/widgets/pages/game/ChatWidget.jsx

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import useChatRooms from '../../utils/useChatRooms';
2828
import useMachineStateSelector from '../../utils/useMachineStateSelector';
2929

3030
import Notifications from './Notifications';
31+
import VideoConference from './VideoConference';
3132

3233
function ChatWidget() {
3334
const { mainService } = useContext(RoomContext);
@@ -38,14 +39,15 @@ function ChatWidget() {
3839
const historyMessages = useSelector(selectors.chatHistoryMessagesSelector);
3940
const gameMode = useSelector(selectors.gameModeSelector);
4041
const useChat = useSelector(selectors.gameUseChatSelector);
42+
const showVideoConferencePanel = useSelector(selectors.showVideoConferencePanelSelector);
4143

4244
const openedReplayer = useMachineStateSelector(mainService, openedReplayerSelector);
4345
const isTestingRoom = useMachineStateSelector(mainService, inTestingRoomSelector);
4446
const isRestricted = useMachineStateSelector(mainService, isRestrictedContentSelector);
4547

4648
// const isTournamentGame = (gameMode === GameRoomModes.tournament);
4749
const isStandardGame = (gameMode === GameRoomModes.standard);
48-
const showChatInput = !openedReplayer && !isTestingRoom && useChat && !isRestricted;
50+
const showChatInput = !openedReplayer && !isTestingRoom && !isRestricted && useChat;
4951
// const showChatParticipants = !isTestingRoom && useChat && !isRestricted;
5052

5153
const disabledChatHeader = isTestingRoom || !isOnline || !useChat;
@@ -83,21 +85,27 @@ function ChatWidget() {
8385
'cb-game-chat-container cb-messages-container',
8486
)}
8587
>
86-
<ChatHeader showRooms={isStandardGame} disabled={disabledChatHeader} />
87-
{openedReplayer
88-
? (
89-
<Messages
90-
messages={historyMessages}
91-
disabled={disabledChatMessages}
92-
/>
93-
) : (
94-
<Messages
95-
displayMenu={displayMenu}
96-
messages={filteredMessages}
97-
disabled={disabledChatMessages}
98-
/>
88+
{showVideoConferencePanel ? (
89+
<VideoConference />
90+
) : (
91+
<>
92+
<ChatHeader showRooms={isStandardGame} disabled={disabledChatHeader} />
93+
{openedReplayer
94+
? (
95+
<Messages
96+
messages={historyMessages}
97+
disabled={disabledChatMessages}
98+
/>
99+
) : (
100+
<Messages
101+
displayMenu={displayMenu}
102+
messages={filteredMessages}
103+
disabled={disabledChatMessages}
104+
/>
105+
)}
106+
{showChatInput && <ChatInput inputRef={inputRef} disabled={disabledChatInput} />}
107+
</>
99108
)}
100-
{showChatInput && <ChatInput inputRef={inputRef} disabled={disabledChatInput} />}
101109
</div>
102110
<div className="flex-shrink-1 p-0 border-left rounded-right cb-game-control-container">
103111
<div className="d-flex flex-column justify-content-start overflow-auto h-100">

services/app/apps/codebattle/assets/js/widgets/pages/game/Notifications.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import BackToTournamentButton from './BackToTournamentButton';
1717
import GameResult from './GameResult';
1818
import GoToNextGame from './GoToNextGame';
1919
import ReplayerControlButton from './ReplayerControlButton';
20+
import VideoConferenceButton from './VideoConferenceButton';
2021

2122
function Notifications() {
2223
const { mainService } = useContext(RoomContext);
@@ -35,6 +36,10 @@ function Notifications() {
3536
return (
3637
<>
3738
{roomMachineState.matches({ room: roomMachineStates.testing }) && <BackToTaskBuilderButton />}
39+
{(isAdmin
40+
&& !roomMachineState.matches({ replayer: replayerMachineStates.off })
41+
&& !roomMachineState.matches({ room: roomMachineStates.testing })
42+
) && <VideoConferenceButton />}
3843
<ReplayerControlButton />
3944
{(isCurrentUserPlayer && roomMachineState.matches({ room: roomMachineStates.gameOver }))
4045
&& (
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, {
2+
memo,
3+
} from 'react';
4+
5+
import cn from 'classnames';
6+
7+
import Loading from '@/components/Loading';
8+
import useJitsiRoom from '@/utils/useJitsiRoom';
9+
10+
import i18n from '../../../i18n';
11+
12+
const mapStatusToDescription = {
13+
loading: i18n.t('Setup Conference Room'),
14+
ready: i18n.t('Conference Room Is Ready'),
15+
joinedGameRoom: i18n.t('Conference Room Is Started'),
16+
notSupported: i18n.t('Not Supported Browser'),
17+
noHaveApiKey: i18n.t('No have jitsi api key'),
18+
};
19+
20+
function ConferenceLoading({ status, hideLoader = false }) {
21+
return (
22+
<div className="d-flex flex-column">
23+
{!hideLoader && <Loading />}
24+
<small>{mapStatusToDescription[status]}</small>
25+
</div>
26+
);
27+
}
28+
29+
function VideoConference() {
30+
const {
31+
ref,
32+
status,
33+
} = useJitsiRoom();
34+
35+
const conferenceClassName = cn('w-100 h-100', {
36+
'd-none invisible absolute': status !== 'joinedGameRoom',
37+
});
38+
39+
return (
40+
<>
41+
{status !== 'joinedGameRoom' && (
42+
<div className="d-flex w-100 h-100 justify-content-center align-items-center">
43+
<ConferenceLoading
44+
status={status}
45+
hideLoader={['notSupported', 'noHaveApiKey'].includes(status)}
46+
/>
47+
</div>
48+
)}
49+
<div ref={ref} id="jaas-container" className={conferenceClassName} />
50+
</>
51+
);
52+
}
53+
54+
export default memo(VideoConference);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* global JitsiMeetExternalAPI */
2+
import React from 'react';
3+
4+
import { useDispatch, useSelector } from 'react-redux';
5+
6+
import { actions } from '@/slices';
7+
8+
import i18n from '../../../i18n';
9+
import * as selectors from '../../selectors';
10+
11+
function VideoConferenceButton() {
12+
const dispatch = useDispatch();
13+
14+
// const { audioMute, videoMute } = useSelector(selectors.videoConferenceSettingsSelector);
15+
const showVideoConferencePanel = useSelector(selectors.showVideoConferencePanelSelector);
16+
17+
const toggleVideoConference = () => {
18+
dispatch(actions.toggleShowVideoConferencePanel());
19+
};
20+
21+
if (!JitsiMeetExternalAPI) {
22+
return <></>;
23+
}
24+
25+
return (
26+
<>
27+
<button
28+
type="button"
29+
onClick={toggleVideoConference}
30+
className="btn btn-secondary btn-block rounded-lg"
31+
aria-label={
32+
showVideoConferencePanel
33+
? 'Open Text Chat'
34+
: 'Open Video Chat'
35+
}
36+
>
37+
{
38+
showVideoConferencePanel
39+
? i18n.t('Open Text Chat')
40+
: i18n.t('Open Video Chat')
41+
}
42+
</button>
43+
{/* {showVideoConferencePanel && ( */}
44+
{/* <div className="d-flex"> */}
45+
{/* <button */}
46+
{/* type="button" */}
47+
{/* className="btn btn-secondary btn-block w-100 rounded-lg" */}
48+
{/* aria-label="Mute audio" */}
49+
{/* /> */}
50+
{/* <button */}
51+
{/* type="button" */}
52+
{/* className="btn btn-secondary btn-block w-100 rounded-lg" */}
53+
{/* aria-label="Mute video" */}
54+
{/* /> */}
55+
{/* </div> */}
56+
{/* )} */}
57+
</>
58+
);
59+
}
60+
61+
export default VideoConferenceButton;

services/app/apps/codebattle/assets/js/widgets/selectors/index.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,17 @@ export const currentChatUserSelector = state => {
450450

451451
export const taskDescriptionLanguageSelector = state => state.gameUI.taskDescriptionLanguage;
452452

453+
export const videoConferenceSettingsSelector = createDraftSafeSelector(
454+
state => state.gameUI.audioMute,
455+
state => state.gameUI.videoMute,
456+
(audioMute, videoMute) => ({
457+
audioMute,
458+
videoMute,
459+
}),
460+
);
461+
462+
export const showVideoConferencePanelSelector = state => state.gameUI.showVideoConferencePanel;
463+
453464
export const playbookStatusSelector = state => state.playbook.state;
454465

455466
export const playbookInitRecordsSelector = state => state.playbook.initRecords;

services/app/apps/codebattle/assets/js/widgets/slices/gameUI.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ const initialState = {
1414
taskDescriptionLanguage: taskDescriptionLanguages.default,
1515
showToastActionsAfterGame: false,
1616
isShowGuide: false,
17+
showVideoConferencePanel: false,
18+
videoMute: true,
19+
audioMute: true,
1720
};
1821

1922
const gameUI = createSlice({
@@ -46,6 +49,15 @@ const gameUI = createSlice({
4649
toggleStreamMode: state => {
4750
state.streamMode = !state.streamMode;
4851
},
52+
toggleShowVideoConferencePanel: state => {
53+
state.showVideoConferencePanel = !state.showVideoConferencePanel;
54+
},
55+
setAudioMute: (state, payload) => {
56+
state.audioMute = payload;
57+
},
58+
setVideoMute: (state, payload) => {
59+
state.videoMute = payload;
60+
},
4961
},
5062
});
5163

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/* global JitsiMeetExternalAPI */
2+
import {
3+
useEffect, useMemo, useRef, useState,
4+
} from 'react';
5+
6+
import Gon from 'gon';
7+
import { useDispatch, useSelector } from 'react-redux';
8+
9+
import { actions } from '@/slices';
10+
11+
import * as selectors from '../selectors';
12+
13+
const apiKey = Gon.getAsset('jitsi_api_key');
14+
15+
const useJitsiRoom = () => {
16+
const dispatch = useDispatch();
17+
18+
const ref = useRef();
19+
const [status, setStatus] = useState('loading');
20+
const userId = useSelector(selectors.currentUserIdSelector);
21+
const gameId = useSelector(selectors.gameIdSelector);
22+
const { name } = useSelector(state => state.user.users[userId]);
23+
24+
const roomName = gameId ? `${apiKey}/codebattle_game_${gameId}` : `${apiKey}/codebattle_testing`;
25+
26+
useEffect(() => {
27+
if (!JitsiMeetExternalAPI) {
28+
dispatch(actions.toggleShowVideoConferencePanel());
29+
}
30+
31+
if (!apiKey) {
32+
setStatus('noHaveApiKey');
33+
}
34+
}, [dispatch]);
35+
36+
useEffect(() => {
37+
if (status === 'loading' && JitsiMeetExternalAPI && apiKey) {
38+
const newApi = new JitsiMeetExternalAPI('8x8.vc', {
39+
roomName,
40+
parentNode: ref.current,
41+
userInfo: {
42+
displayName: name,
43+
},
44+
configOverwrite: {
45+
prejoinPageEnabled: false,
46+
hideConferenceSubject: true,
47+
// hideConferenceTimer: true,
48+
toolbarButtons: [
49+
'camera',
50+
'microphone',
51+
'settings',
52+
],
53+
},
54+
});
55+
56+
newApi.addListener('browserSupport', payload => {
57+
if (payload.supported) {
58+
setStatus('ready');
59+
} else {
60+
setStatus('notSupported');
61+
}
62+
});
63+
64+
newApi.addListener('videoConferenceJoined', () => {
65+
setStatus('joinedGameRoom');
66+
});
67+
}
68+
// eslint-disable-next-line react-hooks/exhaustive-deps
69+
}, [status]);
70+
71+
return useMemo(() => ({
72+
ref,
73+
status,
74+
}), [ref, status]);
75+
};
76+
77+
export default useJitsiRoom;

services/app/apps/codebattle/lib/codebattle/user_game_report.ex

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ defmodule Codebattle.UserGameReport do
1515
:comment,
1616
:game_id,
1717
:id,
18+
:offender_id,
1819
:offender,
1920
:reason,
21+
:reporter_id,
2022
:reporter,
2123
:state,
2224
:tournament_id
@@ -79,8 +81,14 @@ defmodule Codebattle.UserGameReport do
7981
end
8082

8183
def create(params) do
82-
%__MODULE__{}
83-
|> changeset(params)
84-
|> Repo.insert()
84+
result =
85+
%__MODULE__{}
86+
|> changeset(params)
87+
|> Repo.insert()
88+
89+
case result do
90+
{:ok, report} -> {:ok, Repo.preload(report, [:offender, :reporter])}
91+
_ -> result
92+
end
8593
end
8694
end

0 commit comments

Comments
 (0)