feat(reaction-sounds) Added sounds for reactions (#9775)

* Added sounds for reactions

* Updated reactions list

* Added reactions to sound settings

* Added support for multiple sounds

* Added feature flag for sounds

* Updated sound settings

Moved reactions toggle at the top of the list

* Added disable reaction sounds notification

* Added reaction button zoom for burst intensity

* Fixed raise hand sound

* Fixed register sounds for reactions

* Changed boo emoji

* Updated sounds

* Fixed lint errors

* Fixed reaction sounds file names

* Fix raise hand sound

Play sound only on raise hand not on lower hand

* Fixed types for sound constants

* Fixed type for raise hand sound constant
This commit is contained in:
robertpin 2021-08-23 12:57:56 +03:00 committed by GitHub
parent fe41eef398
commit c7a91e1974
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 435 additions and 59 deletions

View File

@ -48,6 +48,13 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: font-size ease .1s;
@for $i from 1 through 12 {
&.increase-#{$i}{
font-size: calc(20px + #{$i}px);
}
}
} }
} }

View File

@ -591,6 +591,7 @@
"moderationStoppedTitle": "Moderation stopped", "moderationStoppedTitle": "Moderation stopped",
"moderationToggleDescription": "by {{participantDisplayName}}", "moderationToggleDescription": "by {{participantDisplayName}}",
"raiseHandAction": "Raise hand", "raiseHandAction": "Raise hand",
"reactionSounds": "Disable sounds",
"groupTitle": "Notifications" "groupTitle": "Notifications"
}, },
"participantsPane": { "participantsPane": {
@ -794,6 +795,7 @@
"participantJoined": "Participant Joined", "participantJoined": "Participant Joined",
"participantLeft": "Participant Left", "participantLeft": "Participant Left",
"playSounds": "Play sound on", "playSounds": "Play sound on",
"reactions": "Meeting reactions",
"sameAsSystem": "Same as system ({{label}})", "sameAsSystem": "Same as system ({{label}})",
"selectAudioOutput": "Audio output", "selectAudioOutput": "Audio output",
"selectCamera": "Camera", "selectCamera": "Camera",
@ -884,7 +886,6 @@
"muteEveryonesVideo": "Disable everyone's camera", "muteEveryonesVideo": "Disable everyone's camera",
"muteEveryoneElsesVideo": "Disable everyone else's camera", "muteEveryoneElsesVideo": "Disable everyone else's camera",
"participants": "Participants", "participants": "Participants",
"party": "Party Popper",
"pip": "Toggle Picture-in-Picture mode", "pip": "Toggle Picture-in-Picture mode",
"privateMessage": "Send private message", "privateMessage": "Send private message",
"profile": "Edit your profile", "profile": "Edit your profile",
@ -901,6 +902,7 @@
"shareYourScreen": "Start / Stop sharing your screen", "shareYourScreen": "Start / Stop sharing your screen",
"shortcuts": "Toggle shortcuts", "shortcuts": "Toggle shortcuts",
"show": "Show on stage", "show": "Show on stage",
"silence": "Silence",
"speakerStats": "Toggle speaker statistics", "speakerStats": "Toggle speaker statistics",
"surprised": "Surprised", "surprised": "Surprised",
"tileView": "Toggle tile view", "tileView": "Toggle tile view",
@ -925,6 +927,7 @@
"clap": "Clap", "clap": "Clap",
"closeChat": "Close chat", "closeChat": "Close chat",
"closeReactionsMenu": "Close reactions menu", "closeReactionsMenu": "Close reactions menu",
"disableReactionSounds": "You can disable reaction sounds for this meeting",
"documentClose": "Close shared document", "documentClose": "Close shared document",
"documentOpen": "Open shared document", "documentOpen": "Open shared document",
"download": "Download our apps", "download": "Download our apps",
@ -960,7 +963,6 @@
"openChat": "Open chat", "openChat": "Open chat",
"openReactionsMenu": "Open reactions menu", "openReactionsMenu": "Open reactions menu",
"participants": "Participants", "participants": "Participants",
"party": "Celebration",
"pip": "Enter Picture-in-Picture mode", "pip": "Enter Picture-in-Picture mode",
"privateMessage": "Send private message", "privateMessage": "Send private message",
"profile": "Edit your profile", "profile": "Edit your profile",
@ -970,7 +972,7 @@
"reactionClap": "Send clap reaction", "reactionClap": "Send clap reaction",
"reactionLaugh": "Send laugh reaction", "reactionLaugh": "Send laugh reaction",
"reactionLike": "Send thumbs up reaction", "reactionLike": "Send thumbs up reaction",
"reactionParty": "Send party popper reaction", "reactionSilence": "Send silence reaction",
"reactionSurprised": "Send surprised reaction", "reactionSurprised": "Send surprised reaction",
"security": "Security options", "security": "Security options",
"Settings": "Settings", "Settings": "Settings",
@ -978,6 +980,7 @@
"sharedvideo": "Share video", "sharedvideo": "Share video",
"shareRoom": "Invite someone", "shareRoom": "Invite someone",
"shortcuts": "View shortcuts", "shortcuts": "View shortcuts",
"silence": "Silence",
"speakerStats": "Speaker stats", "speakerStats": "Speaker stats",
"startScreenSharing": "Start screen sharing", "startScreenSharing": "Start screen sharing",
"startSubtitles": "Start subtitles", "startSubtitles": "Start subtitles",

View File

@ -31,6 +31,7 @@ const DEFAULT_STATE = {
soundsParticipantJoined: true, soundsParticipantJoined: true,
soundsParticipantLeft: true, soundsParticipantLeft: true,
soundsTalkWhileMuted: true, soundsTalkWhileMuted: true,
soundsReactions: true,
startAudioOnly: false, startAudioOnly: false,
startWithAudioMuted: false, startWithAudioMuted: false,
startWithVideoMuted: false, startWithVideoMuted: false,

View File

@ -58,3 +58,8 @@ export const SEND_REACTIONS = 'SEND_REACTIONS';
* The type of action to adds reactions to the queue. * The type of action to adds reactions to the queue.
*/ */
export const PUSH_REACTIONS = 'PUSH_REACTIONS'; export const PUSH_REACTIONS = 'PUSH_REACTIONS';
/**
* The type of action to display disable notification sounds.
*/
export const SHOW_SOUNDS_NOTIFICATION = 'SHOW_SOUNDS_NOTIFICATION';

View File

@ -1,16 +1,28 @@
// @flow // @flow
import { import {
SHOW_SOUNDS_NOTIFICATION,
TOGGLE_REACTIONS_VISIBLE TOGGLE_REACTIONS_VISIBLE
} from './actionTypes'; } from './actionTypes';
/** /**
* Toggles the visibility of the reactions menu. * Toggles the visibility of the reactions menu.
* *
* @returns {Function} * @returns {Object}
*/ */
export function toggleReactionsMenuVisibility() { export function toggleReactionsMenuVisibility() {
return { return {
type: TOGGLE_REACTIONS_VISIBLE type: TOGGLE_REACTIONS_VISIBLE
}; };
} }
/**
* Displays the disable sounds notification.
*
* @returns {Object}
*/
export function displayReactionSoundsNotification() {
return {
type: SHOW_SOUNDS_NOTIFICATION
};
}

View File

@ -28,12 +28,28 @@ type Props = AbstractToolbarButtonProps & {
label?: string label?: string
}; };
/**
* The type of the React {@code Component} state of {@link ReactionButton}.
*/
type State = {
/**
* Used to determine zoom level on reaction burst.
*/
increaseLevel: number,
/**
* Timeout ID to reset reaction burst.
*/
increaseTimeout: TimeoutID | null
}
/** /**
* Represents a button in the reactions menu. * Represents a button in the reactions menu.
* *
* @extends AbstractToolbarButton * @extends AbstractToolbarButton
*/ */
class ReactionButton extends AbstractToolbarButton<Props> { class ReactionButton extends AbstractToolbarButton<Props, State> {
/** /**
* Default values for {@code ReactionButton} component's properties. * Default values for {@code ReactionButton} component's properties.
* *
@ -52,10 +68,18 @@ class ReactionButton extends AbstractToolbarButton<Props> {
super(props); super(props);
this._onKeyDown = this._onKeyDown.bind(this); this._onKeyDown = this._onKeyDown.bind(this);
this._onClickHandler = this._onClickHandler.bind(this);
this.state = {
increaseLevel: 0,
increaseTimeout: null
};
} }
_onKeyDown: (Object) => void; _onKeyDown: (Object) => void;
_onClickHandler: () => void;
/** /**
* Handles 'Enter' key on the button to trigger onClick for accessibility. * Handles 'Enter' key on the button to trigger onClick for accessibility.
* We should be handling Space onKeyUp but it conflicts with PTT. * We should be handling Space onKeyUp but it conflicts with PTT.
@ -78,6 +102,28 @@ class ReactionButton extends AbstractToolbarButton<Props> {
} }
} }
/**
* Handles reaction button click.
*
* @returns {void}
*/
_onClickHandler() {
this.props.onClick();
clearTimeout(this.state.increaseTimeout);
const timeout = setTimeout(() => {
this.setState({
increaseLevel: 0
});
}, 500);
this.setState(state => {
return {
increaseLevel: state.increaseLevel + 1,
increaseTimeout: timeout
};
});
}
/** /**
* Renders the button of this {@code ReactionButton}. * Renders the button of this {@code ReactionButton}.
* *
@ -92,7 +138,7 @@ class ReactionButton extends AbstractToolbarButton<Props> {
aria-label = { this.props.accessibilityLabel } aria-label = { this.props.accessibilityLabel }
aria-pressed = { this.props.toggled } aria-pressed = { this.props.toggled }
className = 'toolbox-button' className = 'toolbox-button'
onClick = { this.props.onClick } onClick = { this._onClickHandler }
onKeyDown = { this._onKeyDown } onKeyDown = { this._onKeyDown }
role = 'button' role = 'button'
tabIndex = { 0 }> tabIndex = { 0 }>
@ -113,10 +159,13 @@ class ReactionButton extends AbstractToolbarButton<Props> {
* @inheritdoc * @inheritdoc
*/ */
_renderIcon() { _renderIcon() {
const { toggled, icon, label } = this.props;
const { increaseLevel } = this.state;
return ( return (
<div className = { `toolbox-icon ${this.props.toggled ? 'toggled' : ''}` }> <div className = { `toolbox-icon ${toggled ? 'toggled' : ''}` }>
<span className = 'emoji'>{this.props.icon}</span> <span className = { `emoji increase-${increaseLevel > 12 ? 12 : increaseLevel}` }>{icon}</span>
{this.props.label && <span className = 'text'>{this.props.label}</span>} {label && <span className = 'text'>{label}</span>}
</div> </div>
); );
} }

View File

@ -11,10 +11,11 @@ import {
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants'; import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { playSound } from '../../../base/sounds';
import { dockToolbox } from '../../../toolbox/actions.web'; import { dockToolbox } from '../../../toolbox/actions.web';
import { addReactionToBuffer } from '../../actions.any'; import { addReactionToBuffer } from '../../actions.any';
import { toggleReactionsMenuVisibility } from '../../actions.web'; import { toggleReactionsMenuVisibility } from '../../actions.web';
import { REACTIONS } from '../../constants'; import { RAISE_HAND_SOUND_ID, REACTIONS } from '../../constants';
import ReactionButton from './ReactionButton'; import ReactionButton from './ReactionButton';
@ -53,7 +54,12 @@ type Props = {
/** /**
* Whether or not it's displayed in the overflow menu. * Whether or not it's displayed in the overflow menu.
*/ */
overflowMenu: boolean overflowMenu: boolean,
/**
* Whether or not reaction sounds are enabled.
*/
_reactionSounds: boolean
}; };
declare var APP: Object; declare var APP: Object;
@ -106,11 +112,16 @@ class ReactionsMenu extends Component<Props> {
* @returns {void} * @returns {void}
*/ */
_onToolbarToggleRaiseHand() { _onToolbarToggleRaiseHand() {
const { dispatch, _raisedHand, _reactionSounds } = this.props;
sendAnalytics(createToolbarEvent( sendAnalytics(createToolbarEvent(
'raise.hand', 'raise.hand',
{ enable: !this.props._raisedHand })); { enable: !_raisedHand }));
this._doToggleRaiseHand(); this._doToggleRaiseHand();
this.props.dispatch(toggleReactionsMenuVisibility()); dispatch(toggleReactionsMenuVisibility());
if (_reactionSounds && _raisedHand) {
dispatch(playSound(RAISE_HAND_SOUND_ID));
}
} }
/** /**
@ -212,11 +223,13 @@ class ReactionsMenu extends Component<Props> {
*/ */
function mapStateToProps(state) { function mapStateToProps(state) {
const localParticipant = getLocalParticipant(state); const localParticipant = getLocalParticipant(state);
const { soundsReactions } = state['features/base/settings'];
return { return {
_localParticipantID: localParticipant.id, _localParticipantID: localParticipant.id,
_raisedHand: localParticipant.raisedHand, _raisedHand: localParticipant.raisedHand,
_participantCount: getParticipantCount(state) _participantCount: getParticipantCount(state),
_reactionSounds: soundsReactions
}; };
} }

View File

@ -1,37 +1,69 @@
// @flow // @flow
export const REACTIONS = { import {
like: { CLAP_SOUND_FILES,
message: ':thumbs_up:', LAUGH_SOUND_FILES,
emoji: '👍', LIKE_SOUND_FILES,
shortcutChar: 'T' BOO_SOUND_FILES,
}, SURPRISE_SOUND_FILES,
clap: { SILENCE_SOUND_FILES
message: ':clap:', } from './sounds';
emoji: '👏',
shortcutChar: 'C' /**
}, * The audio ID prefix of the audio element for which the {@link playAudio} action is
laugh: { * triggered when a new laugh reaction is received.
message: ':grinning_face:', *
emoji: '😀', * @type { string }
shortcutChar: 'L' */
}, export const LAUGH_SOUND_ID = 'LAUGH_SOUND_';
surprised: {
message: ':face_with_open_mouth:', /**
emoji: '😮', * The audio ID prefix of the audio element for which the {@link playAudio} action is
shortcutChar: 'O' * triggered when a new clap reaction is received.
}, *
boo: { * @type {string}
message: ':slightly_frowning_face:', */
emoji: '🙁', export const CLAP_SOUND_ID = 'CLAP_SOUND_';
shortcutChar: 'B'
}, /**
party: { * The audio ID prefix of the audio element for which the {@link playAudio} action is
message: ':party_popper:', * triggered when a new like reaction is received.
emoji: '🎉', *
shortcutChar: 'P' * @type {string}
} */
}; export const LIKE_SOUND_ID = 'LIKE_SOUND_';
/**
* The audio ID prefix of the audio element for which the {@link playAudio} action is
* triggered when a new boo reaction is received.
*
* @type {string}
*/
export const BOO_SOUND_ID = 'BOO_SOUND_';
/**
* The audio ID prefix of the audio element for which the {@link playAudio} action is
* triggered when a new surprised reaction is received.
*
* @type {string}
*/
export const SURPRISE_SOUND_ID = 'SURPRISE_SOUND_';
/**
* The audio ID prefix of the audio element for which the {@link playAudio} action is
* triggered when a new silence reaction is received.
*
* @type {string}
*/
export const SILENCE_SOUND_ID = 'SILENCE_SOUND_';
/**
* The audio ID of the audio element for which the {@link playAudio} action is
* triggered when a new raise hand event is received.
*
* @type {string}
*/
export const RAISE_HAND_SOUND_ID = 'RAISE_HAND_SOUND_ID';
export type ReactionEmojiProps = { export type ReactionEmojiProps = {
@ -45,3 +77,51 @@ export type ReactionEmojiProps = {
*/ */
uid: number uid: number
} }
export const SOUNDS_THRESHOLDS = [ 1, 4, 10 ];
export const REACTIONS = {
like: {
message: ':thumbs_up:',
emoji: '👍',
shortcutChar: 'T',
soundId: LIKE_SOUND_ID,
soundFiles: LIKE_SOUND_FILES
},
clap: {
message: ':clap:',
emoji: '👏',
shortcutChar: 'C',
soundId: CLAP_SOUND_ID,
soundFiles: CLAP_SOUND_FILES
},
laugh: {
message: ':grinning_face:',
emoji: '😀',
shortcutChar: 'L',
soundId: LAUGH_SOUND_ID,
soundFiles: LAUGH_SOUND_FILES
},
surprised: {
message: ':face_with_open_mouth:',
emoji: '😮',
shortcutChar: 'O',
soundId: SURPRISE_SOUND_ID,
soundFiles: SURPRISE_SOUND_FILES
},
boo: {
message: ':slightly_frowning_face:',
emoji: '🙁',
shortcutChar: 'B',
soundId: BOO_SOUND_ID,
soundFiles: BOO_SOUND_FILES
},
silence: {
message: ':face_without_mouth:',
emoji: '😶',
shortcutChar: 'S',
soundId: SILENCE_SOUND_ID,
soundFiles: SILENCE_SOUND_FILES
}
};

View File

@ -5,7 +5,7 @@ import uuid from 'uuid';
import { getLocalParticipant } from '../base/participants'; import { getLocalParticipant } from '../base/participants';
import { extractFqnFromPath } from '../dynamic-branding/functions'; import { extractFqnFromPath } from '../dynamic-branding/functions';
import { REACTIONS } from './constants'; import { REACTIONS, SOUNDS_THRESHOLDS } from './constants';
import logger from './logger'; import logger from './logger';
/** /**
@ -88,3 +88,57 @@ export async function sendReactionsWebhook(state: Object, reactions: Array<?stri
} }
} }
} }
/**
* Returns unique reactions from the reactions buffer.
*
* @param {Array} reactions - The reactions buffer.
* @returns {Array}
*/
function getUniqueReactions(reactions: Array<string>) {
return [ ...new Set(reactions) ];
}
/**
* Returns frequency of given reaction in array.
*
* @param {Array} reactions - Array of reactions.
* @param {string} reaction - Reaction to get frequency for.
* @returns {number}
*/
function getReactionFrequency(reactions: Array<string>, reaction: string) {
return reactions.filter(r => r === reaction).length;
}
/**
* Returns the threshold number for a given frequency.
*
* @param {number} frequency - Frequency of reaction.
* @returns {number}
*/
function getSoundThresholdByFrequency(frequency) {
for (const i of SOUNDS_THRESHOLDS) {
if (frequency <= i) {
return i;
}
}
return SOUNDS_THRESHOLDS[SOUNDS_THRESHOLDS.length - 1];
}
/**
* Returns unique reactions with threshold.
*
* @param {Array} reactions - The reactions buffer.
* @returns {Array}
*/
export function getReactionsSoundsThresholds(reactions: Array<string>) {
const unique = getUniqueReactions(reactions);
return unique.map<Object>(reaction => {
return {
reaction,
threshold: getSoundThresholdByFrequency(getReactionFrequency(reactions, reaction))
};
});
}

View File

@ -3,14 +3,19 @@
import { batch } from 'react-redux'; import { batch } from 'react-redux';
import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants'; import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
import { updateSettings } from '../base/settings';
import { playSound, registerSound, unregisterSound } from '../base/sounds';
import { isVpaasMeeting } from '../jaas/functions'; import { isVpaasMeeting } from '../jaas/functions';
import { NOTIFICATION_TIMEOUT, showNotification } from '../notifications';
import { import {
ADD_REACTION_BUFFER, ADD_REACTION_BUFFER,
FLUSH_REACTION_BUFFER, FLUSH_REACTION_BUFFER,
SEND_REACTIONS, SEND_REACTIONS,
PUSH_REACTIONS PUSH_REACTIONS,
SHOW_SOUNDS_NOTIFICATION
} from './actionTypes'; } from './actionTypes';
import { import {
addReactionsToChat, addReactionsToChat,
@ -19,7 +24,15 @@ import {
sendReactions, sendReactions,
setReactionQueue setReactionQueue
} from './actions.any'; } from './actions.any';
import { getReactionMessageFromBuffer, getReactionsWithId, sendReactionsWebhook } from './functions.any'; import { displayReactionSoundsNotification } from './actions.web';
import { RAISE_HAND_SOUND_ID, REACTIONS, SOUNDS_THRESHOLDS } from './constants';
import {
getReactionMessageFromBuffer,
getReactionsSoundsThresholds,
getReactionsWithId,
sendReactionsWebhook
} from './functions.any';
import { RAISE_HAND_SOUND_FILE } from './sounds';
declare var APP: Object; declare var APP: Object;
@ -35,6 +48,33 @@ MiddlewareRegistry.register(store => next => action => {
const { dispatch, getState } = store; const { dispatch, getState } = store;
switch (action.type) { switch (action.type) {
case APP_WILL_MOUNT:
batch(() => {
Object.keys(REACTIONS).forEach(key => {
for (let i = 0; i < SOUNDS_THRESHOLDS.length; i++) {
dispatch(registerSound(
`${REACTIONS[key].soundId}${SOUNDS_THRESHOLDS[i]}`,
REACTIONS[key].soundFiles[i]
)
);
}
}
);
dispatch(registerSound(RAISE_HAND_SOUND_ID, RAISE_HAND_SOUND_FILE));
});
break;
case APP_WILL_UNMOUNT:
batch(() => {
Object.keys(REACTIONS).forEach(key => {
for (let i = 0; i < SOUNDS_THRESHOLDS.length; i++) {
dispatch(unregisterSound(`${REACTIONS[key].soundId}${SOUNDS_THRESHOLDS[i]}`));
}
});
dispatch(unregisterSound(RAISE_HAND_SOUND_ID));
});
break;
case ADD_REACTION_BUFFER: { case ADD_REACTION_BUFFER: {
const { timeoutID, buffer } = getState()['features/reactions']; const { timeoutID, buffer } = getState()['features/reactions'];
const { reaction } = action; const { reaction } = action;
@ -82,10 +122,36 @@ MiddlewareRegistry.register(store => next => action => {
} }
case PUSH_REACTIONS: { case PUSH_REACTIONS: {
const queue = store.getState()['features/reactions'].queue; const state = getState();
const { queue, notificationDisplayed } = state['features/reactions'];
const { soundsReactions } = state['features/base/settings'];
const reactions = action.reactions; const reactions = action.reactions;
dispatch(setReactionQueue([ ...queue, ...getReactionsWithId(reactions) ])); batch(() => {
if (!notificationDisplayed && soundsReactions) {
dispatch(displayReactionSoundsNotification());
}
if (soundsReactions) {
const reactionSoundsThresholds = getReactionsSoundsThresholds(reactions);
reactionSoundsThresholds.forEach(reaction =>
dispatch(playSound(`${REACTIONS[reaction.reaction].soundId}${reaction.threshold}`))
);
}
dispatch(setReactionQueue([ ...queue, ...getReactionsWithId(reactions) ]));
});
break;
}
case SHOW_SOUNDS_NOTIFICATION: {
dispatch(showNotification({
titleKey: 'toolbar.disableReactionSounds',
customActionNameKey: 'notify.reactionSounds',
customActionHandler: () => dispatch(updateSettings({
soundsReactions: false
}))
}, NOTIFICATION_TIMEOUT));
break;
} }
} }

View File

@ -6,7 +6,8 @@ import {
TOGGLE_REACTIONS_VISIBLE, TOGGLE_REACTIONS_VISIBLE,
SET_REACTION_QUEUE, SET_REACTION_QUEUE,
ADD_REACTION_BUFFER, ADD_REACTION_BUFFER,
FLUSH_REACTION_BUFFER FLUSH_REACTION_BUFFER,
SHOW_SOUNDS_NOTIFICATION
} from './actionTypes'; } from './actionTypes';
/** /**
@ -17,7 +18,8 @@ import {
* visible: boolean, * visible: boolean,
* message: string, * message: string,
* timeoutID: number, * timeoutID: number,
* queue: Array * queue: Array,
* notificationDisplayed: boolean
* }} * }}
*/ */
function _getInitialState() { function _getInitialState() {
@ -49,7 +51,12 @@ function _getInitialState() {
* *
* @type {Array} * @type {Array}
*/ */
queue: [] queue: [],
/**
* Whether or not the disable reaction sounds notification was shown
*/
notificationDisplayed: false
}; };
} }
@ -84,6 +91,13 @@ ReducerRegistry.register(
queue: action.value queue: action.value
}; };
} }
case SHOW_SOUNDS_NOTIFICATION: {
return {
...state,
notificationDisplayed: true
};
}
} }
return state; return state;

View File

@ -0,0 +1,48 @@
/**
* The name of the bundled audio files which will be played for the laugh reaction sound.
*
* @type {Array<string>}
*/
export const LAUGH_SOUND_FILES = [ 'reactions-laughter.mp3', 'reactions-laughter.mp3', 'reactions-laughter.mp3' ];
/**
* The name of the bundled audio file which will be played for the clap reaction sound.
*
* @type {Array<string>}
*/
export const CLAP_SOUND_FILES = [ 'reactionsapplause.mp3', 'reactionsapplause.mp3', 'reactionsapplause.mp3' ];
/**
* The name of the bundled audio file which will be played for the like reaction sound.
*
* @type {Array<string>}
*/
export const LIKE_SOUND_FILES = [ 'reactionsthumbs-up.mp3', 'reactionsthumbs-up.mp3', 'reactionsthumbs-up.mp3' ];
/**
* The name of the bundled audio file which will be played for the boo reaction sound.
*
* @type {Array<string>}
*/
export const BOO_SOUND_FILES = [ 'reactionsboo.mp3', 'reactionsboo.mp3', 'reactionsboo.mp3' ];
/**
* The name of the bundled audio file which will be played for the surprised reaction sound.
*
* @type {Array<string>}
*/
export const SURPRISE_SOUND_FILES = [ 'reactionssurprise.mp3', 'reactionssurprise.mp3', 'reactionssurprise.mp3' ];
/**
* The name of the bundled audio file which will be played for the silence reaction sound.
*
* @type {Array<string>}
*/
export const SILENCE_SOUND_FILES = [ 'reactionscrickets.mp3', 'reactionscrickets.mp3', 'reactionscrickets.mp3' ];
/**
* The name of the bundled audio file which will be played for the raise hand sound.
*
* @type {string}
*/
export const RAISE_HAND_SOUND_FILE = 'reactionsraised-hand.mp3';

View File

@ -142,14 +142,16 @@ export function submitSoundsTab(newState: Object): Function {
const shouldUpdate = (newState.soundsIncomingMessage !== currentState.soundsIncomingMessage) const shouldUpdate = (newState.soundsIncomingMessage !== currentState.soundsIncomingMessage)
|| (newState.soundsParticipantJoined !== currentState.soundsParticipantJoined) || (newState.soundsParticipantJoined !== currentState.soundsParticipantJoined)
|| (newState.soundsParticipantLeft !== currentState.soundsParticipantLeft) || (newState.soundsParticipantLeft !== currentState.soundsParticipantLeft)
|| (newState.soundsTalkWhileMuted !== currentState.soundsTalkWhileMuted); || (newState.soundsTalkWhileMuted !== currentState.soundsTalkWhileMuted)
|| (newState.soundsReactions !== currentState.soundsReactions);
if (shouldUpdate) { if (shouldUpdate) {
dispatch(updateSettings({ dispatch(updateSettings({
soundsIncomingMessage: newState.soundsIncomingMessage, soundsIncomingMessage: newState.soundsIncomingMessage,
soundsParticipantJoined: newState.soundsParticipantJoined, soundsParticipantJoined: newState.soundsParticipantJoined,
soundsParticipantLeft: newState.soundsParticipantLeft, soundsParticipantLeft: newState.soundsParticipantLeft,
soundsTalkWhileMuted: newState.soundsTalkWhileMuted soundsTalkWhileMuted: newState.soundsTalkWhileMuted,
soundsReactions: newState.soundsReactions
})); }));
} }
}; };

View File

@ -35,6 +35,16 @@ export type Props = {
*/ */
soundsTalkWhileMuted: Boolean, soundsTalkWhileMuted: Boolean,
/**
* Whether or not the sound for reactions should play.
*/
soundsReactions: Boolean,
/**
* Whether or not the reactions feature is enabled.
*/
enableReactions: Boolean,
/** /**
* Invoked to obtain translated strings. * Invoked to obtain translated strings.
*/ */
@ -85,6 +95,8 @@ class SoundsTab extends AbstractDialogTab<Props> {
soundsParticipantJoined, soundsParticipantJoined,
soundsParticipantLeft, soundsParticipantLeft,
soundsTalkWhileMuted, soundsTalkWhileMuted,
soundsReactions,
enableReactions,
t t
} = this.props; } = this.props;
@ -95,6 +107,12 @@ class SoundsTab extends AbstractDialogTab<Props> {
<h2 className = 'mock-atlaskit-label'> <h2 className = 'mock-atlaskit-label'>
{t('settings.playSounds')} {t('settings.playSounds')}
</h2> </h2>
{enableReactions && <Checkbox
isChecked = { soundsReactions }
label = { t('settings.reactions') }
name = 'soundsReactions'
onChange = { this._onChange } />
}
<Checkbox <Checkbox
isChecked = { soundsIncomingMessage } isChecked = { soundsIncomingMessage }
label = { t('settings.incomingMessage') } label = { t('settings.incomingMessage') }

View File

@ -171,14 +171,18 @@ export function getSoundsTabProps(stateful: Object | Function) {
soundsIncomingMessage, soundsIncomingMessage,
soundsParticipantJoined, soundsParticipantJoined,
soundsParticipantLeft, soundsParticipantLeft,
soundsTalkWhileMuted soundsTalkWhileMuted,
soundsReactions
} = state['features/base/settings']; } = state['features/base/settings'];
const { enableReactions } = state['features/base/config'];
return { return {
soundsIncomingMessage, soundsIncomingMessage,
soundsParticipantJoined, soundsParticipantJoined,
soundsParticipantLeft, soundsParticipantLeft,
soundsTalkWhileMuted soundsTalkWhileMuted,
soundsReactions,
enableReactions
}; };
} }

View File

@ -50,7 +50,7 @@ export type Props = {
* *
* @abstract * @abstract
*/ */
export default class AbstractToolbarButton<P: Props> extends Component<P> { export default class AbstractToolbarButton<P: Props, State=void> extends Component<P, State> {
/** /**
* Initializes a new {@code AbstractToolbarButton} instance. * Initializes a new {@code AbstractToolbarButton} instance.
* *

Binary file not shown.

Binary file not shown.

BIN
sounds/reactions–boo.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.