mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2025-12-30 11:22:31 +00:00
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:
@@ -48,6 +48,13 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: font-size ease .1s;
|
||||
|
||||
@for $i from 1 through 12 {
|
||||
&.increase-#{$i}{
|
||||
font-size: calc(20px + #{$i}px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -591,6 +591,7 @@
|
||||
"moderationStoppedTitle": "Moderation stopped",
|
||||
"moderationToggleDescription": "by {{participantDisplayName}}",
|
||||
"raiseHandAction": "Raise hand",
|
||||
"reactionSounds": "Disable sounds",
|
||||
"groupTitle": "Notifications"
|
||||
},
|
||||
"participantsPane": {
|
||||
@@ -794,6 +795,7 @@
|
||||
"participantJoined": "Participant Joined",
|
||||
"participantLeft": "Participant Left",
|
||||
"playSounds": "Play sound on",
|
||||
"reactions": "Meeting reactions",
|
||||
"sameAsSystem": "Same as system ({{label}})",
|
||||
"selectAudioOutput": "Audio output",
|
||||
"selectCamera": "Camera",
|
||||
@@ -884,7 +886,6 @@
|
||||
"muteEveryonesVideo": "Disable everyone's camera",
|
||||
"muteEveryoneElsesVideo": "Disable everyone else's camera",
|
||||
"participants": "Participants",
|
||||
"party": "Party Popper",
|
||||
"pip": "Toggle Picture-in-Picture mode",
|
||||
"privateMessage": "Send private message",
|
||||
"profile": "Edit your profile",
|
||||
@@ -901,6 +902,7 @@
|
||||
"shareYourScreen": "Start / Stop sharing your screen",
|
||||
"shortcuts": "Toggle shortcuts",
|
||||
"show": "Show on stage",
|
||||
"silence": "Silence",
|
||||
"speakerStats": "Toggle speaker statistics",
|
||||
"surprised": "Surprised",
|
||||
"tileView": "Toggle tile view",
|
||||
@@ -925,6 +927,7 @@
|
||||
"clap": "Clap",
|
||||
"closeChat": "Close chat",
|
||||
"closeReactionsMenu": "Close reactions menu",
|
||||
"disableReactionSounds": "You can disable reaction sounds for this meeting",
|
||||
"documentClose": "Close shared document",
|
||||
"documentOpen": "Open shared document",
|
||||
"download": "Download our apps",
|
||||
@@ -960,7 +963,6 @@
|
||||
"openChat": "Open chat",
|
||||
"openReactionsMenu": "Open reactions menu",
|
||||
"participants": "Participants",
|
||||
"party": "Celebration",
|
||||
"pip": "Enter Picture-in-Picture mode",
|
||||
"privateMessage": "Send private message",
|
||||
"profile": "Edit your profile",
|
||||
@@ -970,7 +972,7 @@
|
||||
"reactionClap": "Send clap reaction",
|
||||
"reactionLaugh": "Send laugh reaction",
|
||||
"reactionLike": "Send thumbs up reaction",
|
||||
"reactionParty": "Send party popper reaction",
|
||||
"reactionSilence": "Send silence reaction",
|
||||
"reactionSurprised": "Send surprised reaction",
|
||||
"security": "Security options",
|
||||
"Settings": "Settings",
|
||||
@@ -978,6 +980,7 @@
|
||||
"sharedvideo": "Share video",
|
||||
"shareRoom": "Invite someone",
|
||||
"shortcuts": "View shortcuts",
|
||||
"silence": "Silence",
|
||||
"speakerStats": "Speaker stats",
|
||||
"startScreenSharing": "Start screen sharing",
|
||||
"startSubtitles": "Start subtitles",
|
||||
|
||||
@@ -31,6 +31,7 @@ const DEFAULT_STATE = {
|
||||
soundsParticipantJoined: true,
|
||||
soundsParticipantLeft: true,
|
||||
soundsTalkWhileMuted: true,
|
||||
soundsReactions: true,
|
||||
startAudioOnly: false,
|
||||
startWithAudioMuted: false,
|
||||
startWithVideoMuted: false,
|
||||
|
||||
@@ -58,3 +58,8 @@ export const SEND_REACTIONS = 'SEND_REACTIONS';
|
||||
* The type of action to adds reactions to the queue.
|
||||
*/
|
||||
export const PUSH_REACTIONS = 'PUSH_REACTIONS';
|
||||
|
||||
/**
|
||||
* The type of action to display disable notification sounds.
|
||||
*/
|
||||
export const SHOW_SOUNDS_NOTIFICATION = 'SHOW_SOUNDS_NOTIFICATION';
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
// @flow
|
||||
|
||||
import {
|
||||
SHOW_SOUNDS_NOTIFICATION,
|
||||
TOGGLE_REACTIONS_VISIBLE
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the reactions menu.
|
||||
*
|
||||
* @returns {Function}
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function toggleReactionsMenuVisibility() {
|
||||
return {
|
||||
type: TOGGLE_REACTIONS_VISIBLE
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the disable sounds notification.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function displayReactionSoundsNotification() {
|
||||
return {
|
||||
type: SHOW_SOUNDS_NOTIFICATION
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,12 +28,28 @@ type Props = AbstractToolbarButtonProps & {
|
||||
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.
|
||||
*
|
||||
* @extends AbstractToolbarButton
|
||||
*/
|
||||
class ReactionButton extends AbstractToolbarButton<Props> {
|
||||
class ReactionButton extends AbstractToolbarButton<Props, State> {
|
||||
/**
|
||||
* Default values for {@code ReactionButton} component's properties.
|
||||
*
|
||||
@@ -52,10 +68,18 @@ class ReactionButton extends AbstractToolbarButton<Props> {
|
||||
super(props);
|
||||
|
||||
this._onKeyDown = this._onKeyDown.bind(this);
|
||||
this._onClickHandler = this._onClickHandler.bind(this);
|
||||
|
||||
this.state = {
|
||||
increaseLevel: 0,
|
||||
increaseTimeout: null
|
||||
};
|
||||
}
|
||||
|
||||
_onKeyDown: (Object) => void;
|
||||
|
||||
_onClickHandler: () => void;
|
||||
|
||||
/**
|
||||
* Handles 'Enter' key on the button to trigger onClick for accessibility.
|
||||
* 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}.
|
||||
*
|
||||
@@ -92,7 +138,7 @@ class ReactionButton extends AbstractToolbarButton<Props> {
|
||||
aria-label = { this.props.accessibilityLabel }
|
||||
aria-pressed = { this.props.toggled }
|
||||
className = 'toolbox-button'
|
||||
onClick = { this.props.onClick }
|
||||
onClick = { this._onClickHandler }
|
||||
onKeyDown = { this._onKeyDown }
|
||||
role = 'button'
|
||||
tabIndex = { 0 }>
|
||||
@@ -113,10 +159,13 @@ class ReactionButton extends AbstractToolbarButton<Props> {
|
||||
* @inheritdoc
|
||||
*/
|
||||
_renderIcon() {
|
||||
const { toggled, icon, label } = this.props;
|
||||
const { increaseLevel } = this.state;
|
||||
|
||||
return (
|
||||
<div className = { `toolbox-icon ${this.props.toggled ? 'toggled' : ''}` }>
|
||||
<span className = 'emoji'>{this.props.icon}</span>
|
||||
{this.props.label && <span className = 'text'>{this.props.label}</span>}
|
||||
<div className = { `toolbox-icon ${toggled ? 'toggled' : ''}` }>
|
||||
<span className = { `emoji increase-${increaseLevel > 12 ? 12 : increaseLevel}` }>{icon}</span>
|
||||
{label && <span className = 'text'>{label}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,11 @@ import {
|
||||
import { translate } from '../../../base/i18n';
|
||||
import { getLocalParticipant, getParticipantCount, participantUpdated } from '../../../base/participants';
|
||||
import { connect } from '../../../base/redux';
|
||||
import { playSound } from '../../../base/sounds';
|
||||
import { dockToolbox } from '../../../toolbox/actions.web';
|
||||
import { addReactionToBuffer } from '../../actions.any';
|
||||
import { toggleReactionsMenuVisibility } from '../../actions.web';
|
||||
import { REACTIONS } from '../../constants';
|
||||
import { RAISE_HAND_SOUND_ID, REACTIONS } from '../../constants';
|
||||
|
||||
import ReactionButton from './ReactionButton';
|
||||
|
||||
@@ -53,7 +54,12 @@ type Props = {
|
||||
/**
|
||||
* 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;
|
||||
@@ -106,11 +112,16 @@ class ReactionsMenu extends Component<Props> {
|
||||
* @returns {void}
|
||||
*/
|
||||
_onToolbarToggleRaiseHand() {
|
||||
const { dispatch, _raisedHand, _reactionSounds } = this.props;
|
||||
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'raise.hand',
|
||||
{ enable: !this.props._raisedHand }));
|
||||
{ enable: !_raisedHand }));
|
||||
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) {
|
||||
const localParticipant = getLocalParticipant(state);
|
||||
const { soundsReactions } = state['features/base/settings'];
|
||||
|
||||
return {
|
||||
_localParticipantID: localParticipant.id,
|
||||
_raisedHand: localParticipant.raisedHand,
|
||||
_participantCount: getParticipantCount(state)
|
||||
_participantCount: getParticipantCount(state),
|
||||
_reactionSounds: soundsReactions
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,37 +1,69 @@
|
||||
// @flow
|
||||
|
||||
export const REACTIONS = {
|
||||
like: {
|
||||
message: ':thumbs_up:',
|
||||
emoji: '👍',
|
||||
shortcutChar: 'T'
|
||||
},
|
||||
clap: {
|
||||
message: ':clap:',
|
||||
emoji: '👏',
|
||||
shortcutChar: 'C'
|
||||
},
|
||||
laugh: {
|
||||
message: ':grinning_face:',
|
||||
emoji: '😀',
|
||||
shortcutChar: 'L'
|
||||
},
|
||||
surprised: {
|
||||
message: ':face_with_open_mouth:',
|
||||
emoji: '😮',
|
||||
shortcutChar: 'O'
|
||||
},
|
||||
boo: {
|
||||
message: ':slightly_frowning_face:',
|
||||
emoji: '🙁',
|
||||
shortcutChar: 'B'
|
||||
},
|
||||
party: {
|
||||
message: ':party_popper:',
|
||||
emoji: '🎉',
|
||||
shortcutChar: 'P'
|
||||
}
|
||||
};
|
||||
import {
|
||||
CLAP_SOUND_FILES,
|
||||
LAUGH_SOUND_FILES,
|
||||
LIKE_SOUND_FILES,
|
||||
BOO_SOUND_FILES,
|
||||
SURPRISE_SOUND_FILES,
|
||||
SILENCE_SOUND_FILES
|
||||
} from './sounds';
|
||||
|
||||
/**
|
||||
* The audio ID prefix of the audio element for which the {@link playAudio} action is
|
||||
* triggered when a new laugh reaction is received.
|
||||
*
|
||||
* @type { string }
|
||||
*/
|
||||
export const LAUGH_SOUND_ID = 'LAUGH_SOUND_';
|
||||
|
||||
/**
|
||||
* The audio ID prefix of the audio element for which the {@link playAudio} action is
|
||||
* triggered when a new clap reaction is received.
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
export const CLAP_SOUND_ID = 'CLAP_SOUND_';
|
||||
|
||||
/**
|
||||
* The audio ID prefix of the audio element for which the {@link playAudio} action is
|
||||
* triggered when a new like reaction is received.
|
||||
*
|
||||
* @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 = {
|
||||
|
||||
@@ -45,3 +77,51 @@ export type ReactionEmojiProps = {
|
||||
*/
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import uuid from 'uuid';
|
||||
import { getLocalParticipant } from '../base/participants';
|
||||
import { extractFqnFromPath } from '../dynamic-branding/functions';
|
||||
|
||||
import { REACTIONS } from './constants';
|
||||
import { REACTIONS, SOUNDS_THRESHOLDS } from './constants';
|
||||
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))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,14 +3,19 @@
|
||||
import { batch } from 'react-redux';
|
||||
|
||||
import { ENDPOINT_REACTION_NAME } from '../../../modules/API/constants';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
|
||||
import { MiddlewareRegistry } from '../base/redux';
|
||||
import { updateSettings } from '../base/settings';
|
||||
import { playSound, registerSound, unregisterSound } from '../base/sounds';
|
||||
import { isVpaasMeeting } from '../jaas/functions';
|
||||
import { NOTIFICATION_TIMEOUT, showNotification } from '../notifications';
|
||||
|
||||
import {
|
||||
ADD_REACTION_BUFFER,
|
||||
FLUSH_REACTION_BUFFER,
|
||||
SEND_REACTIONS,
|
||||
PUSH_REACTIONS
|
||||
PUSH_REACTIONS,
|
||||
SHOW_SOUNDS_NOTIFICATION
|
||||
} from './actionTypes';
|
||||
import {
|
||||
addReactionsToChat,
|
||||
@@ -19,7 +24,15 @@ import {
|
||||
sendReactions,
|
||||
setReactionQueue
|
||||
} 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;
|
||||
@@ -35,6 +48,33 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
const { dispatch, getState } = store;
|
||||
|
||||
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: {
|
||||
const { timeoutID, buffer } = getState()['features/reactions'];
|
||||
const { reaction } = action;
|
||||
@@ -82,10 +122,36 @@ MiddlewareRegistry.register(store => next => action => {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
TOGGLE_REACTIONS_VISIBLE,
|
||||
SET_REACTION_QUEUE,
|
||||
ADD_REACTION_BUFFER,
|
||||
FLUSH_REACTION_BUFFER
|
||||
FLUSH_REACTION_BUFFER,
|
||||
SHOW_SOUNDS_NOTIFICATION
|
||||
} from './actionTypes';
|
||||
|
||||
/**
|
||||
@@ -17,7 +18,8 @@ import {
|
||||
* visible: boolean,
|
||||
* message: string,
|
||||
* timeoutID: number,
|
||||
* queue: Array
|
||||
* queue: Array,
|
||||
* notificationDisplayed: boolean
|
||||
* }}
|
||||
*/
|
||||
function _getInitialState() {
|
||||
@@ -49,7 +51,12 @@ function _getInitialState() {
|
||||
*
|
||||
* @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
|
||||
};
|
||||
}
|
||||
|
||||
case SHOW_SOUNDS_NOTIFICATION: {
|
||||
return {
|
||||
...state,
|
||||
notificationDisplayed: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
|
||||
48
react/features/reactions/sounds.js
Normal file
48
react/features/reactions/sounds.js
Normal 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 = [ 'reactions–applause.mp3', 'reactions–applause.mp3', 'reactions–applause.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 = [ 'reactions–thumbs-up.mp3', 'reactions–thumbs-up.mp3', 'reactions–thumbs-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 = [ 'reactions–boo.mp3', 'reactions–boo.mp3', 'reactions–boo.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 = [ 'reactions–surprise.mp3', 'reactions–surprise.mp3', 'reactions–surprise.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 = [ 'reactions–crickets.mp3', 'reactions–crickets.mp3', 'reactions–crickets.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 = 'reactions–raised-hand.mp3';
|
||||
@@ -142,14 +142,16 @@ export function submitSoundsTab(newState: Object): Function {
|
||||
const shouldUpdate = (newState.soundsIncomingMessage !== currentState.soundsIncomingMessage)
|
||||
|| (newState.soundsParticipantJoined !== currentState.soundsParticipantJoined)
|
||||
|| (newState.soundsParticipantLeft !== currentState.soundsParticipantLeft)
|
||||
|| (newState.soundsTalkWhileMuted !== currentState.soundsTalkWhileMuted);
|
||||
|| (newState.soundsTalkWhileMuted !== currentState.soundsTalkWhileMuted)
|
||||
|| (newState.soundsReactions !== currentState.soundsReactions);
|
||||
|
||||
if (shouldUpdate) {
|
||||
dispatch(updateSettings({
|
||||
soundsIncomingMessage: newState.soundsIncomingMessage,
|
||||
soundsParticipantJoined: newState.soundsParticipantJoined,
|
||||
soundsParticipantLeft: newState.soundsParticipantLeft,
|
||||
soundsTalkWhileMuted: newState.soundsTalkWhileMuted
|
||||
soundsTalkWhileMuted: newState.soundsTalkWhileMuted,
|
||||
soundsReactions: newState.soundsReactions
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,6 +35,16 @@ export type Props = {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -85,6 +95,8 @@ class SoundsTab extends AbstractDialogTab<Props> {
|
||||
soundsParticipantJoined,
|
||||
soundsParticipantLeft,
|
||||
soundsTalkWhileMuted,
|
||||
soundsReactions,
|
||||
enableReactions,
|
||||
t
|
||||
} = this.props;
|
||||
|
||||
@@ -95,6 +107,12 @@ class SoundsTab extends AbstractDialogTab<Props> {
|
||||
<h2 className = 'mock-atlaskit-label'>
|
||||
{t('settings.playSounds')}
|
||||
</h2>
|
||||
{enableReactions && <Checkbox
|
||||
isChecked = { soundsReactions }
|
||||
label = { t('settings.reactions') }
|
||||
name = 'soundsReactions'
|
||||
onChange = { this._onChange } />
|
||||
}
|
||||
<Checkbox
|
||||
isChecked = { soundsIncomingMessage }
|
||||
label = { t('settings.incomingMessage') }
|
||||
|
||||
@@ -171,14 +171,18 @@ export function getSoundsTabProps(stateful: Object | Function) {
|
||||
soundsIncomingMessage,
|
||||
soundsParticipantJoined,
|
||||
soundsParticipantLeft,
|
||||
soundsTalkWhileMuted
|
||||
soundsTalkWhileMuted,
|
||||
soundsReactions
|
||||
} = state['features/base/settings'];
|
||||
const { enableReactions } = state['features/base/config'];
|
||||
|
||||
return {
|
||||
soundsIncomingMessage,
|
||||
soundsParticipantJoined,
|
||||
soundsParticipantLeft,
|
||||
soundsTalkWhileMuted
|
||||
soundsTalkWhileMuted,
|
||||
soundsReactions,
|
||||
enableReactions
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export type Props = {
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
|
||||
BIN
sounds/reactions-laughter.mp3
Normal file
BIN
sounds/reactions-laughter.mp3
Normal file
Binary file not shown.
BIN
sounds/reactions–applause.mp3
Normal file
BIN
sounds/reactions–applause.mp3
Normal file
Binary file not shown.
BIN
sounds/reactions–boo.mp3
Normal file
BIN
sounds/reactions–boo.mp3
Normal file
Binary file not shown.
BIN
sounds/reactions–crickets.mp3
Normal file
BIN
sounds/reactions–crickets.mp3
Normal file
Binary file not shown.
BIN
sounds/reactions–raised-hand.mp3
Normal file
BIN
sounds/reactions–raised-hand.mp3
Normal file
Binary file not shown.
BIN
sounds/reactions–surprise.mp3
Normal file
BIN
sounds/reactions–surprise.mp3
Normal file
Binary file not shown.
BIN
sounds/reactions–thumbs-up.mp3
Normal file
BIN
sounds/reactions–thumbs-up.mp3
Normal file
Binary file not shown.
Reference in New Issue
Block a user