feat: (moderate-reaction-sounds) enable moderator to mute reaction sounds
This commit is contained in:
parent
646fdef6bb
commit
a077043f1b
|
@ -65,6 +65,11 @@
|
|||
text-align: left;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.moderator-settings-wrapper {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.profile-edit-field {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
|
|
@ -633,6 +633,7 @@
|
|||
"moderationToggleDescription": "by {{participantDisplayName}}",
|
||||
"raiseHandAction": "Raise hand",
|
||||
"reactionSounds": "Disable sounds",
|
||||
"reactionSoundsForAll": "Disable sounds for all",
|
||||
"groupTitle": "Notifications",
|
||||
"videoUnmuteBlockedTitle": "Camera unmute blocked!",
|
||||
"videoUnmuteBlockedDescription": "Camera unmute operation has been temporarily blocked because of system limits."
|
||||
|
@ -660,7 +661,8 @@
|
|||
"stopEveryonesVideo": "Stop everyone's video",
|
||||
"stopVideo": "Stop video",
|
||||
"unblockEveryoneMicCamera": "Unblock everyone's mic and camera",
|
||||
"videoModeration": "Start their video"
|
||||
"videoModeration": "Start their video",
|
||||
"moreModerationControls": "More moderation controls"
|
||||
},
|
||||
"search": "Search participants"
|
||||
},
|
||||
|
@ -855,6 +857,7 @@
|
|||
"sounds": "Sounds",
|
||||
"speakers": "Speakers",
|
||||
"startAudioMuted": "Everyone starts muted",
|
||||
"startReactionsMuted": "Mute reaction sounds for everyone",
|
||||
"startVideoMuted": "Everyone starts hidden",
|
||||
"talkWhileMuted": "Talk while muted",
|
||||
"title": "Settings"
|
||||
|
|
|
@ -98,11 +98,11 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
|
|||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
customActionNameKey: 'notify.raiseHandAction',
|
||||
customActionHandler: () => batch(() => {
|
||||
customActionNameKey: [ 'notify.raiseHandAction' ],
|
||||
customActionHandler: [ () => batch(() => {
|
||||
dispatch(raiseHand(true));
|
||||
dispatch(hideNotification(uid));
|
||||
}),
|
||||
}) ],
|
||||
descriptionKey,
|
||||
sticky: true,
|
||||
titleKey,
|
||||
|
@ -221,8 +221,8 @@ StateListenerRegistry.register(
|
|||
dispatch(showNotification({
|
||||
titleKey: 'notify.hostAskedUnmute',
|
||||
sticky: true,
|
||||
customActionNameKey: 'notify.unmute',
|
||||
customActionHandler: () => dispatch(muteLocal(false, MEDIA_TYPE.AUDIO))
|
||||
customActionNameKey: [ 'notify.unmute' ],
|
||||
customActionHandler: [ () => dispatch(muteLocal(false, MEDIA_TYPE.AUDIO)) ]
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.STICKY));
|
||||
dispatch(playSound(ASKED_TO_UNMUTE_SOUND_ID));
|
||||
}
|
||||
|
|
|
@ -172,6 +172,17 @@ export const SEND_TONES = 'SEND_TONES';
|
|||
*/
|
||||
export const SET_FOLLOW_ME = 'SET_FOLLOW_ME';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which updates the current known status of the
|
||||
* Mute Reactions Sound feature.
|
||||
*
|
||||
* {
|
||||
* type: SET_START_REACTIONS_MUTED,
|
||||
* enabled: boolean
|
||||
* }
|
||||
*/
|
||||
export const SET_START_REACTIONS_MUTED = 'SET_START_REACTIONS_MUTED';
|
||||
|
||||
/**
|
||||
* The type of (redux) action which sets the password to join or lock a specific
|
||||
* {@code JitsiConference}.
|
||||
|
|
|
@ -53,7 +53,8 @@ import {
|
|||
SET_PASSWORD_FAILED,
|
||||
SET_ROOM,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_START_MUTED_POLICY
|
||||
SET_START_MUTED_POLICY,
|
||||
SET_START_REACTIONS_MUTED
|
||||
} from './actionTypes';
|
||||
import {
|
||||
AVATAR_URL_COMMAND,
|
||||
|
@ -669,6 +670,22 @@ export function setFollowMe(enabled: boolean) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables the Mute reaction sounds feature.
|
||||
*
|
||||
* @param {boolean} muted - Whether or not reaction sounds should be muted for all participants.
|
||||
* @returns {{
|
||||
* type: SET_START_REACTIONS_MUTED,
|
||||
* muted: boolean
|
||||
* }}
|
||||
*/
|
||||
export function setStartReactionsMuted(muted: boolean) {
|
||||
return {
|
||||
type: SET_START_REACTIONS_MUTED,
|
||||
muted
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the password to join or lock a specific JitsiConference.
|
||||
*
|
||||
|
|
|
@ -20,7 +20,8 @@ import {
|
|||
SET_PASSWORD,
|
||||
SET_PENDING_SUBJECT_CHANGE,
|
||||
SET_ROOM,
|
||||
SET_START_MUTED_POLICY
|
||||
SET_START_MUTED_POLICY,
|
||||
SET_START_REACTIONS_MUTED
|
||||
} from './actionTypes';
|
||||
import { isRoomValid } from './functions';
|
||||
|
||||
|
@ -77,6 +78,9 @@ ReducerRegistry.register(
|
|||
case SET_FOLLOW_ME:
|
||||
return set(state, 'followMeEnabled', action.enabled);
|
||||
|
||||
case SET_START_REACTIONS_MUTED:
|
||||
return set(state, 'startReactionsMuted', action.muted);
|
||||
|
||||
case SET_LOCATION_URL:
|
||||
return set(state, 'room', undefined);
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import { processExternalDeviceRequest } from '../../device-selection';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, showNotification, showWarningNotification } from '../../notifications';
|
||||
import {
|
||||
NOTIFICATION_TIMEOUT_TYPE,
|
||||
showNotification,
|
||||
showWarningNotification
|
||||
} from '../../notifications';
|
||||
import { replaceAudioTrackById, replaceVideoTrackById, setDeviceStatusWarning } from '../../prejoin/actions';
|
||||
import { isPrejoinPageVisible } from '../../prejoin/functions';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
|
||||
|
@ -294,8 +298,8 @@ function _checkAndNotifyForNewDevice(store, newDevices, oldDevices) {
|
|||
dispatch(showNotification({
|
||||
description,
|
||||
titleKey,
|
||||
customActionNameKey: 'notify.newDeviceAction',
|
||||
customActionHandler: _useDevice.bind(undefined, store, devicesArray)
|
||||
customActionNameKey: [ 'notify.newDeviceAction' ],
|
||||
customActionHandler: [ _useDevice.bind(undefined, store, devicesArray) ]
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -555,8 +555,8 @@ function _raiseHandUpdated({ dispatch, getState }, conference, participantId, ne
|
|||
}
|
||||
|
||||
const action = shouldDisplayAllowAction ? {
|
||||
customActionNameKey: 'notify.allowAction',
|
||||
customActionHandler: () => dispatch(approveParticipant(participantId))
|
||||
customActionNameKey: [ 'notify.allowAction' ],
|
||||
customActionHandler: [ () => dispatch(approveParticipant(participantId)) ]
|
||||
} : {};
|
||||
|
||||
if (raisedHandTimestamp) {
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
* serverURL: string,
|
||||
* startAudioOnly: boolean,
|
||||
* startWithAudioMuted: boolean,
|
||||
* startWithVideoMuted: boolean
|
||||
* startWithVideoMuted: boolean,
|
||||
* startWithReactionsMuted: boolean
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
|
|
|
@ -15,9 +15,11 @@ import { SETTINGS_UPDATED } from './actionTypes';
|
|||
* localFlipX: boolean,
|
||||
* micDeviceId: string,
|
||||
* serverURL: string,
|
||||
* soundsReactions: boolean,
|
||||
* startAudioOnly: boolean,
|
||||
* startWithAudioMuted: boolean,
|
||||
* startWithVideoMuted: boolean
|
||||
* startWithVideoMuted: boolean,
|
||||
* startWithReactionsMuted: boolean
|
||||
* }
|
||||
* }}
|
||||
*/
|
||||
|
|
|
@ -94,8 +94,8 @@ async function _handleNoAudioSignalNotification({ dispatch, getState }, action)
|
|||
// at the point of the implementation the showNotification function only supports doing that for
|
||||
// the description.
|
||||
// TODO Add support for arguments to showNotification title and customAction strings.
|
||||
customActionNameKey = `Switch to ${formatDeviceLabel(activeDevice.deviceLabel)}`;
|
||||
customActionHandler = () => {
|
||||
customActionNameKey = [ `Switch to ${formatDeviceLabel(activeDevice.deviceLabel)}` ];
|
||||
customActionHandler = [ () => {
|
||||
// Select device callback
|
||||
dispatch(
|
||||
updateSettings({
|
||||
|
@ -105,7 +105,7 @@ async function _handleNoAudioSignalNotification({ dispatch, getState }, action)
|
|||
);
|
||||
|
||||
dispatch(setAudioInputDevice(activeDevice.deviceId));
|
||||
};
|
||||
} ];
|
||||
}
|
||||
|
||||
const notification = await dispatch(showNotification({
|
||||
|
|
|
@ -20,12 +20,12 @@ export type Props = {
|
|||
/**
|
||||
* Callback invoked when the custom button is clicked.
|
||||
*/
|
||||
customActionHandler: Function,
|
||||
customActionHandler: Function[],
|
||||
|
||||
/**
|
||||
* The text to display as button in the notification for the custom action.
|
||||
*/
|
||||
customActionNameKey: string,
|
||||
customActionNameKey: string[],
|
||||
|
||||
/**
|
||||
* The text to display in the body of the notification. If not passed
|
||||
|
|
|
@ -128,17 +128,17 @@ class Notification extends AbstractNotification<Props> {
|
|||
];
|
||||
|
||||
default:
|
||||
if (this.props.customActionNameKey && this.props.customActionHandler) {
|
||||
return [
|
||||
{
|
||||
content: this.props.t(this.props.customActionNameKey),
|
||||
if (this.props.customActionNameKey?.length && this.props.customActionHandler?.length) {
|
||||
return this.props.customActionNameKey.map((customAction: string, customActionIndex: number) => {
|
||||
return {
|
||||
content: this.props.t(customAction),
|
||||
onClick: () => {
|
||||
if (this.props.customActionHandler()) {
|
||||
if (this.props.customActionHandler[customActionIndex]()) {
|
||||
this._onDismissed();
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
|
|
|
@ -17,12 +17,17 @@ import {
|
|||
} from '../../../av-moderation/functions';
|
||||
import { ContextMenu, ContextMenuItemGroup } from '../../../base/components';
|
||||
import { openDialog } from '../../../base/dialog';
|
||||
import { IconCheck, IconVideoOff } from '../../../base/icons';
|
||||
import {
|
||||
IconCheck,
|
||||
IconHorizontalPoints,
|
||||
IconVideoOff
|
||||
} from '../../../base/icons';
|
||||
import { MEDIA_TYPE } from '../../../base/media';
|
||||
import {
|
||||
getParticipantCount,
|
||||
isEveryoneModerator
|
||||
} from '../../../base/participants';
|
||||
import { openSettingsDialog, SETTINGS_TABS } from '../../../settings';
|
||||
import { MuteEveryonesVideoDialog } from '../../../video-menu/components';
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
|
@ -95,6 +100,8 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: Props
|
|||
const muteAllVideo = useCallback(
|
||||
() => dispatch(openDialog(MuteEveryonesVideoDialog)), [ dispatch ]);
|
||||
|
||||
const openModeratorSettings = () => dispatch(openSettingsDialog(SETTINGS_TABS.MODERATOR));
|
||||
|
||||
const actions = [
|
||||
{
|
||||
accessibilityLabel: t('participantsPane.actions.audioModeration'),
|
||||
|
@ -139,6 +146,14 @@ export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: Props
|
|||
</div>
|
||||
</ContextMenuItemGroup>
|
||||
)}
|
||||
<ContextMenuItemGroup
|
||||
actions = { [ {
|
||||
accessibilityLabel: t('participantsPane.actions.moreModerationControls'),
|
||||
id: 'participants-pane-open-moderation-control-settings',
|
||||
icon: IconHorizontalPoints,
|
||||
onClick: openModeratorSettings,
|
||||
text: t('participantsPane.actions.moreModerationControls')
|
||||
} ] } />
|
||||
</ContextMenu>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,6 +14,13 @@ import {
|
|||
*/
|
||||
export const ENDPOINT_REACTION_NAME = 'endpoint-reaction';
|
||||
|
||||
/**
|
||||
* The (name of the) command which transports the state (represented by
|
||||
* {State} for the local state at the time of this writing) of a {MuteReactions}
|
||||
* (instance) between moderator and participants.
|
||||
*/
|
||||
export const MUTE_REACTIONS_COMMAND = 'mute-reactions';
|
||||
|
||||
/**
|
||||
* The prefix for all reaction sound IDs. Also the ID used in config to disable reaction sounds.
|
||||
*/
|
||||
|
|
|
@ -4,9 +4,15 @@ import { batch } from 'react-redux';
|
|||
|
||||
import { createReactionSoundsDisabledEvent, sendAnalytics } from '../analytics';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
|
||||
import { getParticipantCount } from '../base/participants';
|
||||
import { CONFERENCE_WILL_JOIN, setStartReactionsMuted } from '../base/conference';
|
||||
import {
|
||||
getParticipantById,
|
||||
getParticipantCount,
|
||||
isLocalParticipantModerator
|
||||
} from '../base/participants';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { SETTINGS_UPDATED, updateSettings } from '../base/settings';
|
||||
import { SETTINGS_UPDATED } from '../base/settings/actionTypes';
|
||||
import { updateSettings } from '../base/settings/actions';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds';
|
||||
import { getDisabledSounds } from '../base/sounds/functions.any';
|
||||
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
|
||||
|
@ -31,7 +37,8 @@ import {
|
|||
RAISE_HAND_SOUND_ID,
|
||||
REACTIONS,
|
||||
REACTION_SOUND,
|
||||
SOUNDS_THRESHOLDS
|
||||
SOUNDS_THRESHOLDS,
|
||||
MUTE_REACTIONS_COMMAND
|
||||
} from './constants';
|
||||
import {
|
||||
getReactionMessageFromBuffer,
|
||||
|
@ -39,8 +46,11 @@ import {
|
|||
getReactionsWithId,
|
||||
sendReactionsWebhook
|
||||
} from './functions.any';
|
||||
import logger from './logger';
|
||||
import { RAISE_HAND_SOUND_FILE } from './sounds';
|
||||
|
||||
import './subscriber';
|
||||
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
|
@ -95,7 +105,15 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
|
||||
break;
|
||||
}
|
||||
case CONFERENCE_WILL_JOIN: {
|
||||
const { conference } = action;
|
||||
|
||||
conference.addCommandListener(
|
||||
MUTE_REACTIONS_COMMAND, ({ attributes }, id) => {
|
||||
_onMuteReactionsCommand(attributes, id, store);
|
||||
});
|
||||
break;
|
||||
}
|
||||
case FLUSH_REACTION_BUFFER: {
|
||||
const state = getState();
|
||||
const { buffer } = state['features/reactions'];
|
||||
|
@ -163,12 +181,26 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
}
|
||||
|
||||
case SHOW_SOUNDS_NOTIFICATION: {
|
||||
const state = getState();
|
||||
const isModerator = isLocalParticipantModerator(state);
|
||||
|
||||
const customActions = [ 'notify.reactionSounds' ];
|
||||
const customFunctions = [ () => dispatch(updateSettings({
|
||||
soundsReactions: false
|
||||
})) ];
|
||||
|
||||
if (isModerator) {
|
||||
customActions.push('notify.reactionSoundsForAll');
|
||||
customFunctions.push(() => batch(() => {
|
||||
dispatch(setStartReactionsMuted(true));
|
||||
dispatch(updateSettings({ soundsReactions: false }));
|
||||
}));
|
||||
}
|
||||
|
||||
dispatch(showNotification({
|
||||
titleKey: 'toolbar.disableReactionSounds',
|
||||
customActionNameKey: 'notify.reactionSounds',
|
||||
customActionHandler: () => dispatch(updateSettings({
|
||||
soundsReactions: false
|
||||
}))
|
||||
customActionNameKey: customActions,
|
||||
customActionHandler: customFunctions
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
|
||||
break;
|
||||
}
|
||||
|
@ -176,3 +208,51 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
|
||||
return next(action);
|
||||
});
|
||||
|
||||
/**
|
||||
* Notifies this instance about a "Mute Reaction Sounds" command received by the Jitsi
|
||||
* conference.
|
||||
*
|
||||
* @param {Object} attributes - The attributes carried by the command.
|
||||
* @param {string} id - The identifier of the participant who issuing the
|
||||
* command. A notable idiosyncrasy to be mindful of here is that the command
|
||||
* may be issued by the local participant.
|
||||
* @param {Object} store - The redux store. Used to calculate and dispatch
|
||||
* updates.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _onMuteReactionsCommand(attributes = {}, id, store) {
|
||||
const state = store.getState();
|
||||
|
||||
// We require to know who issued the command because (1) only a
|
||||
// moderator is allowed to send commands and (2) a command MUST be
|
||||
// issued by a defined commander.
|
||||
if (typeof id === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const participantSendingCommand = getParticipantById(state, id);
|
||||
|
||||
// The Command(s) API will send us our own commands and we don't want
|
||||
// to act upon them.
|
||||
if (participantSendingCommand.local) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (participantSendingCommand.role !== 'moderator') {
|
||||
logger.warn('Received mute-reactions command not from moderator');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const oldState = Boolean(state['features/base/conference'].startReactionsMuted);
|
||||
const newState = attributes.startReactionsMuted === 'true';
|
||||
|
||||
if (oldState !== newState) {
|
||||
batch(() => {
|
||||
store.dispatch(setStartReactionsMuted(newState));
|
||||
store.dispatch(updateSettings({ soundsReactions: !newState }));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// @flow
|
||||
|
||||
import { getCurrentConference } from '../base/conference';
|
||||
import { isLocalParticipantModerator } from '../base/participants';
|
||||
import { StateListenerRegistry } from '../base/redux';
|
||||
|
||||
import { MUTE_REACTIONS_COMMAND } from './constants';
|
||||
|
||||
/**
|
||||
* Subscribes to changes to the Mute Reaction Sounds setting for the local participant to
|
||||
* notify remote participants of current user interface status.
|
||||
* Changing newSelectedValue param to off, when feature is turned of so we can
|
||||
* notify all listeners.
|
||||
*/
|
||||
StateListenerRegistry.register(
|
||||
/* selector */ state => state['features/base/conference'].startReactionsMuted,
|
||||
/* listener */ (newSelectedValue, store) => _sendMuteReactionsCommand(newSelectedValue || false, store));
|
||||
|
||||
|
||||
/**
|
||||
* Sends the mute-reactions command, when a local property change occurs.
|
||||
*
|
||||
* @param {*} newSelectedValue - The changed selected value from the selector.
|
||||
* @param {Object} store - The redux store.
|
||||
* @private
|
||||
* @returns {void}
|
||||
*/
|
||||
function _sendMuteReactionsCommand(newSelectedValue, store) {
|
||||
const state = store.getState();
|
||||
const conference = getCurrentConference(state);
|
||||
|
||||
if (!conference) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only a moderator is allowed to send commands.
|
||||
if (!isLocalParticipantModerator(state)) {
|
||||
return;
|
||||
}
|
||||
|
||||
conference.sendCommand(
|
||||
MUTE_REACTIONS_COMMAND,
|
||||
{ attributes: { startReactionsMuted: Boolean(newSelectedValue) } }
|
||||
);
|
||||
}
|
|
@ -170,7 +170,6 @@ export function showStartedRecordingNotification(
|
|||
const initiatorId = getResourceId(initiator);
|
||||
const participantName = getParticipantDisplayName(state, initiatorId);
|
||||
let dialogProps = {
|
||||
customActionNameKey: undefined,
|
||||
descriptionKey: participantName ? 'liveStreaming.onBy' : 'liveStreaming.on',
|
||||
descriptionArguments: { name: participantName },
|
||||
isDismissAllowed: true,
|
||||
|
@ -206,8 +205,8 @@ export function showStartedRecordingNotification(
|
|||
}
|
||||
|
||||
// add the option to copy recording link
|
||||
dialogProps.customActionNameKey = 'recording.copyLink';
|
||||
dialogProps.customActionHandler = () => copyText(link);
|
||||
dialogProps.customActionNameKey = [ 'recording.copyLink' ];
|
||||
dialogProps.customActionHandler = [ () => copyText(link) ];
|
||||
dialogProps.titleKey = 'recording.on';
|
||||
dialogProps.descriptionKey = 'recording.linkGenerated';
|
||||
dialogProps.isDismissAllowed = false;
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
// @flow
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { setFollowMe, setStartMutedPolicy } from '../base/conference';
|
||||
import {
|
||||
setFollowMe,
|
||||
setStartMutedPolicy,
|
||||
setStartReactionsMuted
|
||||
} from '../base/conference';
|
||||
import { openDialog } from '../base/dialog';
|
||||
import { i18next } from '../base/i18n';
|
||||
import { updateSettings } from '../base/settings';
|
||||
|
@ -12,7 +17,12 @@ import {
|
|||
SET_VIDEO_SETTINGS_VISIBILITY
|
||||
} from './actionTypes';
|
||||
import { LogoutDialog, SettingsDialog } from './components';
|
||||
import { getMoreTabProps, getProfileTabProps, getSoundsTabProps } from './functions';
|
||||
import {
|
||||
getModeratorTabProps,
|
||||
getMoreTabProps,
|
||||
getProfileTabProps,
|
||||
getSoundsTabProps
|
||||
} from './functions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
|
@ -74,10 +84,6 @@ export function submitMoreTab(newState: Object): Function {
|
|||
return (dispatch, getState) => {
|
||||
const currentState = getMoreTabProps(getState());
|
||||
|
||||
if (newState.followMeEnabled !== currentState.followMeEnabled) {
|
||||
dispatch(setFollowMe(newState.followMeEnabled));
|
||||
}
|
||||
|
||||
const showPrejoinPage = newState.showPrejoinPage;
|
||||
|
||||
if (showPrejoinPage !== currentState.showPrejoinPage) {
|
||||
|
@ -91,12 +97,6 @@ export function submitMoreTab(newState: Object): Function {
|
|||
}));
|
||||
}
|
||||
|
||||
if (newState.startAudioMuted !== currentState.startAudioMuted
|
||||
|| newState.startVideoMuted !== currentState.startVideoMuted) {
|
||||
dispatch(setStartMutedPolicy(
|
||||
newState.startAudioMuted, newState.startVideoMuted));
|
||||
}
|
||||
|
||||
if (newState.currentLanguage !== currentState.currentLanguage) {
|
||||
i18next.changeLanguage(newState.currentLanguage);
|
||||
}
|
||||
|
@ -109,6 +109,35 @@ export function submitMoreTab(newState: Object): Function {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the settings from the "Moderator" tab of the settings dialog.
|
||||
*
|
||||
* @param {Object} newState - The new settings.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function submitModeratorTab(newState: Object): Function {
|
||||
return (dispatch, getState) => {
|
||||
const currentState = getModeratorTabProps(getState());
|
||||
|
||||
if (newState.followMeEnabled !== currentState.followMeEnabled) {
|
||||
dispatch(setFollowMe(newState.followMeEnabled));
|
||||
}
|
||||
|
||||
if (newState.startReactionsMuted !== currentState.startReactionsMuted) {
|
||||
batch(() => {
|
||||
dispatch(setStartReactionsMuted(newState.startReactionsMuted));
|
||||
dispatch(updateSettings({ soundsReactions: !newState.startReactionsMuted }));
|
||||
});
|
||||
}
|
||||
|
||||
if (newState.startAudioMuted !== currentState.startAudioMuted
|
||||
|| newState.startVideoMuted !== currentState.startVideoMuted) {
|
||||
dispatch(setStartMutedPolicy(
|
||||
newState.startAudioMuted, newState.startVideoMuted));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Submits the settings from the "Profile" tab of the settings dialog.
|
||||
*
|
||||
|
@ -138,6 +167,7 @@ export function submitProfileTab(newState: Object): Function {
|
|||
export function submitSoundsTab(newState: Object): Function {
|
||||
return (dispatch, getState) => {
|
||||
const currentState = getSoundsTabProps(getState());
|
||||
const shouldNotUpdateReactionSounds = getModeratorTabProps(getState()).startReactionsMuted;
|
||||
const shouldUpdate = (newState.soundsIncomingMessage !== currentState.soundsIncomingMessage)
|
||||
|| (newState.soundsParticipantJoined !== currentState.soundsParticipantJoined)
|
||||
|| (newState.soundsParticipantLeft !== currentState.soundsParticipantLeft)
|
||||
|
@ -145,13 +175,18 @@ export function submitSoundsTab(newState: Object): Function {
|
|||
|| (newState.soundsReactions !== currentState.soundsReactions);
|
||||
|
||||
if (shouldUpdate) {
|
||||
dispatch(updateSettings({
|
||||
const settingsToUpdate = {
|
||||
soundsIncomingMessage: newState.soundsIncomingMessage,
|
||||
soundsParticipantJoined: newState.soundsParticipantJoined,
|
||||
soundsParticipantLeft: newState.soundsParticipantLeft,
|
||||
soundsTalkWhileMuted: newState.soundsTalkWhileMuted,
|
||||
soundsReactions: newState.soundsReactions
|
||||
}));
|
||||
};
|
||||
|
||||
if (shouldNotUpdateReactionSounds) {
|
||||
delete settingsToUpdate.soundsReactions;
|
||||
}
|
||||
dispatch(updateSettings(settingsToUpdate));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -67,6 +67,8 @@ export class AbstractSettingsView<P: Props, S: *> extends Component<P, S> {
|
|||
= this._onStartAudioMutedChange.bind(this);
|
||||
this._onStartVideoMutedChange
|
||||
= this._onStartVideoMutedChange.bind(this);
|
||||
this._onStartReactionsMutedChange
|
||||
= this._onStartReactionsMutedChange.bind(this);
|
||||
}
|
||||
|
||||
_onChangeDisplayName: (string) => void;
|
||||
|
@ -146,6 +148,22 @@ export class AbstractSettingsView<P: Props, S: *> extends Component<P, S> {
|
|||
});
|
||||
}
|
||||
|
||||
_onStartReactionsMutedChange: (boolean) => void;
|
||||
|
||||
/**
|
||||
* Handles the start reactions muted change event.
|
||||
*
|
||||
* @param {boolean} newValue - The new value for the start reactions muted
|
||||
* option.
|
||||
* @protected
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStartReactionsMutedChange(newValue) {
|
||||
this._updateSettings({
|
||||
startWithReactionsMuted: newValue
|
||||
});
|
||||
}
|
||||
|
||||
_updateSettings: (Object) => void;
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
// @flow
|
||||
import { Checkbox } from '@atlaskit/checkbox';
|
||||
import React from 'react';
|
||||
|
||||
import { AbstractDialogTab } from '../../../base/dialog';
|
||||
import type { Props as AbstractDialogTabProps } from '../../../base/dialog';
|
||||
import { translate } from '../../../base/i18n';
|
||||
|
||||
/**
|
||||
* The type of the React {@code Component} props of {@link MoreTab}.
|
||||
*/
|
||||
export type Props = {
|
||||
...$Exact<AbstractDialogTabProps>,
|
||||
|
||||
/**
|
||||
* Whether or not follow me is currently active (enabled by some other participant).
|
||||
*/
|
||||
followMeActive: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user has selected the Follow Me feature to be enabled.
|
||||
*/
|
||||
followMeEnabled: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user has selected the Start Audio Muted feature to be
|
||||
* enabled.
|
||||
*/
|
||||
startAudioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user has selected the Start Video Muted feature to be
|
||||
* enabled.
|
||||
*/
|
||||
startVideoMuted: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user has selected the Start Reactions Muted feature to be
|
||||
* enabled.
|
||||
*/
|
||||
startReactionsMuted: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
t: Function
|
||||
};
|
||||
|
||||
/**
|
||||
* React {@code Component} for modifying language and moderator settings.
|
||||
*
|
||||
* @augments Component
|
||||
*/
|
||||
class ModeratorTab extends AbstractDialogTab<Props> {
|
||||
/**
|
||||
* Initializes a new {@code MoreTab} instance.
|
||||
*
|
||||
* @param {Object} props - The read-only properties with which the new
|
||||
* instance is to be initialized.
|
||||
*/
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
// Bind event handler so it is only bound once for every instance.
|
||||
this._onStartAudioMutedChanged = this._onStartAudioMutedChanged.bind(this);
|
||||
this._onStartVideoMutedChanged = this._onStartVideoMutedChanged.bind(this);
|
||||
this._onStartReactionsMutedChanged = this._onStartReactionsMutedChanged.bind(this);
|
||||
this._onFollowMeEnabledChanged = this._onFollowMeEnabledChanged.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements React's {@link Component#render()}.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
return <div className = 'moderator-tab box'>{ this._renderModeratorSettings() }</div>;
|
||||
}
|
||||
|
||||
_onStartAudioMutedChanged: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to select if conferences should start
|
||||
* with audio muted.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStartAudioMutedChanged({ target: { checked } }) {
|
||||
super._onChange({ startAudioMuted: checked });
|
||||
}
|
||||
|
||||
_onStartVideoMutedChanged: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to select if conferences should start
|
||||
* with video disabled.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStartVideoMutedChanged({ target: { checked } }) {
|
||||
super._onChange({ startVideoMuted: checked });
|
||||
}
|
||||
|
||||
_onStartReactionsMutedChanged: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to select if conferences should start
|
||||
* with reactions muted.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStartReactionsMutedChanged({ target: { checked } }) {
|
||||
super._onChange({ startReactionsMuted: checked });
|
||||
}
|
||||
|
||||
_onFollowMeEnabledChanged: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to select if follow-me mode
|
||||
* should be activated.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onFollowMeEnabledChanged({ target: { checked } }) {
|
||||
super._onChange({ followMeEnabled: checked });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the React Element for modifying conference-wide settings.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderModeratorSettings() {
|
||||
const {
|
||||
followMeActive,
|
||||
followMeEnabled,
|
||||
startAudioMuted,
|
||||
startVideoMuted,
|
||||
startReactionsMuted,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'settings-sub-pane-element'
|
||||
key = 'moderator'>
|
||||
<div className = 'moderator-settings-wrapper'>
|
||||
<Checkbox
|
||||
isChecked = { startAudioMuted }
|
||||
label = { t('settings.startAudioMuted') }
|
||||
name = 'start-audio-muted'
|
||||
onChange = { this._onStartAudioMutedChanged } />
|
||||
<Checkbox
|
||||
isChecked = { startVideoMuted }
|
||||
label = { t('settings.startVideoMuted') }
|
||||
name = 'start-video-muted'
|
||||
onChange = { this._onStartVideoMutedChanged } />
|
||||
<Checkbox
|
||||
isChecked = { followMeEnabled && !followMeActive }
|
||||
isDisabled = { followMeActive }
|
||||
label = { t('settings.followMe') }
|
||||
name = 'follow-me'
|
||||
onChange = { this._onFollowMeEnabledChanged } />
|
||||
<Checkbox
|
||||
isChecked = { startReactionsMuted }
|
||||
label = { t('settings.startReactionsMuted') }
|
||||
name = 'start-reactions-muted'
|
||||
onChange = { this._onStartReactionsMutedChanged } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(ModeratorTab);
|
|
@ -40,11 +40,6 @@ export type Props = {
|
|||
*/
|
||||
followMeActive: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user has selected the Follow Me feature to be enabled.
|
||||
*/
|
||||
followMeEnabled: boolean,
|
||||
|
||||
/**
|
||||
* All available languages to display in the language select dropdown.
|
||||
*/
|
||||
|
@ -70,18 +65,6 @@ export type Props = {
|
|||
*/
|
||||
showPrejoinPage: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user has selected the Start Audio Muted feature to be
|
||||
* enabled.
|
||||
*/
|
||||
startAudioMuted: boolean,
|
||||
|
||||
/**
|
||||
* Whether or not the user has selected the Start Video Muted feature to be
|
||||
* enabled.
|
||||
*/
|
||||
startVideoMuted: boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
|
@ -129,9 +112,6 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
this._onFramerateItemSelect = this._onFramerateItemSelect.bind(this);
|
||||
this._onLanguageDropdownOpenChange = this._onLanguageDropdownOpenChange.bind(this);
|
||||
this._onLanguageItemSelect = this._onLanguageItemSelect.bind(this);
|
||||
this._onStartAudioMutedChanged = this._onStartAudioMutedChanged.bind(this);
|
||||
this._onStartVideoMutedChanged = this._onStartVideoMutedChanged.bind(this);
|
||||
this._onFollowMeEnabledChanged = this._onFollowMeEnabledChanged.bind(this);
|
||||
this._onShowPrejoinPageChanged = this._onShowPrejoinPageChanged.bind(this);
|
||||
this._onKeyboardShortcutEnableChanged = this._onKeyboardShortcutEnableChanged.bind(this);
|
||||
}
|
||||
|
@ -148,7 +128,13 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
content.push(this._renderSettingsLeft());
|
||||
content.push(this._renderSettingsRight());
|
||||
|
||||
return <div className = 'more-tab box'>{ content }</div>;
|
||||
return (
|
||||
<div
|
||||
className = 'more-tab box'
|
||||
key = 'more'>
|
||||
{ content }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_onFramerateDropdownOpenChange: (Object) => void;
|
||||
|
@ -207,48 +193,6 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
super._onChange({ currentLanguage: language });
|
||||
}
|
||||
|
||||
_onStartAudioMutedChanged: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to select if conferences should start
|
||||
* with audio muted.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStartAudioMutedChanged({ target: { checked } }) {
|
||||
super._onChange({ startAudioMuted: checked });
|
||||
}
|
||||
|
||||
_onStartVideoMutedChanged: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to select if conferences should start
|
||||
* with video disabled.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onStartVideoMutedChanged({ target: { checked } }) {
|
||||
super._onChange({ startVideoMuted: checked });
|
||||
}
|
||||
|
||||
_onFollowMeEnabledChanged: (Object) => void;
|
||||
|
||||
/**
|
||||
* Callback invoked to select if follow-me mode
|
||||
* should be activated.
|
||||
*
|
||||
* @param {Object} e - The key event to handle.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_onFollowMeEnabledChanged({ target: { checked } }) {
|
||||
super._onChange({ followMeEnabled: checked });
|
||||
}
|
||||
|
||||
_onShowPrejoinPageChanged: (Object) => void;
|
||||
|
||||
/**
|
||||
|
@ -410,48 +354,6 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the React Element for modifying conference-wide settings.
|
||||
*
|
||||
* @private
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderModeratorSettings() {
|
||||
const {
|
||||
followMeActive,
|
||||
followMeEnabled,
|
||||
startAudioMuted,
|
||||
startVideoMuted,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'settings-sub-pane-element'
|
||||
key = 'moderator'>
|
||||
<h2 className = 'mock-atlaskit-label'>
|
||||
{ t('settings.moderator') }
|
||||
</h2>
|
||||
<Checkbox
|
||||
isChecked = { startAudioMuted }
|
||||
label = { t('settings.startAudioMuted') }
|
||||
name = 'start-audio-muted'
|
||||
onChange = { this._onStartAudioMutedChanged } />
|
||||
<Checkbox
|
||||
isChecked = { startVideoMuted }
|
||||
label = { t('settings.startVideoMuted') }
|
||||
name = 'start-video-muted'
|
||||
onChange = { this._onStartVideoMutedChanged } />
|
||||
<Checkbox
|
||||
isChecked = { followMeEnabled && !followMeActive }
|
||||
isDisabled = { followMeActive }
|
||||
label = { t('settings.followMe') }
|
||||
name = 'follow-me'
|
||||
onChange = { this._onFollowMeEnabledChanged } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the React Element for modifying prejoin screen settings.
|
||||
*
|
||||
|
@ -488,7 +390,8 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
|
||||
return (
|
||||
<div
|
||||
className = 'settings-sub-pane right'>
|
||||
className = 'settings-sub-pane right'
|
||||
key = 'settings-sub-pane-right'>
|
||||
{ showLanguageSettings && this._renderLanguageSelect() }
|
||||
{ this._renderFramerateSelect() }
|
||||
</div>
|
||||
|
@ -501,14 +404,14 @@ class MoreTab extends AbstractDialogTab<Props, State> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
_renderSettingsLeft() {
|
||||
const { showPrejoinSettings, showModeratorSettings } = this.props;
|
||||
const { showPrejoinSettings } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = 'settings-sub-pane left'>
|
||||
className = 'settings-sub-pane left'
|
||||
key = 'settings-sub-pane-left'>
|
||||
{ showPrejoinSettings && this._renderPrejoinScreenSettings() }
|
||||
{ this._renderKeyboardShortcutCheckbox() }
|
||||
{ showModeratorSettings && this._renderModeratorSettings() }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,11 +11,22 @@ import {
|
|||
getDeviceSelectionDialogProps,
|
||||
submitDeviceSelectionTab
|
||||
} from '../../../device-selection';
|
||||
import { submitMoreTab, submitProfileTab, submitSoundsTab } from '../../actions';
|
||||
import {
|
||||
submitModeratorTab,
|
||||
submitMoreTab,
|
||||
submitProfileTab,
|
||||
submitSoundsTab
|
||||
} from '../../actions';
|
||||
import { SETTINGS_TABS } from '../../constants';
|
||||
import { getMoreTabProps, getProfileTabProps, getSoundsTabProps } from '../../functions';
|
||||
import {
|
||||
getModeratorTabProps,
|
||||
getMoreTabProps,
|
||||
getProfileTabProps,
|
||||
getSoundsTabProps
|
||||
} from '../../functions';
|
||||
|
||||
import CalendarTab from './CalendarTab';
|
||||
import ModeratorTab from './ModeratorTab';
|
||||
import MoreTab from './MoreTab';
|
||||
import ProfileTab from './ProfileTab';
|
||||
import SoundsTab from './SoundsTab';
|
||||
|
@ -131,7 +142,9 @@ function _mapStateToProps(state) {
|
|||
// The settings sections to display.
|
||||
const showDeviceSettings = configuredTabs.includes('devices');
|
||||
const moreTabProps = getMoreTabProps(state);
|
||||
const { showModeratorSettings, showLanguageSettings, showPrejoinSettings } = moreTabProps;
|
||||
const moderatorTabProps = getModeratorTabProps(state);
|
||||
const { showModeratorSettings } = moderatorTabProps;
|
||||
const { showLanguageSettings, showPrejoinSettings } = moreTabProps;
|
||||
const showProfileSettings
|
||||
= configuredTabs.includes('profile') && !state['features/base/config'].disableProfile;
|
||||
const showCalendarSettings
|
||||
|
@ -176,6 +189,28 @@ function _mapStateToProps(state) {
|
|||
});
|
||||
}
|
||||
|
||||
if (showModeratorSettings) {
|
||||
tabs.push({
|
||||
name: SETTINGS_TABS.MODERATOR,
|
||||
component: ModeratorTab,
|
||||
label: 'settings.moderator',
|
||||
props: moderatorTabProps,
|
||||
propsUpdateFunction: (tabState, newProps) => {
|
||||
// Updates tab props, keeping users selection
|
||||
|
||||
return {
|
||||
...newProps,
|
||||
followMeEnabled: tabState.followMeEnabled,
|
||||
startAudioMuted: tabState.startAudioMuted,
|
||||
startVideoMuted: tabState.startVideoMuted,
|
||||
startReactionsMuted: tabState.startReactionsMuted
|
||||
};
|
||||
},
|
||||
styles: 'settings-pane moderator-pane',
|
||||
submit: submitModeratorTab
|
||||
});
|
||||
}
|
||||
|
||||
if (showCalendarSettings) {
|
||||
tabs.push({
|
||||
name: SETTINGS_TABS.CALENDAR,
|
||||
|
@ -196,7 +231,7 @@ function _mapStateToProps(state) {
|
|||
});
|
||||
}
|
||||
|
||||
if (showModeratorSettings || showLanguageSettings || showPrejoinSettings) {
|
||||
if (showLanguageSettings || showPrejoinSettings) {
|
||||
tabs.push({
|
||||
name: SETTINGS_TABS.MORE,
|
||||
component: MoreTab,
|
||||
|
@ -209,10 +244,7 @@ function _mapStateToProps(state) {
|
|||
...newProps,
|
||||
currentFramerate: tabState.currentFramerate,
|
||||
currentLanguage: tabState.currentLanguage,
|
||||
followMeEnabled: tabState.followMeEnabled,
|
||||
showPrejoinPage: tabState.showPrejoinPage,
|
||||
startAudioMuted: tabState.startAudioMuted,
|
||||
startVideoMuted: tabState.startVideoMuted
|
||||
showPrejoinPage: tabState.showPrejoinPage
|
||||
};
|
||||
},
|
||||
styles: 'settings-pane more-pane',
|
||||
|
|
|
@ -45,6 +45,11 @@ export type Props = {
|
|||
*/
|
||||
soundsReactions: Boolean,
|
||||
|
||||
/**
|
||||
* Whether or not moderator muted the sounds.
|
||||
*/
|
||||
moderatorMutedSoundsReactions: Boolean,
|
||||
|
||||
/**
|
||||
* Invoked to obtain translated strings.
|
||||
*/
|
||||
|
@ -97,6 +102,7 @@ class SoundsTab extends AbstractDialogTab<Props> {
|
|||
soundsTalkWhileMuted,
|
||||
soundsReactions,
|
||||
enableReactions,
|
||||
moderatorMutedSoundsReactions,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
|
@ -109,6 +115,7 @@ class SoundsTab extends AbstractDialogTab<Props> {
|
|||
</h2>
|
||||
{enableReactions && <Checkbox
|
||||
isChecked = { soundsReactions }
|
||||
isDisabled = { moderatorMutedSoundsReactions }
|
||||
label = { t('settings.reactions') }
|
||||
name = 'soundsReactions'
|
||||
onChange = { this._onChange } />
|
||||
|
|
|
@ -2,6 +2,7 @@ export const SETTINGS_TABS = {
|
|||
CALENDAR: 'calendar_tab',
|
||||
DEVICES: 'devices_tab',
|
||||
MORE: 'more_tab',
|
||||
MODERATOR: 'moderator-tab',
|
||||
PROFILE: 'profile_tab',
|
||||
SOUNDS: 'sounds_tab'
|
||||
};
|
||||
|
|
|
@ -78,17 +78,6 @@ export function normalizeUserInputURL(url: string) {
|
|||
/* eslint-enable no-param-reassign */
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for web. Returns whether or not only Device Selection is configured to
|
||||
* display as a setting.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function shouldShowOnlyDeviceSelection() {
|
||||
return interfaceConfig.SETTINGS_SECTIONS.length === 1
|
||||
&& isSettingEnabled('devices');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties for the "More" tab from settings dialog from Redux
|
||||
* state.
|
||||
|
@ -101,32 +90,50 @@ export function getMoreTabProps(stateful: Object | Function) {
|
|||
const state = toState(stateful);
|
||||
const framerate = state['features/screen-share'].captureFrameRate ?? SS_DEFAULT_FRAME_RATE;
|
||||
const language = i18next.language || DEFAULT_LANGUAGE;
|
||||
const {
|
||||
conference,
|
||||
followMeEnabled,
|
||||
startAudioMutedPolicy,
|
||||
startVideoMutedPolicy
|
||||
} = state['features/base/conference'];
|
||||
const followMeActive = isFollowMeActive(state);
|
||||
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
|
||||
|
||||
// The settings sections to display.
|
||||
const showModeratorSettings = Boolean(
|
||||
conference
|
||||
&& configuredTabs.includes('moderator')
|
||||
&& isLocalParticipantModerator(state));
|
||||
|
||||
return {
|
||||
currentFramerate: framerate,
|
||||
currentLanguage: language,
|
||||
desktopShareFramerates: SS_SUPPORTED_FRAMERATES,
|
||||
followMeActive: Boolean(conference && followMeActive),
|
||||
followMeEnabled: Boolean(conference && followMeEnabled),
|
||||
languages: LANGUAGES,
|
||||
showLanguageSettings: configuredTabs.includes('language'),
|
||||
showModeratorSettings,
|
||||
showPrejoinSettings: state['features/base/config'].prejoinPageEnabled,
|
||||
showPrejoinPage: !state['features/base/settings'].userSelectedSkipPrejoin,
|
||||
showPrejoinSettings: state['features/base/config'].prejoinPageEnabled
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the properties for the "More" tab from settings dialog from Redux
|
||||
* state.
|
||||
*
|
||||
* @param {(Function|Object)} stateful -The (whole) redux state, or redux's
|
||||
* {@code getState} function to be used to retrieve the state.
|
||||
* @returns {Object} - The properties for the "More" tab from settings dialog.
|
||||
*/
|
||||
export function getModeratorTabProps(stateful: Object | Function) {
|
||||
const state = toState(stateful);
|
||||
const {
|
||||
conference,
|
||||
followMeEnabled,
|
||||
startAudioMutedPolicy,
|
||||
startVideoMutedPolicy,
|
||||
startReactionsMuted
|
||||
} = state['features/base/conference'];
|
||||
const followMeActive = isFollowMeActive(state);
|
||||
const configuredTabs = interfaceConfig.SETTINGS_SECTIONS || [];
|
||||
|
||||
const showModeratorSettings = Boolean(
|
||||
conference
|
||||
&& configuredTabs.includes('moderator')
|
||||
&& isLocalParticipantModerator(state));
|
||||
|
||||
// The settings sections to display.
|
||||
return {
|
||||
showModeratorSettings,
|
||||
followMeActive: Boolean(conference && followMeActive),
|
||||
followMeEnabled: Boolean(conference && followMeEnabled),
|
||||
startReactionsMuted: Boolean(conference && startReactionsMuted),
|
||||
startAudioMuted: Boolean(conference && startAudioMutedPolicy),
|
||||
startVideoMuted: Boolean(conference && startVideoMutedPolicy)
|
||||
};
|
||||
|
@ -178,6 +185,7 @@ export function getSoundsTabProps(stateful: Object | Function) {
|
|||
soundsReactions
|
||||
} = state['features/base/settings'];
|
||||
const enableReactions = isReactionsEnabled(state);
|
||||
const moderatorMutedSoundsReactions = state['features/base/conference'].startReactionsMuted ?? false;
|
||||
|
||||
return {
|
||||
soundsIncomingMessage,
|
||||
|
@ -185,7 +193,8 @@ export function getSoundsTabProps(stateful: Object | Function) {
|
|||
soundsParticipantLeft,
|
||||
soundsTalkWhileMuted,
|
||||
soundsReactions,
|
||||
enableReactions
|
||||
enableReactions,
|
||||
moderatorMutedSoundsReactions
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -53,8 +53,8 @@ MiddlewareRegistry.register(store => next => action => {
|
|||
const forceMuted = isForceMuted(local, MEDIA_TYPE.AUDIO, state);
|
||||
const notification = await dispatch(showNotification({
|
||||
titleKey: 'toolbar.talkWhileMutedPopup',
|
||||
customActionNameKey: forceMuted ? 'notify.raiseHandAction' : 'notify.unmute',
|
||||
customActionHandler: () => dispatch(forceMuted ? raiseHand(true) : setAudioMuted(false))
|
||||
customActionNameKey: [ forceMuted ? 'notify.raiseHandAction' : 'notify.unmute' ],
|
||||
customActionHandler: [ () => dispatch(forceMuted ? raiseHand(true) : setAudioMuted(false)) ]
|
||||
}, NOTIFICATION_TIMEOUT_TYPE.LONG));
|
||||
|
||||
const { soundsTalkWhileMuted } = getState()['features/base/settings'];
|
||||
|
|
Loading…
Reference in New Issue