diff --git a/css/modals/speaker_stats/_speaker_stats.scss b/css/modals/speaker_stats/_speaker_stats.scss index b137c1b8f..2b0ee22f7 100644 --- a/css/modals/speaker_stats/_speaker_stats.scss +++ b/css/modals/speaker_stats/_speaker_stats.scss @@ -48,7 +48,7 @@ width: 20%; } .speaker-stats-item__time_expressions_on { - width: 25%; + width: 18%; } .speaker-stats-item__expression { diff --git a/react/features/speaker-stats/actionTypes.js b/react/features/speaker-stats/actionTypes.js index f61549bb1..75aca2f7f 100644 --- a/react/features/speaker-stats/actionTypes.js +++ b/react/features/speaker-stats/actionTypes.js @@ -47,3 +47,11 @@ export const INIT_REORDER_STATS = 'INIT_REORDER_STATS'; */ export const RESET_SEARCH_CRITERIA = 'RESET_SEARCH_CRITERIA' +/** + * Action type to toggle the facial expressions grid. + * { + * type: TOGGLE_FACIAL_EXPRESSIONS + * } + */ +export const TOGGLE_FACIAL_EXPRESSIONS = 'SHOW_FACIAL_EXPRESSIONS'; + diff --git a/react/features/speaker-stats/actions.any.js b/react/features/speaker-stats/actions.any.js index a0be70b15..205af9303 100644 --- a/react/features/speaker-stats/actions.any.js +++ b/react/features/speaker-stats/actions.any.js @@ -5,7 +5,8 @@ import { INIT_UPDATE_STATS, UPDATE_STATS, INIT_REORDER_STATS, - RESET_SEARCH_CRITERIA + RESET_SEARCH_CRITERIA, + TOGGLE_FACIAL_EXPRESSIONS } from './actionTypes'; /** @@ -68,3 +69,14 @@ export function resetSearchCriteria() { type: RESET_SEARCH_CRITERIA }; } + +/** + * Toggles the facial expressions grid. + * + * @returns {Object} + */ +export function toggleFacialExpressions() { + return { + type: TOGGLE_FACIAL_EXPRESSIONS + }; +} diff --git a/react/features/speaker-stats/components/AbstractSpeakerStatsList.js b/react/features/speaker-stats/components/AbstractSpeakerStatsList.js index 6a5b8006a..e58ffaeab 100644 --- a/react/features/speaker-stats/components/AbstractSpeakerStatsList.js +++ b/react/features/speaker-stats/components/AbstractSpeakerStatsList.js @@ -21,10 +21,10 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => { const dispatch = useDispatch(); const { t } = useTranslation(); const conference = useSelector(state => state['features/base/conference'].conference); - const speakerStats = useSelector(state => state['features/speaker-stats'].stats); + const { stats: speakerStats, showFacialExpressions } = useSelector(state => state['features/speaker-stats']); const localParticipant = useSelector(getLocalParticipant); const { clientWidth } = useSelector(state => state['features/base/responsive-ui']); - const { defaultRemoteDisplayName, enableFacialRecognition } = useSelector( + const { defaultRemoteDisplayName } = useSelector( state => state['features/base/config']) || {}; const { facialExpressions: localFacialExpressions } = useSelector( state => state['features/facial-recognition']) || {}; @@ -48,7 +48,7 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => { ? `${localParticipant.name} (${meString})` : meString ); - if (enableFacialRecognition) { + if (showFacialExpressions) { stats[userId].setFacialExpressions(localFacialExpressions); } } @@ -91,10 +91,10 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => { props.dominantSpeakerTime = statsModel.getTotalDominantSpeakerTime(); props.participantId = userId; props.hasLeft = statsModel.hasLeft(); - if (enableFacialRecognition) { + if (showFacialExpressions) { props.facialExpressions = statsModel.getFacialExpressions(); } - props.showFacialExpressions = enableFacialRecognition; + props.showFacialExpressions = showFacialExpressions; props.reduceExpressions = clientWidth < REDUCE_EXPRESSIONS_THRESHOLD; props.displayName = statsModel.getDisplayName() || defaultRemoteDisplayName; props.t = t; diff --git a/react/features/speaker-stats/components/web/SpeakerStats.js b/react/features/speaker-stats/components/web/SpeakerStats.js index c5d78d225..a52e40c05 100644 --- a/react/features/speaker-stats/components/web/SpeakerStats.js +++ b/react/features/speaker-stats/components/web/SpeakerStats.js @@ -7,11 +7,12 @@ import { Dialog } from '../../../base/dialog'; import { translate } from '../../../base/i18n'; import { connect } from '../../../base/redux'; import { escapeRegexp } from '../../../base/util'; -import { initSearch, resetSearchCriteria } from '../../actions'; +import { initSearch, resetSearchCriteria, toggleFacialExpressions } from '../../actions'; import SpeakerStatsLabels from './SpeakerStatsLabels'; import SpeakerStatsList from './SpeakerStatsList'; import SpeakerStatsSearch from './SpeakerStatsSearch'; +import ToggleFacialExpressionsButton from './ToggleFacialExpressionsButton'; /** * The type of the React {@code Component} props of {@link SpeakerStats}. @@ -20,12 +21,17 @@ type Props = { /** * The flag which shows if the facial recognition is enabled, obtained from the redux store. - * If enabled facial expressions are shown. + * If enabled facial expressions can be expanded. + */ + _enableFacialRecognition: boolean, + + /** + * The flag which shows if the facial expressions are displayed or not. */ _showFacialExpressions: boolean, /** - * True if the client width is les than 750. + * True if the client width is less than 750. */ _reduceExpressions: boolean, @@ -63,6 +69,7 @@ class SpeakerStats extends Component { // Bind event handlers so they are only bound once per instance. this._onSearch = this._onSearch.bind(this); + this._onToggleFacialExpressions = this._onToggleFacialExpressions.bind(this); } /** @@ -90,6 +97,11 @@ class SpeakerStats extends Component { width = { this.props._showFacialExpressions ? 'large' : 'medium' }>
+ { this.props._enableFacialRecognition + && + } @@ -111,6 +123,14 @@ class SpeakerStats extends Component { _onSearch(criteria = '') { this.props.dispatch(initSearch(escapeRegexp(criteria))); } + + _onToggleFacialExpressions: () => void; + + /** + */ + _onToggleFacialExpressions() { + this.props.dispatch(toggleFacialExpressions()); + } } /** @@ -125,6 +145,7 @@ class SpeakerStats extends Component { */ function _mapStateToProps(state) { const { enableFacialRecognition } = state['features/base/config']; + const { showFacialExpressions } = state['features/speaker-stats']; const { clientWidth } = state['features/base/responsive-ui']; return { @@ -134,7 +155,8 @@ function _mapStateToProps(state) { * @private * @type {string|undefined} */ - _showFacialExpressions: enableFacialRecognition, + _enableFacialRecognition: enableFacialRecognition, + _showFacialExpressions: showFacialExpressions, _reduceExpressions: clientWidth < 750 }; } diff --git a/react/features/speaker-stats/components/web/ToggleFacialExpressionsButton.js b/react/features/speaker-stats/components/web/ToggleFacialExpressionsButton.js new file mode 100644 index 000000000..dd7287dbb --- /dev/null +++ b/react/features/speaker-stats/components/web/ToggleFacialExpressionsButton.js @@ -0,0 +1,76 @@ +// @flow +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; + +import { + Icon, + IconArrowDownSmall +} from '../../../base/icons'; +import { Tooltip } from '../../../base/tooltip'; + + +const useStyles = makeStyles(theme => { + return { + expandButton: { + position: 'absolute', + right: '24px', + top: '93px', + '&:hover': { + backgroundColor: theme.palette.ui03 + }, + padding: '12px', + borderRadius: '6px', + cursor: 'pointer' + }, + arrowRight: { + transform: 'rotate(-90deg)' + }, + arrowLeft: { + transform: 'rotate(90deg)' + } + }; +}); + +/** + * The type of the React {@code Component} props of {@link SpeakerStatsSearch}. + */ +type Props = { + + /** + * The function to initiate the change in the speaker stats table. + */ + onClick: Function, + + /** + * The state of the button. + */ + showFacialExpressions: boolean, + +}; + +/** + */ +export default function ToggleFacialExpressionsButton({ onClick, showFacialExpressions }: Props) { + const classes = useStyles(); + + const onClickCallback = React.useCallback(() => { + onClick(); + }, []); + + return ( + +
+ +
+
+ ); +} diff --git a/react/features/speaker-stats/reducer.js b/react/features/speaker-stats/reducer.js index fb136ef78..3df0f6bd2 100644 --- a/react/features/speaker-stats/reducer.js +++ b/react/features/speaker-stats/reducer.js @@ -8,7 +8,8 @@ import { INIT_SEARCH, UPDATE_STATS, INIT_REORDER_STATS, - RESET_SEARCH_CRITERIA + RESET_SEARCH_CRITERIA, + TOGGLE_FACIAL_EXPRESSIONS } from './actionTypes'; /** @@ -20,7 +21,8 @@ const INITIAL_STATE = { stats: {}, isOpen: false, pendingReorder: true, - criteria: null + criteria: null, + showFacialExpressions: false }; ReducerRegistry.register('features/speaker-stats', (state = _getInitialState(), action) => { @@ -33,6 +35,12 @@ ReducerRegistry.register('features/speaker-stats', (state = _getInitialState(), return _initReorderStats(state); case RESET_SEARCH_CRITERIA: return _updateCriteria(state, { criteria: null }); + case TOGGLE_FACIAL_EXPRESSIONS: { + return { + ...state, + showFacialExpressions: !state.showFacialExpressions + }; + } } return state;