feat(speaker-stats): make facial expressions expandable
This commit is contained in:
parent
40f5f4cd0d
commit
2b0f58c4eb
|
@ -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 {
|
||||||
|
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue