snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
283
react/features/speaker-stats/components/web/SpeakerStats.tsx
Normal file
283
react/features/speaker-stats/components/web/SpeakerStats.tsx
Normal file
@@ -0,0 +1,283 @@
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { makeStyles } from 'tss-react/mui';
|
||||
|
||||
import { IReduxState } from '../../../app/types';
|
||||
import Icon from '../../../base/icons/components/Icon';
|
||||
import {
|
||||
IconEmotionsAngry,
|
||||
IconEmotionsDisgusted,
|
||||
IconEmotionsFearful,
|
||||
IconEmotionsHappy,
|
||||
IconEmotionsNeutral,
|
||||
IconEmotionsSad,
|
||||
IconEmotionsSurprised
|
||||
} from '../../../base/icons/svg';
|
||||
import Tooltip from '../../../base/tooltip/components/Tooltip';
|
||||
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||
import { escapeRegexp } from '../../../base/util/helpers';
|
||||
import { initSearch, resetSearchCriteria, toggleFaceExpressions } from '../../actions.any';
|
||||
import {
|
||||
DISPLAY_SWITCH_BREAKPOINT,
|
||||
MOBILE_BREAKPOINT
|
||||
} from '../../constants';
|
||||
|
||||
import FaceExpressionsSwitch from './FaceExpressionsSwitch';
|
||||
import SpeakerStatsLabels from './SpeakerStatsLabels';
|
||||
import SpeakerStatsList from './SpeakerStatsList';
|
||||
import SpeakerStatsSearch from './SpeakerStatsSearch';
|
||||
|
||||
const useStyles = makeStyles()(theme => {
|
||||
return {
|
||||
speakerStats: {
|
||||
'& .header': {
|
||||
position: 'fixed',
|
||||
backgroundColor: theme.palette.speakerStatsBackground,
|
||||
paddingLeft: theme.spacing(4),
|
||||
paddingRight: theme.spacing(4),
|
||||
marginLeft: `-${theme.spacing(4)}`,
|
||||
'&.large': {
|
||||
width: '616px'
|
||||
},
|
||||
'&.medium': {
|
||||
width: '352px'
|
||||
},
|
||||
'@media (max-width: 448px)': {
|
||||
width: 'calc(100% - 48px) !important'
|
||||
},
|
||||
'& .upper-header': {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
'& .search-switch-container': {
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
'& .search-container': {
|
||||
width: 175,
|
||||
marginRight: theme.spacing(3)
|
||||
},
|
||||
'& .search-container-full-width': {
|
||||
width: '100%'
|
||||
}
|
||||
},
|
||||
'& .emotions-icons': {
|
||||
display: 'flex',
|
||||
'& svg': {
|
||||
fill: '#000'
|
||||
},
|
||||
'&>div': {
|
||||
marginRight: theme.spacing(3)
|
||||
},
|
||||
'&>div:last-child': {
|
||||
marginRight: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .row': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'& .name-time': {
|
||||
width: 'calc(100% - 48px)',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
'&.expressions-on': {
|
||||
width: 'calc(47% - 48px)',
|
||||
marginRight: theme.spacing(4)
|
||||
}
|
||||
},
|
||||
'& .timeline-container': {
|
||||
height: '100%',
|
||||
width: `calc(53% - ${theme.spacing(4)})`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
borderLeftWidth: 1,
|
||||
borderLeftColor: theme.palette.speakerStatsBorder,
|
||||
borderLeftStyle: 'solid',
|
||||
'& .timeline': {
|
||||
height: theme.spacing(2),
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
'&>div': {
|
||||
marginRight: theme.spacing(1),
|
||||
borderRadius: 5
|
||||
},
|
||||
'&>div:first-child': {
|
||||
borderRadius: '0 5px 5px 0'
|
||||
},
|
||||
'&>div:last-child': {
|
||||
marginRight: 0,
|
||||
borderRadius: '5px 0 0 5px'
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .axis-container': {
|
||||
height: '100%',
|
||||
width: `calc(53% - ${theme.spacing(6)})`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginLeft: theme.spacing(3),
|
||||
'& div': {
|
||||
borderRadius: 5
|
||||
},
|
||||
'& .axis': {
|
||||
height: theme.spacing(1),
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.speakerStatsRowAlternate,
|
||||
position: 'relative',
|
||||
'& .left-bound': {
|
||||
position: 'absolute',
|
||||
bottom: 10,
|
||||
left: 0
|
||||
},
|
||||
'& .right-bound': {
|
||||
position: 'absolute',
|
||||
bottom: 10,
|
||||
right: 0
|
||||
},
|
||||
'& .handler': {
|
||||
position: 'absolute',
|
||||
backgroundColor: theme.palette.speakerStatsHeaderBackground,
|
||||
height: 12,
|
||||
marginTop: -4,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
'& .resize': {
|
||||
height: '100%',
|
||||
width: 5,
|
||||
cursor: 'col-resize'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
'& .separator': {
|
||||
width: 'calc(100% + 48px)',
|
||||
height: 1,
|
||||
marginLeft: -24,
|
||||
backgroundColor: theme.palette.speakerStatsBorder
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const EMOTIONS_LEGEND = [
|
||||
{
|
||||
translationKey: 'speakerStats.neutral',
|
||||
icon: IconEmotionsNeutral
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.happy',
|
||||
icon: IconEmotionsHappy
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.surprised',
|
||||
icon: IconEmotionsSurprised
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.sad',
|
||||
icon: IconEmotionsSad
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.fearful',
|
||||
icon: IconEmotionsFearful
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.angry',
|
||||
icon: IconEmotionsAngry
|
||||
},
|
||||
{
|
||||
translationKey: 'speakerStats.disgusted',
|
||||
icon: IconEmotionsDisgusted
|
||||
}
|
||||
];
|
||||
|
||||
const SpeakerStats = () => {
|
||||
const { faceLandmarks } = useSelector((state: IReduxState) => state['features/base/config']);
|
||||
const { showFaceExpressions } = useSelector((state: IReduxState) => state['features/speaker-stats']);
|
||||
const { videoSpaceWidth } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
|
||||
const displaySwitch = faceLandmarks?.enableDisplayFaceExpressions && videoSpaceWidth > DISPLAY_SWITCH_BREAKPOINT;
|
||||
const displayLabels = videoSpaceWidth > MOBILE_BREAKPOINT;
|
||||
const dispatch = useDispatch();
|
||||
const { classes } = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const onToggleFaceExpressions = useCallback(() =>
|
||||
dispatch(toggleFaceExpressions())
|
||||
, [ dispatch ]);
|
||||
|
||||
const onSearch = useCallback((criteria = '') => {
|
||||
dispatch(initSearch(escapeRegexp(criteria)));
|
||||
}
|
||||
, [ dispatch ]);
|
||||
|
||||
useEffect(() => {
|
||||
showFaceExpressions && !displaySwitch && dispatch(toggleFaceExpressions());
|
||||
}, [ videoSpaceWidth ]);
|
||||
|
||||
useEffect(() => () => {
|
||||
dispatch(resetSearchCriteria());
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
cancel = {{ hidden: true }}
|
||||
ok = {{ hidden: true }}
|
||||
size = { showFaceExpressions ? 'large' : 'medium' }
|
||||
titleKey = 'speakerStats.speakerStats'>
|
||||
<div className = { classes.speakerStats }>
|
||||
<div className = { `header ${showFaceExpressions ? 'large' : 'medium'}` }>
|
||||
<div className = 'upper-header'>
|
||||
<div
|
||||
className = {
|
||||
`search-switch-container
|
||||
${showFaceExpressions ? 'expressions-on' : ''}`
|
||||
}>
|
||||
<div
|
||||
className = {
|
||||
displaySwitch
|
||||
? 'search-container'
|
||||
: 'search-container-full-width' }>
|
||||
<SpeakerStatsSearch
|
||||
onSearch = { onSearch } />
|
||||
</div>
|
||||
|
||||
{ displaySwitch
|
||||
&& <FaceExpressionsSwitch
|
||||
onChange = { onToggleFaceExpressions }
|
||||
showFaceExpressions = { showFaceExpressions } />
|
||||
|
||||
}
|
||||
</div>
|
||||
{ showFaceExpressions && <div className = 'emotions-icons'>
|
||||
{
|
||||
EMOTIONS_LEGEND.map(emotion => (
|
||||
<Tooltip
|
||||
content = { t(emotion.translationKey) }
|
||||
key = { emotion.translationKey }
|
||||
position = { 'top' }>
|
||||
<Icon
|
||||
size = { 20 }
|
||||
src = { emotion.icon } />
|
||||
</Tooltip>
|
||||
))
|
||||
}
|
||||
</div>}
|
||||
</div>
|
||||
{ displayLabels && (
|
||||
<SpeakerStatsLabels
|
||||
showFaceExpressions = { showFaceExpressions ?? false } />
|
||||
)}
|
||||
</div>
|
||||
<SpeakerStatsList />
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeakerStats;
|
||||
Reference in New Issue
Block a user