feat(conference) add end conference
Add the ability (for moderators) to end the meeting for everyone.
This commit is contained in:
parent
3bb581c8d9
commit
09efaecc41
|
@ -134,6 +134,20 @@
|
|||
}
|
||||
}
|
||||
|
||||
.hangup-menu-button {
|
||||
background-color: $hangupMenuButtonColor;
|
||||
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
&:hover {
|
||||
background-color: $hangupMenuButtonHoverColor;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-button-avatar {
|
||||
align-items: center;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
$baseFontFamily: -apple-system, BlinkMacSystemFont, 'open_sanslight', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
$hangupColor:#DD3849;
|
||||
$hangupHoverColor: #F25363;
|
||||
$hangupMenuButtonColor:#0056E0;;
|
||||
$hangupMenuButtonHoverColor: #246FE5;
|
||||
$hangupFontSize: 2em;
|
||||
|
||||
/**
|
||||
|
|
|
@ -52,6 +52,7 @@ VirtualHost "jitmeet.example.com"
|
|||
av_moderation_component = "avmoderation.jitmeet.example.com"
|
||||
speakerstats_component = "speakerstats.jitmeet.example.com"
|
||||
conference_duration_component = "conferenceduration.jitmeet.example.com"
|
||||
end_conference_component = "endconference.jitmeet.example.com"
|
||||
-- we need bosh
|
||||
modules_enabled = {
|
||||
"bosh";
|
||||
|
@ -60,6 +61,7 @@ VirtualHost "jitmeet.example.com"
|
|||
"speakerstats";
|
||||
"external_services";
|
||||
"conference_duration";
|
||||
"end_conference";
|
||||
"muc_lobby_rooms";
|
||||
"muc_breakout_rooms";
|
||||
"av_moderation";
|
||||
|
@ -123,6 +125,9 @@ Component "speakerstats.jitmeet.example.com" "speakerstats_component"
|
|||
Component "conferenceduration.jitmeet.example.com" "conference_duration_component"
|
||||
muc_component = "conference.jitmeet.example.com"
|
||||
|
||||
Component "endconference.jitmeet.example.com" "end_conference"
|
||||
muc_component = "conference.jitmeet.example.com"
|
||||
|
||||
Component "avmoderation.jitmeet.example.com" "av_moderation_component"
|
||||
muc_component = "conference.jitmeet.example.com"
|
||||
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": "اترك الاجتماع",
|
||||
"selectSoundDevice": "حدد جهاز الصوت"
|
||||
},
|
||||
"labels": {
|
||||
|
@ -1072,6 +1071,7 @@
|
|||
"invite": "ادعُ آخرين",
|
||||
"kick": "اطرد مشاركًا",
|
||||
"laugh": "يضحك",
|
||||
"leaveConference": "اترك الاجتماع",
|
||||
"like": "رفع الإبهام متمنيا النجاح",
|
||||
"linkToSalesforce": "ارتباط إلى Salesforce",
|
||||
"lobbyButton": "فعِّل/عطِّل وضع غرفة الانتظار",
|
||||
|
@ -1146,6 +1146,7 @@
|
|||
"joinBreakoutRoom": "انضم إلى غرفة الجانبية",
|
||||
"laugh": "يضحك",
|
||||
"leaveBreakoutRoom": "اترك إلى غرفة الجانبية",
|
||||
"leaveConference": "اترك الاجتماع",
|
||||
"like": "رفع الإبهام متمنيا النجاح",
|
||||
"linkToSalesforce": "ارتباط إلى Salesforce",
|
||||
"lobbyButtonDisable": "عطِّل وضع غرفة الانتظار",
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": "Abandona la reunió",
|
||||
"selectSoundDevice": "Seleccioneu l'aparell d'àudio"
|
||||
},
|
||||
"labels": {
|
||||
|
@ -1039,6 +1038,7 @@
|
|||
"invite": "Convida-hi persones",
|
||||
"kick": "Expulsa el participant",
|
||||
"laugh": "Riure",
|
||||
"leaveConference": "Abandona la reunió",
|
||||
"like": "Polzes amunt",
|
||||
"linkToSalesforce": "Enllaç a Salesforce",
|
||||
"lobbyButton": "Activa o desactiva la sala d'espera",
|
||||
|
@ -1111,6 +1111,7 @@
|
|||
"joinBreakoutRoom": "Entra a la sala de descans",
|
||||
"laugh": "Riure",
|
||||
"leaveBreakoutRoom": "Surt de la sala de descans",
|
||||
"leaveConference": "Abandona la reunió",
|
||||
"like": "Polzes amunt",
|
||||
"linkToSalesforce": "Enllaç a Salesforce",
|
||||
"lobbyButtonDisable": "Desactiva el mode de sala d'espera",
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": "Konferenz verlassen",
|
||||
"selectSoundDevice": "Audiogerät auswählen"
|
||||
},
|
||||
"labels": {
|
||||
|
@ -1066,6 +1065,7 @@
|
|||
"invite": "Person einladen",
|
||||
"kick": "Person entfernen",
|
||||
"laugh": "Lachen",
|
||||
"leaveConference": "Konferenz verlassen",
|
||||
"like": "Daumen nach oben",
|
||||
"linkToSalesforce": "Mit Salesforce verlinken",
|
||||
"lobbyButton": "Lobbymodus ein-/ausschalten",
|
||||
|
@ -1140,6 +1140,7 @@
|
|||
"joinBreakoutRoom": "In Breakout-Raum wechseln",
|
||||
"laugh": "Lachen",
|
||||
"leaveBreakoutRoom": "Breakout-Raum verlassen",
|
||||
"leaveConference": "Konferenz verlassen",
|
||||
"like": "Daumen hoch",
|
||||
"linkToSalesforce": "Mit Salesforce verknüpfen",
|
||||
"lobbyButtonDisable": "Lobbymodus deaktivieren",
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": "konferencu wopušćić",
|
||||
"selectSoundDevice": "nastroj za zwuk wuzwolić"
|
||||
},
|
||||
"labels": {
|
||||
|
@ -1045,6 +1044,7 @@
|
|||
"invite": "wobdźělnika přeprosyć",
|
||||
"kick": " wobdźělnika wuzamknyć",
|
||||
"laugh": "so smjeć",
|
||||
"leaveConference": "konferencu wopušćić",
|
||||
"like": "palc horje",
|
||||
"linkToSalesforce": "ze Salesforce zwjazać",
|
||||
"lobbyButton": "lobby-modus zapnyć/hasnyć",
|
||||
|
@ -1117,6 +1117,7 @@
|
|||
"joinBreakoutRoom": "do breakout rumnosće měnić",
|
||||
"laugh": "so smjeć",
|
||||
"leaveBreakoutRoom": "breakout rumnosć wopusćić",
|
||||
"leaveConference": "konferencu wopušćić",
|
||||
"like": "palc horje",
|
||||
"linkToSalesforce": "ze Salesforce zwjazać",
|
||||
"lobbyButtonDisable": "lobby-modus deaktiwěrować",
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": " Lascia riunione",
|
||||
"selectSoundDevice": "Scegli audio"
|
||||
},
|
||||
"labels": {
|
||||
|
@ -1045,6 +1044,7 @@
|
|||
"invite": "Invita partecipanti",
|
||||
"kick": "Espelli partecipante",
|
||||
"laugh": "Ridi",
|
||||
"leaveConference": " Lascia riunione",
|
||||
"like": "Mi piace",
|
||||
"linkToSalesforce": "Collega a Salesforce",
|
||||
"lobbyButton": "Attiva/Disattiva sala d'attesa",
|
||||
|
@ -1117,6 +1117,7 @@
|
|||
"joinBreakoutRoom": "Entra in sottogruppo",
|
||||
"laugh": "Ridi",
|
||||
"leaveBreakoutRoom": "Lascia breakout room",
|
||||
"leaveConference": " Lascia riunione",
|
||||
"like": "Mi piace",
|
||||
"linkToSalesforce": "Collega a Salesforce",
|
||||
"lobbyButtonDisable": "Disabilita sala d'attesa",
|
||||
|
|
|
@ -79,7 +79,6 @@
|
|||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": " Deixar a reunião",
|
||||
"selectSoundDevice": "Seleccionar dispositivo de som"
|
||||
},
|
||||
"labels": {
|
||||
|
@ -1076,6 +1075,7 @@
|
|||
"invite": "Convidar pessoas",
|
||||
"kick": "Remover participante",
|
||||
"laugh": "Risos",
|
||||
"leaveConference": "Deixar a reunião",
|
||||
"like": "Aprovado",
|
||||
"linkToSalesforce": "Link para a Salesforce",
|
||||
"lobbyButton": "Ativar/desativar sala de espera",
|
||||
|
@ -1150,6 +1150,7 @@
|
|||
"joinBreakoutRoom": "Entrar na sala",
|
||||
"laugh": "Risos",
|
||||
"leaveBreakoutRoom": "Sair da sala",
|
||||
"leaveConference": "Deixar a reunião",
|
||||
"like": "Aprovado",
|
||||
"linkToSalesforce": "Link para a Salesforce",
|
||||
"lobbyButtonDisable": "Desativar sala de espera",
|
||||
|
|
|
@ -78,7 +78,6 @@
|
|||
},
|
||||
"carmode": {
|
||||
"actions": {
|
||||
"leaveMeeting": " Leave meeting",
|
||||
"selectSoundDevice": "Select sound device"
|
||||
},
|
||||
"labels": {
|
||||
|
@ -1064,6 +1063,7 @@
|
|||
"document": "Toggle shared document",
|
||||
"download": "Download our apps",
|
||||
"embedMeeting": "Embed meeting",
|
||||
"endConference": "End meeting for all",
|
||||
"expand": "Expand",
|
||||
"feedback": "Leave feedback",
|
||||
"fullScreen": "Toggle full screen",
|
||||
|
@ -1074,6 +1074,7 @@
|
|||
"invite": "Invite people",
|
||||
"kick": "Kick participant",
|
||||
"laugh": "Laugh",
|
||||
"leaveConference": "Leave meeting",
|
||||
"like": "Thumbs Up",
|
||||
"linkToSalesforce": "Link to Salesforce",
|
||||
"lobbyButton": "Enable/disable lobby mode",
|
||||
|
@ -1136,6 +1137,7 @@
|
|||
"download": "Download our apps",
|
||||
"e2ee": "End-to-End Encryption",
|
||||
"embedMeeting": "Embed meeting",
|
||||
"endConference": "End meeting for all",
|
||||
"enterFullScreen": "View full screen",
|
||||
"enterTileView": "Enter tile view",
|
||||
"exitFullScreen": "Exit full screen",
|
||||
|
@ -1148,6 +1150,7 @@
|
|||
"joinBreakoutRoom": "Join breakout room",
|
||||
"laugh": "Laugh",
|
||||
"leaveBreakoutRoom": "Leave breakout room",
|
||||
"leaveConference": "Leave meeting",
|
||||
"like": "Thumbs Up",
|
||||
"linkToSalesforce": "Link to Salesforce",
|
||||
"lobbyButtonDisable": "Disable lobby mode",
|
||||
|
|
|
@ -6,9 +6,10 @@ import {
|
|||
createStartMutedConfigurationEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { appNavigate } from '../../app/actions';
|
||||
import { endpointMessageReceived } from '../../subtitles';
|
||||
import { getReplaceParticipant } from '../config/functions';
|
||||
import { JITSI_CONNECTION_CONFERENCE_KEY } from '../connection';
|
||||
import { JITSI_CONNECTION_CONFERENCE_KEY, disconnect } from '../connection';
|
||||
import { JitsiConferenceEvents, JitsiE2ePingEvents } from '../lib-jitsi-meet';
|
||||
import {
|
||||
MEDIA_TYPE,
|
||||
|
@ -27,6 +28,7 @@ import {
|
|||
participantRoleChanged,
|
||||
participantUpdated
|
||||
} from '../participants';
|
||||
import { toState } from '../redux';
|
||||
import {
|
||||
destroyLocalTracks,
|
||||
getLocalTracks,
|
||||
|
@ -75,6 +77,7 @@ import {
|
|||
commonUserLeftHandling,
|
||||
getConferenceOptions,
|
||||
getCurrentConference,
|
||||
getConferenceState,
|
||||
sendLocalParticipant
|
||||
} from './functions';
|
||||
import logger from './logger';
|
||||
|
@ -584,6 +587,19 @@ export function dataChannelOpened() {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to end a conference for all participants.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function endConference() {
|
||||
return async (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const { conference } = getConferenceState(toState(getState));
|
||||
|
||||
conference?.end();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Signals that we've been kicked out of the conference.
|
||||
*
|
||||
|
@ -605,6 +621,25 @@ export function kickedOut(conference: Object, participant: Object) {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action to leave a conference.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function leaveConference() {
|
||||
return async (dispatch: Dispatch<any>) => {
|
||||
|
||||
// FIXME: these should be unified.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
dispatch(appNavigate(undefined));
|
||||
} else {
|
||||
dispatch(disconnect(true));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Signals that the lock state of a specific JitsiConference changed.
|
||||
*
|
||||
|
|
|
@ -11,10 +11,7 @@ import {
|
|||
import { reloadNow } from '../../app/actions';
|
||||
import { removeLobbyChatParticipant } from '../../chat/actions.any';
|
||||
import { openDisplayNamePrompt } from '../../display-name';
|
||||
import {
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
showErrorNotification
|
||||
} from '../../notifications';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, showErrorNotification, showWarningNotification } from '../../notifications';
|
||||
import { CONNECTION_ESTABLISHED, CONNECTION_FAILED, connectionDisconnected } from '../connection';
|
||||
import { validateJwt } from '../jwt';
|
||||
import { JitsiConferenceErrors } from '../lib-jitsi-meet';
|
||||
|
@ -132,7 +129,7 @@ function _conferenceFailed({ dispatch, getState }, next, action) {
|
|||
case JitsiConferenceErrors.CONFERENCE_DESTROYED: {
|
||||
const [ reason ] = error.params;
|
||||
|
||||
dispatch(showErrorNotification({
|
||||
dispatch(showWarningNotification({
|
||||
description: reason,
|
||||
titleKey: 'dialog.sessTerminated'
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
|
|
|
@ -29,9 +29,9 @@ const EndMeetingButton = () : JSX.Element => {
|
|||
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = 'carmode.actions.leaveMeeting'
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.leaveConference'
|
||||
icon = { EndMeetingIcon }
|
||||
labelKey = 'carmode.actions.leaveMeeting'
|
||||
labelKey = 'toolbar.leaveConference'
|
||||
onClick = { onSelect }
|
||||
style = { styles.endMeetingButton }
|
||||
type = { BUTTON_TYPES.DESTRUCTIVE } />
|
||||
|
|
|
@ -29,6 +29,16 @@ export const FULL_SCREEN_CHANGED = 'FULL_SCREEN_CHANGED';
|
|||
*/
|
||||
export const SET_FULL_SCREEN = 'SET_FULL_SCREEN';
|
||||
|
||||
/**
|
||||
* The type of the (redux) action which shows/hides the hangup menu.
|
||||
*
|
||||
* {
|
||||
* type: SET_HANGUP_MENU_VISIBLE,
|
||||
* visible: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_HANGUP_MENU_VISIBLE = 'SET_HANGUP_MENU_VISIBLE';
|
||||
|
||||
/**
|
||||
* The type of the redux action that toggles whether the overflow menu(s) should be shown as drawers.
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
CLEAR_TOOLBOX_TIMEOUT,
|
||||
FULL_SCREEN_CHANGED,
|
||||
SET_FULL_SCREEN,
|
||||
SET_HANGUP_MENU_VISIBLE,
|
||||
SET_OVERFLOW_DRAWER,
|
||||
SET_OVERFLOW_MENU_VISIBLE,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
|
@ -188,6 +189,22 @@ export function clearToolboxTimeout(): Object {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides the hangup menu.
|
||||
*
|
||||
* @param {boolean} visible - True to show it or false to hide it.
|
||||
* @returns {{
|
||||
* type: SET_HANGUP_MENU_VISIBLE,
|
||||
* visible: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setHangupMenuVisible(visible: boolean): Object {
|
||||
return {
|
||||
type: SET_HANGUP_MENU_VISIBLE,
|
||||
visible
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows/hides the overflow menu.
|
||||
*
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
import _ from 'lodash';
|
||||
|
||||
import { createToolbarEvent, sendAnalytics } from '../../analytics';
|
||||
import { appNavigate } from '../../app/actions';
|
||||
import { disconnect } from '../../base/connection';
|
||||
import { leaveConference } from '../../base/conference/actions';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractHangupButton } from '../../base/toolbox/components';
|
||||
|
@ -44,13 +43,7 @@ class HangupButton extends AbstractHangupButton<Props, *> {
|
|||
|
||||
this._hangup = _.once(() => {
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
|
||||
// FIXME: these should be unified.
|
||||
if (navigator.product === 'ReactNative') {
|
||||
this.props.dispatch(appNavigate(undefined));
|
||||
} else {
|
||||
this.props.dispatch(disconnect(true));
|
||||
}
|
||||
this.props.dispatch(leaveConference());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { View } from 'react-native';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// @ts-ignore
|
||||
import { createBreakoutRoomsEvent, createToolbarEvent, sendAnalytics } from '../../../analytics';
|
||||
// @ts-ignore
|
||||
import { appNavigate } from '../../../app/actions';
|
||||
// @ts-ignore
|
||||
import { ColorSchemeRegistry } from '../../../base/color-scheme';
|
||||
// @ts-ignore
|
||||
import { endConference } from '../../../base/conference';
|
||||
// @ts-ignore
|
||||
import { hideSheet } from '../../../base/dialog';
|
||||
// @ts-ignore
|
||||
import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
|
||||
// @ts-ignore
|
||||
import { getLocalParticipant, PARTICIPANT_ROLE } from '../../../base/participants';
|
||||
import Button from '../../../base/ui/components/native/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants';
|
||||
// @ts-ignore
|
||||
import { moveToRoom } from '../../../breakout-rooms/actions';
|
||||
// @ts-ignore
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
|
||||
/**
|
||||
* Menu presenting options to leave a room or meeting and to end meeting.
|
||||
*
|
||||
* @returns {JSX.Element} - The hangup menu.
|
||||
*/
|
||||
function HangupMenu() {
|
||||
const dispatch = useDispatch();
|
||||
const _styles = useSelector(state => ColorSchemeRegistry.get(state, 'Toolbox'));
|
||||
const inBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
const isModerator = useSelector(state => getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR);
|
||||
const { DESTRUCTIVE, SECONDARY } = BUTTON_TYPES;
|
||||
|
||||
const handleEndConference = useCallback(() => {
|
||||
dispatch(hideSheet());
|
||||
sendAnalytics(createToolbarEvent('endmeeting'));
|
||||
dispatch(endConference());
|
||||
}, [ hideSheet ]);
|
||||
|
||||
const handleLeaveConference = useCallback(() => {
|
||||
dispatch(hideSheet());
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
dispatch(appNavigate(undefined));
|
||||
}, [ hideSheet ]);
|
||||
|
||||
const handleLeaveBreakoutRoom = useCallback(() => {
|
||||
dispatch(hideSheet());
|
||||
sendAnalytics(createBreakoutRoomsEvent('leave'));
|
||||
dispatch(moveToRoom());
|
||||
}, [ hideSheet ]);
|
||||
|
||||
return (
|
||||
<BottomSheet>
|
||||
<View style = { _styles.hangupMenuContainer }>
|
||||
{ isModerator && <Button
|
||||
accessibilityLabel = 'toolbar.endConference'
|
||||
labelKey = 'toolbar.endConference'
|
||||
onClick = { handleEndConference }
|
||||
style = { _styles.hangupButton }
|
||||
type = { DESTRUCTIVE } /> }
|
||||
<Button
|
||||
accessibilityLabel = 'toolbar.leaveConference'
|
||||
labelKey = 'toolbar.leaveConference'
|
||||
onClick = { handleLeaveConference }
|
||||
style = { _styles.hangupButton }
|
||||
type = { SECONDARY } />
|
||||
{ inBreakoutRoom && <Button
|
||||
accessibilityLabel = 'breakoutRooms.actions.leaveBreakoutRoom'
|
||||
labelKey = 'breakoutRooms.actions.leaveBreakoutRoom'
|
||||
onClick = { handleLeaveBreakoutRoom }
|
||||
style = { _styles.hangupButton }
|
||||
type = { SECONDARY } /> }
|
||||
</View>
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
||||
|
||||
export default HangupMenu;
|
|
@ -0,0 +1,36 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
// @ts-ignore
|
||||
import { openSheet } from '../../../base/dialog';
|
||||
// @ts-ignore
|
||||
import { IconHangup } from '../../../base/icons';
|
||||
import IconButton from '../../../base/react/components/native/IconButton';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants';
|
||||
|
||||
import HangupMenu from './HangupMenu';
|
||||
|
||||
|
||||
/**
|
||||
* Button for showing the hangup menu.
|
||||
*
|
||||
* @returns {JSX.Element} - The hangup menu button.
|
||||
*/
|
||||
const HangupMenuButton = () : JSX.Element => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
dispatch(openSheet(HangupMenu));
|
||||
}, [ dispatch ]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.hangup'
|
||||
onPress = { onSelect }
|
||||
src = { IconHangup }
|
||||
type = { BUTTON_TYPES.PRIMARY } />
|
||||
);
|
||||
};
|
||||
|
||||
export default HangupMenuButton;
|
|
@ -18,6 +18,7 @@ import AudioMuteButton from '../AudioMuteButton';
|
|||
import HangupButton from '../HangupButton';
|
||||
import VideoMuteButton from '../VideoMuteButton';
|
||||
|
||||
import HangupMenuButton from './HangupMenuButton';
|
||||
import OverflowMenuButton from './OverflowMenuButton';
|
||||
import RaiseHandButton from './RaiseHandButton';
|
||||
import styles from './styles';
|
||||
|
@ -27,6 +28,11 @@ import styles from './styles';
|
|||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* Whether the end conference feature is supported.
|
||||
*/
|
||||
_endConferenceSupported: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the reactions feature is enabled.
|
||||
*/
|
||||
|
@ -55,14 +61,14 @@ type Props = {
|
|||
* @returns {React$Element}.
|
||||
*/
|
||||
function Toolbox(props: Props) {
|
||||
const { _reactionsEnabled, _styles, _visible, _width } = props;
|
||||
const { _endConferenceSupported, _reactionsEnabled, _styles, _visible, _width } = props;
|
||||
|
||||
if (!_visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bottomEdge = Platform.OS === 'ios' && _visible;
|
||||
const { buttonStylesBorderless, hangupButtonStyles, toggledButtonStyles } = _styles;
|
||||
const { buttonStylesBorderless, hangupButtonStyles, hangupMenuButtonStyles, toggledButtonStyles } = _styles;
|
||||
const additionalButtons = getMovableButtons(_width);
|
||||
const backgroundToggledStyle = {
|
||||
...toggledButtonStyles,
|
||||
|
@ -110,8 +116,13 @@ function Toolbox(props: Props) {
|
|||
<OverflowMenuButton
|
||||
styles = { buttonStylesBorderless }
|
||||
toggledStyles = { toggledButtonStyles } />
|
||||
<HangupButton
|
||||
styles = { hangupButtonStyles } />
|
||||
{ _endConferenceSupported
|
||||
? <HangupMenuButton
|
||||
styles = { hangupMenuButtonStyles }
|
||||
toggledStyles = { toggledButtonStyles } />
|
||||
: <HangupButton
|
||||
styles = { hangupButtonStyles } />
|
||||
}
|
||||
</SafeAreaView>
|
||||
</View>
|
||||
);
|
||||
|
@ -127,7 +138,11 @@ function Toolbox(props: Props) {
|
|||
* @returns {Props}
|
||||
*/
|
||||
function _mapStateToProps(state: Object): Object {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const endConferenceSupported = conference?.isEndConferenceSupported();
|
||||
|
||||
return {
|
||||
_endConferenceSupported: Boolean(endConferenceSupported),
|
||||
_styles: ColorSchemeRegistry.get(state, 'Toolbox'),
|
||||
_visible: isToolboxVisible(state),
|
||||
_width: state['features/base/responsive-ui'].clientWidth,
|
||||
|
|
|
@ -133,6 +133,17 @@ ColorSchemeRegistry.register('Toolbox', {
|
|||
backgroundColor: BaseTheme.palette.ui13
|
||||
},
|
||||
|
||||
hangupMenuContainer: {
|
||||
marginHorizontal: BaseTheme.spacing[2],
|
||||
marginVertical: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
hangupButton: {
|
||||
flex: 1,
|
||||
marginHorizontal: BaseTheme.spacing[2],
|
||||
marginVertical: BaseTheme.spacing[2]
|
||||
},
|
||||
|
||||
hangupButtonStyles: {
|
||||
iconStyle: whiteToolbarButtonIcon,
|
||||
style: {
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
// @ts-ignore
|
||||
import { endConference } from '../../../base/conference';
|
||||
// @ts-ignore
|
||||
import { isLocalParticipantModerator } from '../../../base/participants';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants';
|
||||
// @ts-ignore
|
||||
import { isInBreakoutRoom } from '../../../breakout-rooms/functions';
|
||||
|
||||
/**
|
||||
* Button to end the conference for all participants.
|
||||
*
|
||||
* @returns {JSX.Element} - The end conference button.
|
||||
*/
|
||||
export const EndConferenceButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const _isLocalParticipantModerator = useSelector(isLocalParticipantModerator);
|
||||
const _isInBreakoutRoom = useSelector(isInBreakoutRoom);
|
||||
|
||||
const onEndConference = useCallback(() => {
|
||||
dispatch(endConference());
|
||||
}, [ dispatch ]);
|
||||
|
||||
return (<>
|
||||
{ !_isInBreakoutRoom && _isLocalParticipantModerator && <Button
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.endConference') }
|
||||
fullWidth = { true }
|
||||
label = { t('toolbar.endConference') }
|
||||
onClick = { onEndConference }
|
||||
type = { BUTTON_TYPES.DESTRUCTIVE } /> }
|
||||
</>);
|
||||
};
|
|
@ -0,0 +1,130 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
import InlineDialog from '@atlaskit/inline-dialog';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
// @ts-ignore
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../analytics';
|
||||
// @ts-ignore
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
import HangupToggleButton from './HangupToggleButton';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link HangupMenuButton}.
|
||||
*/
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* ID of the menu that is controlled by this button.
|
||||
*/
|
||||
ariaControls: String,
|
||||
|
||||
/**
|
||||
* A child React Element to display within {@code InlineDialog}.
|
||||
*/
|
||||
children: React.ReactNode,
|
||||
|
||||
/**
|
||||
* Whether or not the HangupMenu popover should display.
|
||||
*/
|
||||
isOpen: boolean,
|
||||
|
||||
/**
|
||||
* Callback to change the visibility of the hangup menu.
|
||||
*/
|
||||
onVisibilityChange: Function,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* A React {@code Component} for opening or closing the {@code HangupMenu}.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class HangupMenuButton extends Component<Props> {
|
||||
/**
|
||||
* Initializes a new {@code HangupMenuButton} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onCloseDialog = this._onCloseDialog.bind(this);
|
||||
this._toggleDialogVisibility
|
||||
= this._toggleDialogVisibility.bind(this);
|
||||
this._onEscClick = this._onEscClick.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click handler for the more actions entries.
|
||||
*
|
||||
* @param {KeyboardEvent} event - Esc key click to close the popup.
|
||||
* @returns {void}
|
||||
*/
|
||||
_onEscClick(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape' && this.props.isOpen) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this._onCloseDialog();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { children, isOpen } = this.props;
|
||||
|
||||
return (
|
||||
<div className = 'toolbox-button-wth-dialog context-menu'>
|
||||
<InlineDialog
|
||||
content = { children }
|
||||
isOpen = { isOpen }
|
||||
onClose = { this._onCloseDialog }
|
||||
placement = 'top-end'>
|
||||
<HangupToggleButton
|
||||
customClass = 'hangup-menu-button'
|
||||
handleClick = { this._toggleDialogVisibility }
|
||||
isOpen = { isOpen }
|
||||
onKeyDown = { this._onEscClick } />
|
||||
</InlineDialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked when {@code InlineDialog} signals that it should be
|
||||
* close.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onCloseDialog() {
|
||||
this.props.onVisibilityChange(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked to signal that an event has occurred that should change
|
||||
* the visibility of the {@code InlineDialog} component.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_toggleDialogVisibility() {
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
|
||||
this.props.onVisibilityChange(!this.props.isOpen);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(HangupMenuButton);
|
|
@ -0,0 +1,75 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
// @ts-ignore
|
||||
import { translate } from '../../../base/i18n';
|
||||
// @ts-ignore
|
||||
import { IconClose, IconHangup } from '../../../base/icons';
|
||||
// @ts-ignore
|
||||
import { AbstractButton, type AbstractButtonProps } from '../../../base/toolbox/components';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link HangupToggleButton}.
|
||||
*/
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether the more options menu is open.
|
||||
*/
|
||||
isOpen: boolean,
|
||||
|
||||
/**
|
||||
* External handler for key down action.
|
||||
*/
|
||||
onKeyDown: Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* Implementation of a button for toggling the hangup menu.
|
||||
*/
|
||||
class HangupToggleButton extends AbstractButton<Props, any, any> {
|
||||
accessibilityLabel = 'toolbar.accessibilityLabel.hangup';
|
||||
icon = IconHangup;
|
||||
label = 'toolbar.hangup';
|
||||
toggledIcon = IconClose;
|
||||
toggledLabel = 'toolbar.hangup';
|
||||
props: Props;
|
||||
|
||||
/**
|
||||
* Retrieves tooltip dynamically.
|
||||
*/
|
||||
get tooltip() {
|
||||
return 'toolbar.hangup';
|
||||
}
|
||||
|
||||
/**
|
||||
* Required by linter due to AbstractButton overwritten prop being writable.
|
||||
*
|
||||
* @param {string} _value - The value.
|
||||
*/
|
||||
set tooltip(_value) {
|
||||
// Unused.
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this button is in toggled state or not.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_isToggled() {
|
||||
return this.props.isOpen;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether a key was pressed.
|
||||
*
|
||||
* @override
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_onKeyDown() {
|
||||
this.props.onKeyDown();
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(HangupToggleButton);
|
|
@ -0,0 +1,35 @@
|
|||
/* eslint-disable lines-around-comment */
|
||||
import React, { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
// @ts-ignore
|
||||
import { createToolbarEvent, sendAnalytics } from '../../../analytics';
|
||||
// @ts-ignore
|
||||
import { leaveConference } from '../../../base/conference/actions';
|
||||
import Button from '../../../base/ui/components/web/Button';
|
||||
import { BUTTON_TYPES } from '../../../base/ui/constants';
|
||||
|
||||
/**
|
||||
* Button to leave the conference.
|
||||
*
|
||||
* @returns {JSX.Element} - The leave conference button.
|
||||
*/
|
||||
export const LeaveConferenceButton = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onLeaveConference = useCallback(() => {
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
dispatch(leaveConference());
|
||||
}, [ dispatch ]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
accessibilityLabel = { t('toolbar.accessibilityLabel.leaveConference') }
|
||||
fullWidth = { true }
|
||||
label = { t('toolbar.leaveConference') }
|
||||
onClick = { onLeaveConference }
|
||||
type = { BUTTON_TYPES.SECONDARY } />
|
||||
);
|
||||
};
|
|
@ -108,6 +108,7 @@ import { VideoBackgroundButton, toggleBackgroundEffect } from '../../../virtual-
|
|||
import { VIRTUAL_BACKGROUND_TYPE } from '../../../virtual-background/constants';
|
||||
import {
|
||||
setFullScreen,
|
||||
setHangupMenuVisible,
|
||||
setOverflowMenuVisible,
|
||||
setToolbarHovered,
|
||||
showToolbox
|
||||
|
@ -127,8 +128,11 @@ import HelpButton from '../HelpButton';
|
|||
import AudioSettingsButton from './AudioSettingsButton';
|
||||
// @ts-ignore
|
||||
import DockIframeButton from './DockIframeButton';
|
||||
import { EndConferenceButton } from './EndConferenceButton';
|
||||
// @ts-ignore
|
||||
import FullscreenButton from './FullscreenButton';
|
||||
import HangupMenuButton from './HangupMenuButton';
|
||||
import { LeaveConferenceButton } from './LeaveConferenceButton';
|
||||
// @ts-ignore
|
||||
import LinkToSalesforceButton from './LinkToSalesforceButton';
|
||||
// @ts-ignore
|
||||
|
@ -199,6 +203,11 @@ interface Props extends WithTranslation {
|
|||
*/
|
||||
_disabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether the end conference feature is supported.
|
||||
*/
|
||||
_endConferenceSupported: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not call feedback can be sent.
|
||||
*/
|
||||
|
@ -214,12 +223,17 @@ interface Props extends WithTranslation {
|
|||
*/
|
||||
_gifsEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether the hangup menu is visible.
|
||||
*/
|
||||
_hangupMenuVisible: boolean,
|
||||
|
||||
/**
|
||||
* Whether the app has Salesforce integration.
|
||||
*/
|
||||
_hasSalesforce: boolean,
|
||||
|
||||
/**
|
||||
/**
|
||||
* Whether or not the app is running in an ios mobile browser.
|
||||
*/
|
||||
_isIosMobile: boolean,
|
||||
|
@ -335,6 +349,15 @@ const styles = () => {
|
|||
right: 'auto',
|
||||
maxHeight: 'inherit',
|
||||
margin: 0
|
||||
},
|
||||
hangupMenu: {
|
||||
position: 'relative',
|
||||
right: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
rowGap: '8px',
|
||||
margin: 0,
|
||||
padding: '16px'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
@ -357,6 +380,7 @@ class Toolbox extends Component<Props> {
|
|||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onMouseOut = this._onMouseOut.bind(this);
|
||||
this._onMouseOver = this._onMouseOver.bind(this);
|
||||
this._onSetHangupVisible = this._onSetHangupVisible.bind(this);
|
||||
this._onSetOverflowVisible = this._onSetOverflowVisible.bind(this);
|
||||
this._onTabIn = this._onTabIn.bind(this);
|
||||
|
||||
|
@ -482,7 +506,7 @@ class Toolbox extends Component<Props> {
|
|||
* @inheritdoc
|
||||
*/
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const { _dialog, dispatch } = this.props;
|
||||
const { _dialog, _visible, dispatch } = this.props;
|
||||
|
||||
|
||||
if (prevProps._overflowMenuVisible
|
||||
|
@ -491,6 +515,12 @@ class Toolbox extends Component<Props> {
|
|||
this._onSetOverflowVisible(false);
|
||||
dispatch(setToolbarHovered(false));
|
||||
}
|
||||
if (prevProps._hangupMenuVisible
|
||||
&& prevProps._visible
|
||||
&& !_visible) {
|
||||
this._onSetHangupVisible(false);
|
||||
dispatch(setToolbarHovered(false));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -535,7 +565,7 @@ class Toolbox extends Component<Props> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Key handler for overflow menu.
|
||||
* Key handler for overflow/hangup menus.
|
||||
*
|
||||
* @param {KeyboardEvent} e - Esc key click to close the popup.
|
||||
* @returns {void}
|
||||
|
@ -543,10 +573,23 @@ class Toolbox extends Component<Props> {
|
|||
_onEscKey(e: React.KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
e.stopPropagation();
|
||||
this._closeHangupMenuIfOpen();
|
||||
this._closeOverflowMenuIfOpen();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the hangup menu if opened.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_closeHangupMenuIfOpen() {
|
||||
const { dispatch, _hangupMenuVisible } = this.props;
|
||||
|
||||
_hangupMenuVisible && dispatch(setHangupMenuVisible(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the overflow menu if opened.
|
||||
*
|
||||
|
@ -1012,6 +1055,19 @@ class Toolbox extends Component<Props> {
|
|||
this.props.dispatch(setToolbarHovered(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the hangup menu.
|
||||
*
|
||||
* @param {boolean} visible - Whether or not the hangup menu should be
|
||||
* displayed.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
_onSetHangupVisible(visible: boolean) {
|
||||
this.props.dispatch(setHangupMenuVisible(visible));
|
||||
this.props.dispatch(setToolbarHovered(visible));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the visibility of the overflow menu.
|
||||
*
|
||||
|
@ -1307,6 +1363,8 @@ class Toolbox extends Component<Props> {
|
|||
*/
|
||||
_renderToolboxContent() {
|
||||
const {
|
||||
_endConferenceSupported,
|
||||
_hangupMenuVisible,
|
||||
_isMobile,
|
||||
_overflowDrawer,
|
||||
_overflowMenuVisible,
|
||||
|
@ -1383,12 +1441,30 @@ class Toolbox extends Component<Props> {
|
|||
</OverflowMenuButton>
|
||||
)}
|
||||
|
||||
<HangupButton
|
||||
buttonKey = 'hangup'
|
||||
customClass = 'hangup-button'
|
||||
key = 'hangup-button'
|
||||
notifyMode = { this._getButtonNotifyMode('hangup') }
|
||||
visible = { isToolbarButtonEnabled('hangup', _toolbarButtons) } />
|
||||
{ isToolbarButtonEnabled('hangup', _toolbarButtons) && (
|
||||
_endConferenceSupported
|
||||
? <HangupMenuButton
|
||||
ariaControls = 'hangup-menu'
|
||||
isOpen = { _hangupMenuVisible }
|
||||
key = 'hangup-menu'
|
||||
onVisibilityChange = { this._onSetHangupVisible }>
|
||||
<ContextMenu
|
||||
accessibilityLabel = { t(toolbarAccLabel) }
|
||||
className = { classes.hangupMenu }
|
||||
hidden = { false }
|
||||
inDrawer = { _overflowDrawer }
|
||||
onKeyDown = { this._onEscKey }>
|
||||
<EndConferenceButton />
|
||||
<LeaveConferenceButton />
|
||||
</ContextMenu>
|
||||
</HangupMenuButton>
|
||||
: <HangupButton
|
||||
buttonKey = 'hangup'
|
||||
customClass = 'hangup-button'
|
||||
key = 'hangup-button'
|
||||
notifyMode = { this._getButtonNotifyMode('hangup') }
|
||||
visible = { isToolbarButtonEnabled('hangup', _toolbarButtons) } />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1407,6 +1483,7 @@ class Toolbox extends Component<Props> {
|
|||
*/
|
||||
function _mapStateToProps(state: any, ownProps: Partial<Props>) {
|
||||
const { conference } = state['features/base/conference'];
|
||||
const endConferenceSupported = conference?.isEndConferenceSupported();
|
||||
const {
|
||||
buttonsWithNotifyClick,
|
||||
callStatsID,
|
||||
|
@ -1416,6 +1493,7 @@ function _mapStateToProps(state: any, ownProps: Partial<Props>) {
|
|||
} = state['features/base/config'];
|
||||
const {
|
||||
fullScreen,
|
||||
hangupMenuVisible,
|
||||
overflowMenuVisible,
|
||||
overflowDrawer
|
||||
} = state['features/toolbox'];
|
||||
|
@ -1434,6 +1512,7 @@ function _mapStateToProps(state: any, ownProps: Partial<Props>) {
|
|||
_desktopSharingButtonDisabled: isDesktopShareButtonDisabled(state),
|
||||
_dialog: Boolean(state['features/base/dialog'].component),
|
||||
_disabled: Boolean(iAmRecorder || iAmSipGateway),
|
||||
_endConferenceSupported: Boolean(endConferenceSupported),
|
||||
_feedbackConfigured: Boolean(callStatsID),
|
||||
_fullScreen: fullScreen,
|
||||
_gifsEnabled: isGifEnabled(state),
|
||||
|
@ -1442,6 +1521,7 @@ function _mapStateToProps(state: any, ownProps: Partial<Props>) {
|
|||
_isMobile: isMobileBrowser(),
|
||||
_isVpaasMeeting: isVpaasMeeting(state),
|
||||
_hasSalesforce: isSalesforceEnabled(state),
|
||||
_hangupMenuVisible: hangupMenuVisible,
|
||||
_localParticipantID: localParticipant?.id,
|
||||
_localVideo: localVideo,
|
||||
_multiStreamModeEnabled: getMultipleVideoSendingSupportFeatureFlag(state),
|
||||
|
|
|
@ -5,6 +5,7 @@ import { ReducerRegistry, set } from '../base/redux';
|
|||
import {
|
||||
CLEAR_TOOLBOX_TIMEOUT,
|
||||
FULL_SCREEN_CHANGED,
|
||||
SET_HANGUP_MENU_VISIBLE,
|
||||
SET_OVERFLOW_DRAWER,
|
||||
SET_OVERFLOW_MENU_VISIBLE,
|
||||
SET_TOOLBAR_HOVERED,
|
||||
|
@ -26,6 +27,13 @@ const INITIAL_STATE = {
|
|||
*/
|
||||
enabled: true,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether the hangup menu is visible.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
hangupMenuVisible: false,
|
||||
|
||||
/**
|
||||
* The indicator which determines whether a Toolbar in the Toolbox is
|
||||
* hovered.
|
||||
|
@ -81,6 +89,12 @@ ReducerRegistry.register(
|
|||
fullScreen: action.fullScreen
|
||||
};
|
||||
|
||||
case SET_HANGUP_MENU_VISIBLE:
|
||||
return {
|
||||
...state,
|
||||
hangupMenuVisible: action.visible
|
||||
};
|
||||
|
||||
case SET_OVERFLOW_DRAWER:
|
||||
return {
|
||||
...state,
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
-- This module is added under the main virtual host domain
|
||||
--
|
||||
-- VirtualHost "jitmeet.example.com"
|
||||
-- modules_enabled = {
|
||||
-- "end_conference"
|
||||
-- }
|
||||
-- end_conference_component = "endconference.jitmeet.example.com"
|
||||
--
|
||||
-- Component "endconference.jitmeet.example.com" "end_conference"
|
||||
-- muc_component = muc.jitmeet.example.com
|
||||
--
|
||||
local get_room_by_name_and_subdomain = module:require 'util'.get_room_by_name_and_subdomain;
|
||||
|
||||
local END_CONFERENCE_REASON = 'The meeting has been terminated';
|
||||
|
||||
local end_conference_component = module:get_option_string('end_conference_component', 'endconference.'..module.host);
|
||||
if end_conference_component == nil then
|
||||
log('error', 'No end_conference_component specified.');
|
||||
return;
|
||||
end
|
||||
|
||||
-- Advertise end conference so client can pick up the address and use it
|
||||
module:add_identity('component', 'end_conference', end_conference_component);
|
||||
|
||||
module:depends("jitsi_session");
|
||||
|
||||
local muc_component_host = module:get_option_string('muc_component');
|
||||
if muc_component_host == nil then
|
||||
log('error', 'No muc_component specified. No muc to operate on!');
|
||||
return;
|
||||
end
|
||||
|
||||
module:log('info', 'Starting end_conference for %s', muc_component_host);
|
||||
|
||||
-- receives messages from clients to the component to end a conference
|
||||
function on_message(event)
|
||||
local session = event.origin;
|
||||
|
||||
-- Check the type of the incoming stanza to avoid loops:
|
||||
if event.stanza.attr.type == 'error' then
|
||||
return; -- We do not want to reply to these, so leave.
|
||||
end
|
||||
|
||||
if not session or not session.jitsi_web_query_room then
|
||||
return false;
|
||||
end
|
||||
|
||||
local moderation_command = event.stanza:get_child('end_conference');
|
||||
|
||||
if moderation_command then
|
||||
-- get room name with tenant and find room
|
||||
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
|
||||
|
||||
if not room then
|
||||
module:log('warn', 'No room found found for %s/%s',
|
||||
session.jitsi_web_query_prefix, session.jitsi_web_query_room);
|
||||
return false;
|
||||
end
|
||||
|
||||
-- check that the participant requesting is a moderator and is an occupant in the room
|
||||
local from = event.stanza.attr.from;
|
||||
local occupant = room:get_occupant_by_real_jid(from);
|
||||
if not occupant then
|
||||
log('warn', 'No occupant %s found for %s', from, room.jid);
|
||||
return false;
|
||||
end
|
||||
if occupant.role ~= 'moderator' then
|
||||
log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid);
|
||||
return false;
|
||||
end
|
||||
|
||||
-- destroy the room
|
||||
room:destroy(nil, END_CONFERENCE_REASON);
|
||||
log('info', 'Room %s destroyed by occupant %s', room.jid, from);
|
||||
return true;
|
||||
end
|
||||
|
||||
-- return error
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- we will receive messages from the clients
|
||||
module:hook('message/host', on_message);
|
|
@ -213,7 +213,7 @@ end
|
|||
function destroy_breakout_room(room_jid, message)
|
||||
local main_room, main_room_jid = get_main_room(room_jid);
|
||||
|
||||
if room_jid == main_room_jid or not main_room then
|
||||
if room_jid == main_room_jid then
|
||||
return;
|
||||
end
|
||||
|
||||
|
@ -221,7 +221,7 @@ function destroy_breakout_room(room_jid, message)
|
|||
|
||||
if breakout_room then
|
||||
message = message or 'Breakout room removed.';
|
||||
breakout_room:destroy(main_room_jid, message);
|
||||
breakout_room:destroy(main_room and main_room_jid or nil, message);
|
||||
end
|
||||
if main_room then
|
||||
if main_room._data.breakout_rooms then
|
||||
|
@ -418,10 +418,8 @@ function on_main_room_destroyed(event)
|
|||
return;
|
||||
end
|
||||
|
||||
local message = 'Conference ended.';
|
||||
|
||||
for breakout_room_jid in pairs(main_room._data.breakout_rooms or {}) do
|
||||
destroy_breakout_room(breakout_room_jid, message)
|
||||
destroy_breakout_room(breakout_room_jid, event.reason)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue