mirror of https://github.com/jitsi/jitsi-meet
parent
40f5f4cd0d
commit
eb70c611c2
@ -1,80 +1,30 @@ |
||||
.speaker-stats { |
||||
list-style: none; |
||||
padding: 0; |
||||
width: 100%; |
||||
font-weight: 500; |
||||
|
||||
.speaker-stats-item__status-dot { |
||||
position: relative; |
||||
display: block; |
||||
width: 9px; |
||||
height: 9px; |
||||
border-radius: 50%; |
||||
margin: 0 auto; |
||||
|
||||
&.status-active { |
||||
background: green; |
||||
.row{ |
||||
display: flex; |
||||
align-items: center; |
||||
.avatar { |
||||
width: 32px; |
||||
margin-right: 16px; |
||||
} |
||||
|
||||
&.status-inactive { |
||||
background: gray; |
||||
.name-time { |
||||
width: calc(100% - 48px); |
||||
display: flex; |
||||
justify-content: space-between; |
||||
align-items: center; |
||||
|
||||
} |
||||
} |
||||
|
||||
.status-user-left { |
||||
color: $placeHolderColor; |
||||
} |
||||
|
||||
.speaker-stats-item__status, |
||||
.speaker-stats-item__name, |
||||
.speaker-stats-item__time, |
||||
.speaker-stats-item__name_expressions_on, |
||||
.speaker-stats-item__time_expressions_on, |
||||
.speaker-stats-item__expression { |
||||
display: inline-block; |
||||
margin: 5px 0; |
||||
vertical-align: middle; |
||||
} |
||||
.speaker-stats-item__status { |
||||
width: 5%; |
||||
} |
||||
.speaker-stats-item__name { |
||||
width: 40%; |
||||
} |
||||
.speaker-stats-item__time { |
||||
width: 55%; |
||||
} |
||||
.speaker-stats-item__name_expressions_on { |
||||
width: 20%; |
||||
} |
||||
.speaker-stats-item__time_expressions_on { |
||||
width: 25%; |
||||
} |
||||
|
||||
.speaker-stats-item__expression { |
||||
width: 7%; |
||||
text-align: center; |
||||
} |
||||
|
||||
@media(max-width: 750px) { |
||||
.speaker-stats-item__name_expressions_on { |
||||
width: 25%; |
||||
.name-time_expressions-on { |
||||
width: calc(47% - 48px); |
||||
} |
||||
.speaker-stats-item__time_expressions_on { |
||||
width: 30%; |
||||
.expressions { |
||||
width: calc(53% - 29px); |
||||
display: flex; |
||||
justify-content: space-between; |
||||
.expression { |
||||
width: 30px; |
||||
text-align: center; |
||||
} |
||||
} |
||||
.speaker-stats-item__expression { |
||||
width: 10%; |
||||
} |
||||
} |
||||
|
||||
.speaker-stats-item__name, |
||||
.speaker-stats-item__time, |
||||
.speaker-stats-item__name_expressions_on, |
||||
.speaker-stats-item__time_expressions_on, |
||||
.speaker-stats-item__expression { |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
} |
||||
|
@ -1,35 +0,0 @@ |
||||
/* @flow */ |
||||
|
||||
import React from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
import { Text, View } from 'react-native'; |
||||
|
||||
import style from './styles'; |
||||
|
||||
/** |
||||
* React component for labeling speaker stats column items. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
const SpeakerStatsLabels = () => { |
||||
|
||||
const { t } = useTranslation(); |
||||
|
||||
return ( |
||||
<View style = { style.speakerStatsLabelContainer } > |
||||
<View style = { style.dummyElement } /> |
||||
<View style = { style.speakerName }> |
||||
<Text> |
||||
{ t('speakerStats.name') } |
||||
</Text> |
||||
</View> |
||||
<View style = { style.speakerTime }> |
||||
<Text> |
||||
{ t('speakerStats.speakerTime') } |
||||
</Text> |
||||
</View> |
||||
</View> |
||||
); |
||||
}; |
||||
|
||||
export default SpeakerStatsLabels; |
@ -0,0 +1,60 @@ |
||||
// @flow
|
||||
import React from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
import { withTheme } from 'react-native-paper'; |
||||
import { useSelector } from 'react-redux'; |
||||
|
||||
import { IconSearch, Icon } from '../../../base/icons'; |
||||
import ClearableInput from '../../../participants-pane/components/native/ClearableInput'; |
||||
import { isSpeakerStatsSearchDisabled } from '../../functions'; |
||||
|
||||
import styles from './styles'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link SpeakerStatsSearch}. |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* The function to initiate the change in the speaker stats table. |
||||
*/ |
||||
onSearch: Function, |
||||
|
||||
/** |
||||
* Theme used for styles. |
||||
*/ |
||||
theme: Object |
||||
}; |
||||
|
||||
/** |
||||
* React component for display an individual user's speaker stats. |
||||
* |
||||
* @returns {React$Element<any>} |
||||
*/ |
||||
function SpeakerStatsSearch({ onSearch, theme }: Props) { |
||||
const { t } = useTranslation(); |
||||
|
||||
const disableSpeakerStatsSearch = useSelector(isSpeakerStatsSearchDisabled); |
||||
|
||||
if (disableSpeakerStatsSearch) { |
||||
return null; |
||||
} |
||||
|
||||
return ( |
||||
<ClearableInput |
||||
customStyles = { styles.speakerStatsSearch } |
||||
onChange = { onSearch } |
||||
placeholder = { t('speakerStats.search') } |
||||
placeholderColor = { theme.palette.text03 } |
||||
prefixComponent = { |
||||
<Icon |
||||
color = { theme.palette.text03 } |
||||
size = { 20 } |
||||
src = { IconSearch } |
||||
style = { styles.speakerStatsSearch.searchIcon } /> |
||||
} |
||||
selectionColor = { theme.palette.text01 } /> |
||||
); |
||||
} |
||||
|
||||
export default withTheme(SpeakerStatsSearch); |
@ -0,0 +1,97 @@ |
||||
// @flow
|
||||
import { makeStyles } from '@material-ui/core/styles'; |
||||
import React from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
|
||||
import { Switch } from '../../../base/react'; |
||||
|
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
switchContainer: { |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
'& svg': { |
||||
display: 'none' |
||||
|
||||
}, |
||||
'& div': { |
||||
width: 38, |
||||
'& > label': { |
||||
width: 32, |
||||
height: 20, |
||||
backgroundColor: theme.palette.ui05, |
||||
'&:not([data-checked]):hover': { |
||||
backgroundColor: theme.palette.ui05 |
||||
}, |
||||
'&[data-checked]': { |
||||
backgroundColor: theme.palette.action01, |
||||
'&:hover': { |
||||
backgroundColor: theme.palette.action01 |
||||
}, |
||||
'&::before': { |
||||
margin: '0 0 1.5px -3px', |
||||
backgroundColor: theme.palette.text01 |
||||
} |
||||
}, |
||||
'&:focus-within': { |
||||
borderColor: 'transparent' |
||||
}, |
||||
'&::before': { |
||||
width: 14, |
||||
height: 14, |
||||
margin: '0 0 1.5px 1.5px', |
||||
backgroundColor: theme.palette.text01 |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
switchLabel: { |
||||
marginRight: 10, |
||||
...theme.typography.bodyShortRegular, |
||||
lineHeight: `${theme.typography.bodyShortRegular.lineHeight}px` |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link ToggleFacialExpressionsButton}. |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* The function to initiate the change in the speaker stats table. |
||||
*/ |
||||
onChange: Function, |
||||
|
||||
/** |
||||
* The state of the button. |
||||
*/ |
||||
showFacialExpressions: boolean, |
||||
|
||||
}; |
||||
|
||||
/** |
||||
* React component for toggling facial expressions grid. |
||||
* |
||||
* @returns {React$Element<any>} |
||||
*/ |
||||
export default function FacialExpressionsSwitch({ onChange, showFacialExpressions }: Props) { |
||||
const classes = useStyles(); |
||||
const { t } = useTranslation(); |
||||
|
||||
return ( |
||||
<div className = { classes.switchContainer } > |
||||
<label |
||||
className = { classes.switchLabel } |
||||
htmlFor = 'facial-expressions-switch'> |
||||
{ t('speakerStats.displayEmotions')} |
||||
</label> |
||||
<Switch |
||||
id = 'facial-expressions-switch' |
||||
onValueChange = { onChange } |
||||
trackColor = {{ false: 'blue' }} |
||||
value = { showFacialExpressions } /> |
||||
</div> |
||||
); |
||||
} |
@ -1,142 +1,116 @@ |
||||
// @flow
|
||||
|
||||
import React, { Component } from 'react'; |
||||
import type { Dispatch } from 'redux'; |
||||
import { makeStyles } from '@material-ui/core/styles'; |
||||
import React, { useCallback, useEffect } from 'react'; |
||||
import { useSelector, useDispatch } from 'react-redux'; |
||||
|
||||
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 { resetSearchCriteria, toggleFacialExpressions, initSearch } from '../../actions'; |
||||
import { |
||||
DISPLAY_SWITCH_BREAKPOINT, |
||||
MOBILE_BREAKPOINT, |
||||
RESIZE_SEARCH_SWITCH_CONTAINER_BREAKPOINT |
||||
} from '../../constants'; |
||||
|
||||
import FacialExpressionsSwitch from './FacialExpressionsSwitch'; |
||||
import SpeakerStatsLabels from './SpeakerStatsLabels'; |
||||
import SpeakerStatsList from './SpeakerStatsList'; |
||||
import SpeakerStatsSearch from './SpeakerStatsSearch'; |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link SpeakerStats}. |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* The flag which shows if the facial recognition is enabled, obtained from the redux store. |
||||
* If enabled facial expressions are shown. |
||||
*/ |
||||
_showFacialExpressions: boolean, |
||||
|
||||
/** |
||||
* True if the client width is les than 750. |
||||
*/ |
||||
_reduceExpressions: boolean, |
||||
|
||||
/** |
||||
* The search criteria. |
||||
*/ |
||||
_criteria: string | null, |
||||
|
||||
/** |
||||
* Redux store dispatch method. |
||||
*/ |
||||
dispatch: Dispatch<any>, |
||||
|
||||
/** |
||||
* The function to translate human-readable text. |
||||
*/ |
||||
t: Function |
||||
}; |
||||
|
||||
/** |
||||
* React component for displaying a list of speaker stats. |
||||
* |
||||
* @augments Component |
||||
*/ |
||||
class SpeakerStats extends Component<Props> { |
||||
|
||||
/** |
||||
* Initializes a new SpeakerStats instance. |
||||
* |
||||
* @param {Object} props - The read-only React Component props with which |
||||
* the new instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
// Bind event handlers so they are only bound once per instance.
|
||||
this._onSearch = this._onSearch.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Resets the search criteria when component will unmount. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
componentWillUnmount() { |
||||
this.props.dispatch(resetSearchCriteria()); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
return ( |
||||
<Dialog |
||||
cancelKey = 'dialog.close' |
||||
submitDisabled = { true } |
||||
titleKey = 'speakerStats.speakerStats' |
||||
width = { this.props._showFacialExpressions ? 'large' : 'medium' }> |
||||
<div className = 'speaker-stats'> |
||||
<SpeakerStatsSearch onSearch = { this._onSearch } /> |
||||
<SpeakerStatsLabels |
||||
reduceExpressions = { this.props._reduceExpressions } |
||||
showFacialExpressions = { this.props._showFacialExpressions ?? false } /> |
||||
<SpeakerStatsList /> |
||||
</div> |
||||
</Dialog> |
||||
); |
||||
} |
||||
|
||||
_onSearch: () => void; |
||||
|
||||
/** |
||||
* Search the existing participants by name. |
||||
* |
||||
* @returns {void} |
||||
* @param {string} criteria - The search parameter. |
||||
* @protected |
||||
*/ |
||||
_onSearch(criteria = '') { |
||||
this.props.dispatch(initSearch(escapeRegexp(criteria))); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the redux state to the associated SpeakerStats's props. |
||||
* |
||||
* @param {Object} state - The redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _showFacialExpressions: ?boolean, |
||||
* _reduceExpressions: boolean, |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
const { enableFacialRecognition } = state['features/base/config']; |
||||
const { clientWidth } = state['features/base/responsive-ui']; |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
/** |
||||
* The local display name. |
||||
* |
||||
* @private |
||||
* @type {string|undefined} |
||||
*/ |
||||
_showFacialExpressions: enableFacialRecognition, |
||||
_reduceExpressions: clientWidth < 750 |
||||
separator: { |
||||
position: 'absolute', |
||||
width: '100%', |
||||
height: 1, |
||||
left: 0, |
||||
backgroundColor: theme.palette.border02 |
||||
}, |
||||
searchSwitchContainer: { |
||||
display: 'flex', |
||||
justifyContent: 'space-between', |
||||
alignItems: 'center', |
||||
width: '100%' |
||||
}, |
||||
searchSwitchContainerExpressionsOn: { |
||||
width: '58.5%', |
||||
[theme.breakpoints.down(RESIZE_SEARCH_SWITCH_CONTAINER_BREAKPOINT)]: { |
||||
width: '100%' |
||||
} |
||||
}, |
||||
searchContainer: { |
||||
width: '50%' |
||||
}, |
||||
searchContainerFullWidth: { |
||||
width: '100%' |
||||
} |
||||
}; |
||||
} |
||||
}); |
||||
|
||||
const SpeakerStats = () => { |
||||
const { enableDisplayFacialExpressions } = useSelector(state => state['features/base/config']); |
||||
const { showFacialExpressions } = useSelector(state => state['features/speaker-stats']); |
||||
const { clientWidth } = useSelector(state => state['features/base/responsive-ui']); |
||||
const displaySwitch = enableDisplayFacialExpressions && clientWidth > DISPLAY_SWITCH_BREAKPOINT; |
||||
const displayLabels = clientWidth > MOBILE_BREAKPOINT; |
||||
const dispatch = useDispatch(); |
||||
const classes = useStyles(); |
||||
|
||||
const onToggleFacialExpressions = useCallback(() => |
||||
dispatch(toggleFacialExpressions()) |
||||
, [ dispatch ]); |
||||
|
||||
const onSearch = useCallback((criteria = '') => { |
||||
dispatch(initSearch(escapeRegexp(criteria))); |
||||
} |
||||
, [ dispatch ]); |
||||
|
||||
useEffect(() => { |
||||
showFacialExpressions && !displaySwitch && dispatch(toggleFacialExpressions()); |
||||
}, [ clientWidth ]); |
||||
useEffect(() => () => dispatch(resetSearchCriteria()), []); |
||||
|
||||
return ( |
||||
<Dialog |
||||
cancelKey = 'dialog.close' |
||||
hideCancelButton = { true } |
||||
submitDisabled = { true } |
||||
titleKey = 'speakerStats.speakerStats' |
||||
width = { showFacialExpressions ? '664px' : 'small' }> |
||||
<div className = 'speaker-stats'> |
||||
<div |
||||
className = { |
||||
`${classes.searchSwitchContainer} |
||||
${showFacialExpressions ? classes.searchSwitchContainerExpressionsOn : ''}` |
||||
}> |
||||
<div |
||||
className = { |
||||
displaySwitch |
||||
? classes.searchContainer |
||||
: classes.searchContainerFullWidth }> |
||||
<SpeakerStatsSearch |
||||
onSearch = { onSearch } /> |
||||
</div> |
||||
|
||||
{ displaySwitch |
||||
&& <FacialExpressionsSwitch |
||||
onChange = { onToggleFacialExpressions } |
||||
showFacialExpressions = { showFacialExpressions } /> |
||||
} |
||||
</div> |
||||
{ displayLabels && ( |
||||
<> |
||||
<SpeakerStatsLabels |
||||
showFacialExpressions = { showFacialExpressions ?? false } /> |
||||
<div className = { classes.separator } /> |
||||
</> |
||||
)} |
||||
<SpeakerStatsList /> |
||||
</div> |
||||
</Dialog> |
||||
|
||||
); |
||||
}; |
||||
|
||||
export default translate(connect(_mapStateToProps)(SpeakerStats)); |
||||
export default SpeakerStats; |
||||
|
@ -1,90 +1,79 @@ |
||||
/* @flow */ |
||||
import { makeStyles } from '@material-ui/core/styles'; |
||||
import React from 'react'; |
||||
import { useTranslation } from 'react-i18next'; |
||||
|
||||
import React, { Component } from 'react'; |
||||
|
||||
import { translate } from '../../../base/i18n'; |
||||
import { Tooltip } from '../../../base/tooltip'; |
||||
import { FACIAL_EXPRESSION_EMOJIS } from '../../../facial-recognition/constants.js'; |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
labels: { |
||||
padding: '22px 0 7px 0', |
||||
height: 20 |
||||
}, |
||||
emojis: { |
||||
paddingLeft: 27, |
||||
...theme.typography.bodyShortRegularLarge, |
||||
lineHeight: `${theme.typography.bodyShortRegular.lineHeightLarge}px` |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
/** |
||||
* The type of the React {@code Component} props of {@link SpeakerStatsLabels}. |
||||
*/ |
||||
type Props = { |
||||
|
||||
/** |
||||
* True if the client width is les than 750. |
||||
*/ |
||||
reduceExpressions: boolean, |
||||
|
||||
/** |
||||
* True if the facial recognition is not disabled. |
||||
*/ |
||||
showFacialExpressions: boolean, |
||||
|
||||
/** |
||||
* The function to translate human-readable text. |
||||
*/ |
||||
t: Function |
||||
}; |
||||
|
||||
/** |
||||
* React component for labeling speaker stats column items. |
||||
* |
||||
* @augments Component |
||||
*/ |
||||
class SpeakerStatsLabels extends Component<Props> { |
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { t } = this.props; |
||||
|
||||
return ( |
||||
<div className = 'speaker-stats-item__labels'> |
||||
<div className = 'speaker-stats-item__status' /> |
||||
const SpeakerStatsLabels = (props: Props) => { |
||||
const { t } = useTranslation(); |
||||
const classes = useStyles(); |
||||
const FacialExpressionsLabels = () => Object.keys(FACIAL_EXPRESSION_EMOJIS).map( |
||||
expression => ( |
||||
<div |
||||
className = { `speaker-stats-item__name${ |
||||
this.props.showFacialExpressions ? '_expressions_on' : '' |
||||
}` }>
|
||||
className = 'expression' |
||||
key = { expression }> |
||||
<Tooltip |
||||
content = { t(`speakerStats.${expression}`) } |
||||
position = { 'top' } > |
||||
<div> |
||||
{ FACIAL_EXPRESSION_EMOJIS[expression] } |
||||
</div> |
||||
|
||||
</Tooltip> |
||||
</div> |
||||
) |
||||
); |
||||
const nameTimeClass = `name-time${ |
||||
props.showFacialExpressions ? ' name-time_expressions-on' : '' |
||||
}`;
|
||||
|
||||
return ( |
||||
<div className = { `row ${classes.labels}` }> |
||||
<div className = 'avatar' /> |
||||
|
||||
<div className = { nameTimeClass }> |
||||
<div> |
||||
{ t('speakerStats.name') } |
||||
</div> |
||||
<div |
||||
className = { `speaker-stats-item__time${ |
||||
this.props.showFacialExpressions ? '_expressions_on' : '' |
||||
}` }>
|
||||
<div> |
||||
{ t('speakerStats.speakerTime') } |
||||
</div> |
||||
{ this.props.showFacialExpressions |
||||
&& (this.props.reduceExpressions |
||||
? Object.keys(FACIAL_EXPRESSION_EMOJIS) |
||||
.filter(expression => ![ 'angry', 'fearful', 'disgusted' ].includes(expression)) |
||||
: Object.keys(FACIAL_EXPRESSION_EMOJIS) |
||||
).map( |
||||
expression => ( |
||||
<div |
||||
className = 'speaker-stats-item__expression' |
||||
key = { expression }> |
||||
<Tooltip |
||||
content = { t(`speakerStats.${expression}`) } |
||||
position = { 'top' } > |
||||
<div |
||||
// eslint-disable-next-line react-native/no-inline-styles
|
||||
style = {{ fontSize: 17 }}> |
||||
|
||||
{ FACIAL_EXPRESSION_EMOJIS[expression] } |
||||
</div> |
||||
|
||||
</Tooltip> |
||||
</div> |
||||
|
||||
)) |
||||
} |
||||
</div> |
||||
); |
||||
} |
||||
} |
||||
{ |
||||
props.showFacialExpressions |
||||
&& <div className = { `expressions ${classes.emojis}` }> |
||||
<FacialExpressionsLabels /> |
||||
</div> |
||||
} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default translate(SpeakerStatsLabels); |
||||
export default SpeakerStatsLabels; |
||||
|
@ -1,25 +1,71 @@ |
||||
// @flow
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles'; |
||||
import React from 'react'; |
||||
|
||||
import { MOBILE_BREAKPOINT } from '../../constants'; |
||||
import abstractSpeakerStatsList from '../AbstractSpeakerStatsList'; |
||||
|
||||
import SpeakerStatsItem from './SpeakerStatsItem'; |
||||
|
||||
const useStyles = makeStyles(theme => { |
||||
return { |
||||
list: { |
||||
marginTop: `${theme.spacing(3)}px` |
||||
}, |
||||
item: { |
||||
height: `${theme.spacing(7)}px`, |
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: { |
||||
height: `${theme.spacing(8)}px` |
||||
} |
||||
}, |
||||
avatar: { |
||||
height: `${theme.spacing(5)}px` |
||||
}, |
||||
expressions: { |
||||
paddingLeft: 29 |
||||
}, |
||||
hasLeft: { |
||||
color: theme.palette.text03 |
||||
}, |
||||
displayName: { |
||||
...theme.typography.bodyShortRegular, |
||||
lineHeight: `${theme.typography.bodyShortRegular.lineHeight}px`, |
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: { |
||||
...theme.typography.bodyShortRegularLarge, |
||||
lineHeight: `${theme.typography.bodyShortRegular.lineHeightLarge}px` |
||||
} |
||||
}, |
||||
time: { |
||||
padding: '2px 4px', |
||||
borderRadius: '4px', |
||||
...theme.typography.labelBold, |
||||
lineHeight: `${theme.typography.labelBold.lineHeight}px`, |
||||
[theme.breakpoints.down(MOBILE_BREAKPOINT)]: { |
||||
...theme.typography.bodyShortRegularLarge, |
||||
lineHeight: `${theme.typography.bodyShortRegular.lineHeightLarge}px` |
||||
} |
||||
}, |
||||
dominant: { |
||||
backgroundColor: theme.palette.success02 |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
/** |
||||
* Component that renders the list of speaker stats. |
||||
* |
||||
* @returns {React$Element<any>} |
||||
*/ |
||||
const SpeakerStatsList = () => { |
||||
const items = abstractSpeakerStatsList(SpeakerStatsItem); |
||||
const classes = useStyles(); |
||||
const items = abstractSpeakerStatsList(SpeakerStatsItem, classes); |
||||
|
||||
return ( |
||||
<div> |
||||
<div className = { classes.list }> |
||||
{items} |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
|
||||
export default SpeakerStatsList; |
||||
|
@ -1,6 +1,7 @@ |
||||
/** |
||||
* The with of the client at witch the facial expressions will be reduced to only 4. |
||||
*/ |
||||
export const REDUCE_EXPRESSIONS_THRESHOLD = 750; |
||||
|
||||
export const SPEAKER_STATS_RELOAD_INTERVAL = 1000; |
||||
|
||||
export const DISPLAY_SWITCH_BREAKPOINT = 600; |
||||
|
||||
export const RESIZE_SEARCH_SWITCH_CONTAINER_BREAKPOINT = 750; |
||||
|
||||
export const MOBILE_BREAKPOINT = 480; |
||||
|
Loading…
Reference in new issue