ref(reactions) Re-write using TypeScript (#11603)

This commit is contained in:
Robert Pintilii 2022-06-08 08:44:47 +01:00 committed by GitHub
parent 1bce1524db
commit 38724458e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 168 additions and 160 deletions

View File

@ -0,0 +1,4 @@
export interface IStore {
getState: Function,
dispatch: Function
}

View File

@ -1,5 +1,3 @@
// @flow
import {
ADD_REACTION_BUFFER,
ADD_REACTION_MESSAGE,
@ -8,18 +6,18 @@ import {
SEND_REACTIONS,
SET_REACTION_QUEUE
} from './actionTypes';
import { type ReactionEmojiProps } from './constants';
import { ReactionEmojiProps } from './constants';
import { ReactionsAction } from './reducer';
/**
* Sets the reaction queue.
*
* @param {Array} value - The new queue.
* @returns {Function}
* @param {Array} queue - The new queue.
*/
export function setReactionQueue(value: Array<ReactionEmojiProps>) {
export function setReactionQueue(queue: Array<ReactionEmojiProps>): ReactionsAction {
return {
type: SET_REACTION_QUEUE,
value
queue
};
}
@ -27,26 +25,21 @@ export function setReactionQueue(value: Array<ReactionEmojiProps>) {
/**
* Removes a reaction from the queue.
*
* @param {number} uid - Id of the reaction to be removed.
* @returns {void}
* @param {string} uid - Id of the reaction to be removed.
*/
export function removeReaction(uid: number) {
export function removeReaction(uid: string): Function {
return (dispatch: Function, getState: Function) => {
const queue = getState()['features/reactions'].queue;
dispatch(setReactionQueue(queue.filter(reaction => reaction.uid !== uid)));
dispatch(setReactionQueue(queue.filter((reaction: ReactionEmojiProps) => reaction.uid !== uid)));
};
}
/**
* Sends the reactions buffer to everyone in the conference.
*
* @returns {{
* type: SEND_REACTION
* }}
*/
export function sendReactions() {
export function sendReactions(): ReactionsAction {
return {
type: SEND_REACTIONS
};
@ -56,12 +49,8 @@ export function sendReactions() {
* Adds a reaction to the local buffer.
*
* @param {string} reaction - The reaction to be added.
* @returns {{
* type: ADD_REACTION_BUFFER,
* reaction: string
* }}
*/
export function addReactionToBuffer(reaction: string) {
export function addReactionToBuffer(reaction: string): ReactionsAction {
return {
type: ADD_REACTION_BUFFER,
reaction
@ -70,12 +59,8 @@ export function addReactionToBuffer(reaction: string) {
/**
* Clears the reaction buffer.
*
* @returns {{
* type: FLUSH_REACTION_BUFFER
* }}
*/
export function flushReactionBuffer() {
export function flushReactionBuffer(): ReactionsAction {
return {
type: FLUSH_REACTION_BUFFER
};
@ -85,12 +70,8 @@ export function flushReactionBuffer() {
* Adds a reaction message to the chat.
*
* @param {string} message - The reaction message.
* @returns {{
* type: ADD_REACTION_MESSAGE,
* message: string
* }}
*/
export function addReactionsToChat(message: string) {
export function addReactionsToChat(message: string): ReactionsAction {
return {
type: ADD_REACTION_MESSAGE,
message
@ -101,12 +82,8 @@ export function addReactionsToChat(message: string) {
* Adds reactions to the animation queue.
*
* @param {Array} reactions - The reactions to be animated.
* @returns {{
* type: PUSH_REACTIONS,
* reactions: Array
* }}
*/
export function pushReactions(reactions: Array<string>) {
export function pushReactions(reactions: Array<string>): ReactionsAction {
return {
type: PUSH_REACTIONS,
reactions

View File

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

View File

@ -1,5 +1,3 @@
// @flow
import { translate } from '../../../base/i18n';
import { IconRaisedHand } from '../../../base/icons';
import { getLocalParticipant, hasRaisedHand } from '../../../base/participants';
@ -15,7 +13,7 @@ type Props = AbstractButtonProps & {
/**
* Whether or not the hand is raised.
*/
raisedHand: boolean,
raisedHand: boolean,
};
/**

View File

@ -1,10 +1,10 @@
// @flow
import React, { Component } from 'react';
// @ts-ignore
import { connect } from '../../../base/redux';
import { removeReaction } from '../../actions.any';
import { REACTIONS } from '../../constants';
import { IStore } from '../../../app/types';
type Props = {
@ -16,12 +16,12 @@ type Props = {
/**
* Id of the reaction.
*/
uid: Number,
uid: string,
/**
* Removes reaction from redux state.
*/
removeReaction: Function,
reactionRemove: Function,
/**
* Index of the reaction in the queue.
@ -64,7 +64,7 @@ class ReactionEmoji extends Component<Props, State> {
* @inheritdoc
*/
componentDidMount() {
setTimeout(() => this.props.removeReaction(this.props.uid), 5000);
setTimeout(() => this.props.reactionRemove(this.props.uid), 5000);
}
/**
@ -86,11 +86,8 @@ class ReactionEmoji extends Component<Props, State> {
}
}
const mapDispatchToProps = {
removeReaction
};
const mapDispatchToProps = (dispatch: IStore['dispatch']) => ({
reactionRemove: (uid: string) => dispatch(removeReaction(uid))
});
export default connect(
null,
mapDispatchToProps
)(ReactionEmoji);
export default connect(null, mapDispatchToProps)(ReactionEmoji);

View File

@ -1,7 +1,3 @@
// @flow
/* eslint-disable react/jsx-no-bind */
import { withStyles } from '@material-ui/styles';
import clsx from 'clsx';
import React, { Component } from 'react';
@ -11,20 +7,34 @@ import {
createReactionMenuEvent,
createToolbarEvent,
sendAnalytics
// @ts-ignore
} from '../../../analytics';
import { IStore } from '../../../app/types';
// @ts-ignore
import { isMobileBrowser } from '../../../base/environment/utils';
// @ts-ignore
import { translate } from '../../../base/i18n';
// @ts-ignore
import { getLocalParticipant, hasRaisedHand, raiseHand } from '../../../base/participants';
// @ts-ignore
import { connect } from '../../../base/redux';
// @ts-ignore
import { GifsMenu, GifsMenuButton } from '../../../gifs/components';
// @ts-ignore
import { isGifEnabled, isGifsMenuOpen } from '../../../gifs/functions';
// @ts-ignore
import { dockToolbox } from '../../../toolbox/actions.web';
import { addReactionToBuffer } from '../../actions.any';
import { toggleReactionsMenuVisibility } from '../../actions.web';
import { REACTIONS, REACTIONS_MENU_HEIGHT } from '../../constants';
// @ts-ignore
import ReactionButton from './ReactionButton';
interface Classes {
overflow: string
}
type Props = {
/**
@ -60,7 +70,7 @@ type Props = {
/**
* An object containing the CSS classes.
*/
classes: Object,
classes: Classes,
/**
* The Redux Dispatch function.
@ -80,7 +90,7 @@ type Props = {
declare var APP: Object;
const styles = theme => {
const styles = (theme: any) => {
return {
overflow: {
width: 'auto',
@ -114,10 +124,6 @@ class ReactionsMenu extends Component<Props> {
this._getReactionButtons = this._getReactionButtons.bind(this);
}
_onToolbarToggleRaiseHand: () => void;
_getReactionButtons: () => Array<React$Element<*>>;
/**
* Implements React Component's componentDidMount.
*
@ -190,6 +196,7 @@ class ReactionsMenu extends Component<Props> {
sendAnalytics(createReactionMenuEvent(key));
}
// @ts-ignore
return (<ReactionButton
accessibilityLabel = { t(`toolbar.accessibilityLabel.${key}`) }
icon = { REACTIONS[key].emoji }
@ -219,6 +226,7 @@ class ReactionsMenu extends Component<Props> {
</div>
{_isMobile && (
<div className = 'raise-hand-row'>
{/* @ts-ignore */}
<ReactionButton
accessibilityLabel = { t('toolbar.accessibilityLabel.raiseHand') }
icon = '✋'
@ -242,7 +250,7 @@ class ReactionsMenu extends Component<Props> {
* @param {Object} state - Redux state.
* @returns {Object}
*/
function mapStateToProps(state) {
function mapStateToProps(state: any) {
const localParticipant = getLocalParticipant(state);
return {
@ -260,12 +268,13 @@ function mapStateToProps(state) {
* @param {Object} dispatch - Redux dispatch.
* @returns {Object}
*/
function mapDispatchToProps(dispatch) {
function mapDispatchToProps(dispatch: IStore['dispatch']) {
return {
dispatch,
...bindActionCreators(
{
_dockToolbox: dockToolbox
// @ts-ignore
}, dispatch)
};
}
@ -273,4 +282,5 @@ function mapDispatchToProps(dispatch) {
export default translate(connect(
mapStateToProps,
mapDispatchToProps
// @ts-ignore
)(withStyles(styles)(ReactionsMenu)));

View File

@ -1,18 +1,25 @@
// @flow
import React, { useCallback } from 'react';
// @ts-ignore
import { useSelector } from 'react-redux';
// @ts-ignore
import { isMobileBrowser } from '../../../base/environment/utils';
// @ts-ignore
import { translate } from '../../../base/i18n';
// @ts-ignore
import { IconArrowUp } from '../../../base/icons';
// @ts-ignore
import { connect } from '../../../base/redux';
// @ts-ignore
import ToolboxButtonWithIconPopup from '../../../base/toolbox/components/web/ToolboxButtonWithIconPopup';
import { toggleReactionsMenuVisibility } from '../../actions.web';
import { type ReactionEmojiProps } from '../../constants';
import { ReactionEmojiProps } from '../../constants';
import { getReactionsQueue, isReactionsEnabled } from '../../functions.any';
import { getReactionsMenuVisibility } from '../../functions.web';
// @ts-ignore
import RaiseHandButton from './RaiseHandButton';
import ReactionEmoji from './ReactionEmoji';
import ReactionsMenu from './ReactionsMenu';
@ -111,11 +118,9 @@ function ReactionsMenuButton({
ariaExpanded = { isOpen }
ariaHasPopup = { true }
ariaLabel = { t('toolbar.accessibilityLabel.reactionsMenu') }
buttonKey = { buttonKey }
icon = { IconArrowUp }
iconDisabled = { false }
iconId = 'reactions-menu-button'
notifyMode = { notifyMode }
onPopoverClose = { toggleReactionsMenu }
onPopoverOpen = { openReactionsMenu }
popoverContent = { reactionsMenu }
@ -141,7 +146,7 @@ function ReactionsMenuButton({
* @param {Object} state - Redux state.
* @returns {Object}
*/
function mapStateToProps(state) {
function mapStateToProps(state: any) {
return {
_reactionsEnabled: isReactionsEnabled(state),
isOpen: getReactionsMenuVisibility(state),

View File

@ -1,5 +1,3 @@
// @flow
import {
CLAP_SOUND_FILES,
LAUGH_SOUND_FILES,
@ -87,7 +85,7 @@ export const SILENCE_SOUND_ID = `${REACTION_SOUND}_SILENCE_`;
*/
export const RAISE_HAND_SOUND_ID = 'RAISE_HAND_SOUND';
export type ReactionEmojiProps = {
export interface ReactionEmojiProps {
/**
* Reaction to be displayed.
@ -97,13 +95,22 @@ export type ReactionEmojiProps = {
/**
* Id of the reaction.
*/
uid: number
uid: string
}
export const SOUNDS_THRESHOLDS = [ 1, 4, 10 ];
interface IReactions {
[key: string]: {
message: string;
emoji: string;
shortcutChar: string;
soundId: string;
soundFiles: string[];
}
}
export const REACTIONS = {
export const REACTIONS: IReactions = {
like: {
message: ':thumbs_up:',
emoji: '👍',
@ -147,3 +154,12 @@ export const REACTIONS = {
soundFiles: SILENCE_SOUND_FILES
}
};
export type ReactionThreshold = {
reaction: string,
threshold: number
}
export interface MuteCommandAttributes {
startReactionsMuted?: string;
}

View File

@ -1,21 +1,21 @@
// @flow
import { v4 as uuidv4 } from 'uuid';
// @ts-ignore
import { getFeatureFlag, REACTIONS_ENABLED } from '../base/flags';
// @ts-ignore
import { getLocalParticipant } from '../base/participants';
// @ts-ignore
import { extractFqnFromPath } from '../dynamic-branding/functions.any';
import { REACTIONS, SOUNDS_THRESHOLDS } from './constants';
import { ReactionEmojiProps, REACTIONS, ReactionThreshold, SOUNDS_THRESHOLDS } from './constants';
import logger from './logger';
/**
* Returns the queue of reactions.
*
* @param {Object} state - The state of the application.
* @returns {boolean}
*/
export function getReactionsQueue(state: Object) {
export function getReactionsQueue(state: any): Array<ReactionEmojiProps> {
return state['features/reactions'].queue;
}
@ -23,20 +23,18 @@ export function getReactionsQueue(state: Object) {
* Returns chat message from reactions buffer.
*
* @param {Array} buffer - The reactions buffer.
* @returns {string}
*/
export function getReactionMessageFromBuffer(buffer: Array<string>) {
return buffer.map(reaction => REACTIONS[reaction].message).reduce((acc, val) => `${acc}${val}`);
export function getReactionMessageFromBuffer(buffer: Array<string>): string {
return buffer.map<string>(reaction => REACTIONS[reaction].message).reduce((acc, val) => `${acc}${val}`);
}
/**
* Returns reactions array with uid.
*
* @param {Array} buffer - The reactions buffer.
* @returns {Array}
*/
export function getReactionsWithId(buffer: Array<string>) {
return buffer.map<Object>(reaction => {
export function getReactionsWithId(buffer: Array<string>): Array<ReactionEmojiProps> {
return buffer.map<ReactionEmojiProps>(reaction => {
return {
reaction,
uid: uuidv4()
@ -49,9 +47,8 @@ export function getReactionsWithId(buffer: Array<string>) {
*
* @param {Object} state - The redux state object.
* @param {Array} reactions - Reactions array to be sent.
* @returns {void}
*/
export async function sendReactionsWebhook(state: Object, reactions: Array<?string>) {
export async function sendReactionsWebhook(state: any, reactions: Array<string>) {
const { webhookProxyUrl: url } = state['features/base/config'];
const { conference } = state['features/base/conference'];
const { jwt } = state['features/base/jwt'];
@ -96,9 +93,8 @@ 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>) {
function getUniqueReactions(reactions: Array<string>): Array<string> {
return [ ...new Set(reactions) ];
}
@ -107,9 +103,8 @@ function getUniqueReactions(reactions: Array<string>) {
*
* @param {Array} reactions - Array of reactions.
* @param {string} reaction - Reaction to get frequency for.
* @returns {number}
*/
function getReactionFrequency(reactions: Array<string>, reaction: string) {
function getReactionFrequency(reactions: Array<string>, reaction: string): number {
return reactions.filter(r => r === reaction).length;
}
@ -117,9 +112,8 @@ function getReactionFrequency(reactions: Array<string>, reaction: string) {
* Returns the threshold number for a given frequency.
*
* @param {number} frequency - Frequency of reaction.
* @returns {number}
*/
function getSoundThresholdByFrequency(frequency) {
function getSoundThresholdByFrequency(frequency: number): number {
for (const i of SOUNDS_THRESHOLDS) {
if (frequency <= i) {
return i;
@ -133,12 +127,11 @@ function getSoundThresholdByFrequency(frequency) {
* Returns unique reactions with threshold.
*
* @param {Array} reactions - The reactions buffer.
* @returns {Array}
*/
export function getReactionsSoundsThresholds(reactions: Array<string>) {
export function getReactionsSoundsThresholds(reactions: Array<string>): Array<ReactionThreshold> {
const unique = getUniqueReactions(reactions);
return unique.map<Object>(reaction => {
return unique.map<ReactionThreshold>(reaction => {
return {
reaction,
threshold: getSoundThresholdByFrequency(getReactionFrequency(reactions, reaction))
@ -150,9 +143,8 @@ export function getReactionsSoundsThresholds(reactions: Array<string>) {
* Whether or not the reactions are enabled.
*
* @param {Object} state - The Redux state object.
* @returns {boolean}
*/
export function isReactionsEnabled(state: Object) {
export function isReactionsEnabled(state: any): boolean {
const { disableReactions } = state['features/base/config'];
if (navigator.product === 'ReactNative') {

View File

@ -1,11 +1,8 @@
// @flow
/**
* Returns the visibility state of the reactions menu.
*
* @param {Object} state - The state of the application.
* @returns {boolean}
*/
export function getReactionsMenuVisibility(state: Object) {
export function getReactionsMenuVisibility(state: any): boolean {
return state['features/reactions'].visible;
}

View File

@ -1,5 +1,4 @@
// @flow
// @ts-ignore
import { getLogger } from '../base/logging/functions';
export default getLogger('features/base/reactions');

View File

@ -1,24 +1,31 @@
// @flow
// @ts-ignore
import { batch } from 'react-redux';
// @ts-ignore
import { createReactionSoundsDisabledEvent, sendAnalytics } from '../analytics';
// @ts-ignore
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../base/app';
import {
CONFERENCE_JOIN_IN_PROGRESS,
SET_START_REACTIONS_MUTED,
setStartReactionsMuted
// @ts-ignore
} from '../base/conference';
import {
getParticipantById,
getParticipantCount,
isLocalParticipantModerator
// @ts-ignore
} from '../base/participants';
// @ts-ignore
import { MiddlewareRegistry } from '../base/redux';
import { SETTINGS_UPDATED } from '../base/settings/actionTypes';
// @ts-ignore
import { updateSettings } from '../base/settings/actions';
// @ts-ignore
import { playSound, registerSound, unregisterSound } from '../base/sounds';
// @ts-ignore
import { getDisabledSounds } from '../base/sounds/functions.any';
// @ts-ignore
import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../notifications';
import {
@ -28,7 +35,6 @@ import {
PUSH_REACTIONS,
SHOW_SOUNDS_NOTIFICATION
} from './actionTypes';
import { displayReactionSoundsNotification } from './actions';
import {
addReactionsToChat,
flushReactionBuffer,
@ -36,13 +42,15 @@ import {
sendReactions,
setReactionQueue
} from './actions.any';
import { displayReactionSoundsNotification } from './actions.web';
import {
ENDPOINT_REACTION_NAME,
RAISE_HAND_SOUND_ID,
REACTIONS,
REACTION_SOUND,
SOUNDS_THRESHOLDS,
MUTE_REACTIONS_COMMAND
MUTE_REACTIONS_COMMAND,
MuteCommandAttributes
} from './constants';
import {
getReactionMessageFromBuffer,
@ -52,15 +60,16 @@ import {
} from './functions.any';
import logger from './logger';
import { RAISE_HAND_SOUND_FILE } from './sounds';
import { IStore } from '../app/types';
/**
* Middleware which intercepts Reactions actions to handle changes to the
* visibility timeout of the Reactions.
*
* @param {Store} store - The redux store.
* @param {IStore} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
MiddlewareRegistry.register((store: IStore) => (next: Function) => (action:any) => {
const { dispatch, getState } = store;
switch (action.type) {
@ -108,7 +117,7 @@ MiddlewareRegistry.register(store => next => action => {
const { conference } = action;
conference.addCommandListener(
MUTE_REACTIONS_COMMAND, ({ attributes }, id) => {
MUTE_REACTIONS_COMMAND, ({ attributes }: { attributes: MuteCommandAttributes }, id: any) => {
_onMuteReactionsCommand(attributes, id, store);
});
break;
@ -232,9 +241,8 @@ MiddlewareRegistry.register(store => next => action => {
* @param {Object} store - The redux store. Used to calculate and dispatch
* updates.
* @private
* @returns {void}
*/
function _onMuteReactionsCommand(attributes = {}, id, store) {
function _onMuteReactionsCommand(attributes: MuteCommandAttributes = {}, id: string, store: IStore) {
const state = store.getState();
// We require to know who issued the command because (1) only a
@ -259,6 +267,7 @@ function _onMuteReactionsCommand(attributes = {}, id, store) {
}
const oldState = Boolean(state['features/base/conference'].startReactionsMuted);
// @ts-ignore
const newState = attributes.startReactionsMuted === 'true';
if (oldState !== newState) {

View File

@ -1,5 +1,4 @@
// @flow
// @ts-ignore
import { ReducerRegistry } from '../base/redux';
import {
@ -9,60 +8,70 @@ import {
FLUSH_REACTION_BUFFER,
SHOW_SOUNDS_NOTIFICATION
} from './actionTypes';
import { ReactionEmojiProps } from './constants';
interface State {
/**
* The indicator that determines whether the reactions menu is visible.
*/
visible: boolean,
/**
* An array that contains the reactions buffer to be sent.
*/
buffer: Array<string>,
/**
* A number, non-zero value which identifies the timer created by a call
* to setTimeout().
*/
timeoutID: number|null,
/**
* The array of reactions to animate.
*/
queue: Array<ReactionEmojiProps>,
/**
* Whether or not the disable reaction sounds notification was shown.
*/
notificationDisplayed: boolean
}
export interface ReactionsAction extends Partial<State> {
/**
* The message to be added to the chat.
*/
message?: string,
/**
* The reaction to be added to buffer.
*/
reaction?: string,
/**
* The reactions to be added to the animation queue.
*/
reactions?: Array<string>,
/**
* The action type.
*/
type: string
}
/**
* Returns initial state for reactions' part of Redux store.
*
* @private
* @returns {{
* visible: boolean,
* message: string,
* timeoutID: number,
* queue: Array,
* notificationDisplayed: boolean
* }}
*/
function _getInitialState() {
function _getInitialState(): State {
return {
/**
* The indicator that determines whether the reactions menu is visible.
*
* @type {boolean}
*/
visible: false,
/**
* An array that contains the reactions buffer to be sent.
*
* @type {Array}
*/
buffer: [],
/**
* A number, non-zero value which identifies the timer created by a call
* to setTimeout().
*
* @type {number|null}
*/
timeoutID: null,
/**
* The array of reactions to animate.
*
* @type {Array}
*/
queue: [],
/**
* Whether or not the disable reaction sounds notification was shown.
*/
notificationDisplayed: false
};
}
ReducerRegistry.register(
'features/reactions',
(state: Object = _getInitialState(), action: Object) => {
(state: State = _getInitialState(), action: ReactionsAction) => {
switch (action.type) {
case TOGGLE_REACTIONS_VISIBLE:
@ -88,7 +97,7 @@ ReducerRegistry.register(
case SET_REACTION_QUEUE: {
return {
...state,
queue: action.value
queue: action.queue
};
}