feat(speaker-stats): make facial expressions expandable

This commit is contained in:
Gabriel Borlea 2021-12-28 16:35:21 +02:00 committed by Calinteodor
parent 40f5f4cd0d
commit 2b0f58c4eb
7 changed files with 139 additions and 13 deletions

View File

@ -48,7 +48,7 @@
width: 20%; width: 20%;
} }
.speaker-stats-item__time_expressions_on { .speaker-stats-item__time_expressions_on {
width: 25%; width: 18%;
} }
.speaker-stats-item__expression { .speaker-stats-item__expression {

View File

@ -47,3 +47,11 @@ export const INIT_REORDER_STATS = 'INIT_REORDER_STATS';
*/ */
export const RESET_SEARCH_CRITERIA = 'RESET_SEARCH_CRITERIA' 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';

View File

@ -5,7 +5,8 @@ import {
INIT_UPDATE_STATS, INIT_UPDATE_STATS,
UPDATE_STATS, UPDATE_STATS,
INIT_REORDER_STATS, INIT_REORDER_STATS,
RESET_SEARCH_CRITERIA RESET_SEARCH_CRITERIA,
TOGGLE_FACIAL_EXPRESSIONS
} from './actionTypes'; } from './actionTypes';
/** /**
@ -68,3 +69,14 @@ export function resetSearchCriteria() {
type: RESET_SEARCH_CRITERIA type: RESET_SEARCH_CRITERIA
}; };
} }
/**
* Toggles the facial expressions grid.
*
* @returns {Object}
*/
export function toggleFacialExpressions() {
return {
type: TOGGLE_FACIAL_EXPRESSIONS
};
}

View File

@ -21,10 +21,10 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { t } = useTranslation(); const { t } = useTranslation();
const conference = useSelector(state => state['features/base/conference'].conference); 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 localParticipant = useSelector(getLocalParticipant);
const { clientWidth } = useSelector(state => state['features/base/responsive-ui']); const { clientWidth } = useSelector(state => state['features/base/responsive-ui']);
const { defaultRemoteDisplayName, enableFacialRecognition } = useSelector( const { defaultRemoteDisplayName } = useSelector(
state => state['features/base/config']) || {}; state => state['features/base/config']) || {};
const { facialExpressions: localFacialExpressions } = useSelector( const { facialExpressions: localFacialExpressions } = useSelector(
state => state['features/facial-recognition']) || {}; state => state['features/facial-recognition']) || {};
@ -48,7 +48,7 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => {
? `${localParticipant.name} (${meString})` ? `${localParticipant.name} (${meString})`
: meString : meString
); );
if (enableFacialRecognition) { if (showFacialExpressions) {
stats[userId].setFacialExpressions(localFacialExpressions); stats[userId].setFacialExpressions(localFacialExpressions);
} }
} }
@ -91,10 +91,10 @@ const abstractSpeakerStatsList = (speakerStatsItem: Function): Function[] => {
props.dominantSpeakerTime = statsModel.getTotalDominantSpeakerTime(); props.dominantSpeakerTime = statsModel.getTotalDominantSpeakerTime();
props.participantId = userId; props.participantId = userId;
props.hasLeft = statsModel.hasLeft(); props.hasLeft = statsModel.hasLeft();
if (enableFacialRecognition) { if (showFacialExpressions) {
props.facialExpressions = statsModel.getFacialExpressions(); props.facialExpressions = statsModel.getFacialExpressions();
} }
props.showFacialExpressions = enableFacialRecognition; props.showFacialExpressions = showFacialExpressions;
props.reduceExpressions = clientWidth < REDUCE_EXPRESSIONS_THRESHOLD; props.reduceExpressions = clientWidth < REDUCE_EXPRESSIONS_THRESHOLD;
props.displayName = statsModel.getDisplayName() || defaultRemoteDisplayName; props.displayName = statsModel.getDisplayName() || defaultRemoteDisplayName;
props.t = t; props.t = t;

View File

@ -7,11 +7,12 @@ import { Dialog } from '../../../base/dialog';
import { translate } from '../../../base/i18n'; import { translate } from '../../../base/i18n';
import { connect } from '../../../base/redux'; import { connect } from '../../../base/redux';
import { escapeRegexp } from '../../../base/util'; import { escapeRegexp } from '../../../base/util';
import { initSearch, resetSearchCriteria } from '../../actions'; import { initSearch, resetSearchCriteria, toggleFacialExpressions } from '../../actions';
import SpeakerStatsLabels from './SpeakerStatsLabels'; import SpeakerStatsLabels from './SpeakerStatsLabels';
import SpeakerStatsList from './SpeakerStatsList'; import SpeakerStatsList from './SpeakerStatsList';
import SpeakerStatsSearch from './SpeakerStatsSearch'; import SpeakerStatsSearch from './SpeakerStatsSearch';
import ToggleFacialExpressionsButton from './ToggleFacialExpressionsButton';
/** /**
* The type of the React {@code Component} props of {@link SpeakerStats}. * 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. * 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, _showFacialExpressions: boolean,
/** /**
* True if the client width is les than 750. * True if the client width is less than 750.
*/ */
_reduceExpressions: boolean, _reduceExpressions: boolean,
@ -63,6 +69,7 @@ class SpeakerStats extends Component<Props> {
// Bind event handlers so they are only bound once per instance. // Bind event handlers so they are only bound once per instance.
this._onSearch = this._onSearch.bind(this); this._onSearch = this._onSearch.bind(this);
this._onToggleFacialExpressions = this._onToggleFacialExpressions.bind(this);
} }
/** /**
@ -90,6 +97,11 @@ class SpeakerStats extends Component<Props> {
width = { this.props._showFacialExpressions ? 'large' : 'medium' }> width = { this.props._showFacialExpressions ? 'large' : 'medium' }>
<div className = 'speaker-stats'> <div className = 'speaker-stats'>
<SpeakerStatsSearch onSearch = { this._onSearch } /> <SpeakerStatsSearch onSearch = { this._onSearch } />
{ this.props._enableFacialRecognition
&& <ToggleFacialExpressionsButton
onClick = { this._onToggleFacialExpressions }
showFacialExpressions = { this.props._showFacialExpressions } />
}
<SpeakerStatsLabels <SpeakerStatsLabels
reduceExpressions = { this.props._reduceExpressions } reduceExpressions = { this.props._reduceExpressions }
showFacialExpressions = { this.props._showFacialExpressions ?? false } /> showFacialExpressions = { this.props._showFacialExpressions ?? false } />
@ -111,6 +123,14 @@ class SpeakerStats extends Component<Props> {
_onSearch(criteria = '') { _onSearch(criteria = '') {
this.props.dispatch(initSearch(escapeRegexp(criteria))); this.props.dispatch(initSearch(escapeRegexp(criteria)));
} }
_onToggleFacialExpressions: () => void;
/**
*/
_onToggleFacialExpressions() {
this.props.dispatch(toggleFacialExpressions());
}
} }
/** /**
@ -125,6 +145,7 @@ class SpeakerStats extends Component<Props> {
*/ */
function _mapStateToProps(state) { function _mapStateToProps(state) {
const { enableFacialRecognition } = state['features/base/config']; const { enableFacialRecognition } = state['features/base/config'];
const { showFacialExpressions } = state['features/speaker-stats'];
const { clientWidth } = state['features/base/responsive-ui']; const { clientWidth } = state['features/base/responsive-ui'];
return { return {
@ -134,7 +155,8 @@ function _mapStateToProps(state) {
* @private * @private
* @type {string|undefined} * @type {string|undefined}
*/ */
_showFacialExpressions: enableFacialRecognition, _enableFacialRecognition: enableFacialRecognition,
_showFacialExpressions: showFacialExpressions,
_reduceExpressions: clientWidth < 750 _reduceExpressions: clientWidth < 750
}; };
} }

View File

@ -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 (
<Tooltip
content = { `${showFacialExpressions ? 'Hide' : 'Show'} facial expressions` }
position = { 'top' } >
<div
className = { classes.expandButton }
onClick = { onClickCallback }
role = 'button'
tabIndex = { 0 }>
<Icon
className = { showFacialExpressions ? classes.arrowLeft : classes.arrowRight }
size = { 24 }
src = { IconArrowDownSmall } />
</div>
</Tooltip>
);
}

View File

@ -8,7 +8,8 @@ import {
INIT_SEARCH, INIT_SEARCH,
UPDATE_STATS, UPDATE_STATS,
INIT_REORDER_STATS, INIT_REORDER_STATS,
RESET_SEARCH_CRITERIA RESET_SEARCH_CRITERIA,
TOGGLE_FACIAL_EXPRESSIONS
} from './actionTypes'; } from './actionTypes';
/** /**
@ -20,7 +21,8 @@ const INITIAL_STATE = {
stats: {}, stats: {},
isOpen: false, isOpen: false,
pendingReorder: true, pendingReorder: true,
criteria: null criteria: null,
showFacialExpressions: false
}; };
ReducerRegistry.register('features/speaker-stats', (state = _getInitialState(), action) => { ReducerRegistry.register('features/speaker-stats', (state = _getInitialState(), action) => {
@ -33,6 +35,12 @@ ReducerRegistry.register('features/speaker-stats', (state = _getInitialState(),
return _initReorderStats(state); return _initReorderStats(state);
case RESET_SEARCH_CRITERIA: case RESET_SEARCH_CRITERIA:
return _updateCriteria(state, { criteria: null }); return _updateCriteria(state, { criteria: null });
case TOGGLE_FACIAL_EXPRESSIONS: {
return {
...state,
showFacialExpressions: !state.showFacialExpressions
};
}
} }
return state; return state;