feat(dialogs): Redesign Video Quality dialog & change dialog background color
This commit is contained in:
parent
bad8911fe8
commit
1ad8f77179
|
@ -24,61 +24,6 @@
|
|||
bottom: calc(#{$newToolbarSizeWithPadding}) !important;
|
||||
}
|
||||
|
||||
.modal-dialog-form {
|
||||
/**
|
||||
* Override @atlaskit/dropdown-menu styling when in a modal because the
|
||||
* dropdown backgrounds clash with the modal backgrounds.
|
||||
*/
|
||||
.dropdown-menu div[style*="transform"] {
|
||||
outline: 1px solid #455166;
|
||||
}
|
||||
.dropdown-menu button:not(:active):not(:hover) > span {
|
||||
color: #B8C7E0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override @atlaskit/tab styling when in a modal because the
|
||||
* tab text color clash with the modal backgrounds.
|
||||
*/
|
||||
div[role="tablist"] > div:not([data-selected]):not(:hover),
|
||||
label > div > span {
|
||||
color: #B8C7E0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override @atlaskit/modal-dialog header styling
|
||||
*/
|
||||
.atlaskit-portal [role="dialog"] header {
|
||||
box-shadow: none;
|
||||
.jitsi-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jitsi-icon svg {
|
||||
fill: #B8C7E0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override @atlaskit/modal-dialog footer styling.
|
||||
*/
|
||||
.atlaskit-portal [role="dialog"] footer {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make header close button more easily tappable on mobile.
|
||||
*/
|
||||
.mobile-browser .atlaskit-portal [role="dialog"] header .jitsi-icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
background: #2a3a4b;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override @atlaskit/theme styling for the top toolbar so it displays over
|
||||
* the video thumbnail while obscuring as little as possible.
|
||||
|
|
|
@ -44,7 +44,6 @@ $flagsImagePath: "../images/";
|
|||
@import 'modals/screen-share/share-audio';
|
||||
@import 'modals/screen-share/share-screen-warning';
|
||||
@import 'modals/speaker_stats/speaker_stats';
|
||||
@import 'modals/video-quality/video-quality';
|
||||
@import 'modals/virtual-background/virtual-background';
|
||||
@import 'modals/local-recording/local-recording';
|
||||
@import 'videolayout_default';
|
||||
|
|
|
@ -122,9 +122,6 @@
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
.modal-dialog-footer {
|
||||
font-size: $modalButtonFontSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Styling inline dialog errors.
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
.video-quality-dialog {
|
||||
.video-quality-dialog-title {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.video-quality-dialog-contents {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
min-width: 250px;
|
||||
|
||||
.video-quality-dialog-slider-container {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.video-quality-dialog-slider {
|
||||
width: calc(100% - 5px);
|
||||
|
||||
@mixin sliderTrackStyles() {
|
||||
height: 15px;
|
||||
border-radius: 10px;
|
||||
background: rgb(14, 22, 36);
|
||||
}
|
||||
|
||||
&::-ms-track {
|
||||
@include sliderTrackStyles();
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
@include sliderTrackStyles();
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
@include sliderTrackStyles();
|
||||
}
|
||||
|
||||
@mixin sliderThumbStyles() {
|
||||
top: 50%;
|
||||
border: none;
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&::-ms-thumb {
|
||||
@include sliderThumbStyles();
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
@include sliderThumbStyles();
|
||||
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
@include sliderThumbStyles();
|
||||
}
|
||||
}
|
||||
|
||||
.video-quality-dialog-labels {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.video-quality-dialog-label-container {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
transform: translate(-50%, 0%);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
border-radius: 50%;
|
||||
left: 0;
|
||||
height: 6px;
|
||||
margin: 0 auto;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -16px;
|
||||
width: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.video-quality-dialog-label-container.active {
|
||||
color: $videoQualityActive;
|
||||
font-weight: bold;
|
||||
|
||||
&::before {
|
||||
background: $videoQualityActive;
|
||||
height: 12px;
|
||||
top: -19px;
|
||||
width: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.video-quality-dialog-label-container:first-child {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.video-quality-dialog-label {
|
||||
display: table-caption;
|
||||
word-spacing: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-dialog-form {
|
||||
.video-quality-dialog-title {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -1045,7 +1045,10 @@
|
|||
"pending": "{{displayName}} has been invited"
|
||||
},
|
||||
"videoStatus": {
|
||||
"adjustFor": "Adjust for:",
|
||||
"audioOnly": "AUD",
|
||||
"bestPerformance": "Best performance",
|
||||
"highestQuality": "Highest quality",
|
||||
"audioOnlyExpanded": "You are in low bandwidth mode. In this mode you will receive only audio and screen sharing.",
|
||||
"callQuality": "Video Quality",
|
||||
"hd": "HD",
|
||||
|
|
|
@ -4,6 +4,7 @@ import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
|||
import React from 'react';
|
||||
|
||||
import { DialogContainer } from '../../base/dialog';
|
||||
import GlobalStyles from '../../base/ui/components/GlobalStyles';
|
||||
import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider';
|
||||
import { ChromeExtensionBanner } from '../../chrome-extension-banner';
|
||||
|
||||
|
@ -29,6 +30,7 @@ export class App extends AbstractApp {
|
|||
return (
|
||||
<JitsiThemeProvider>
|
||||
<AtlasKitThemeProvider mode = 'dark'>
|
||||
<GlobalStyles />
|
||||
<ChromeExtensionBanner />
|
||||
{ super._createMainElement(component, props) }
|
||||
</AtlasKitThemeProvider>
|
||||
|
|
|
@ -8,10 +8,12 @@ import {
|
|||
titleIconWrapperStyles,
|
||||
TitleText
|
||||
} from '@atlaskit/modal-dialog/dist/es2019/styled/Content';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import React from 'react';
|
||||
|
||||
import { translate } from '../../../i18n';
|
||||
import { Icon, IconClose } from '../../../icons';
|
||||
import { withPixelLineHeight } from '../../../styles/functions';
|
||||
|
||||
const TitleIcon = ({ appearance }: { appearance?: 'danger' | 'warning' }) => {
|
||||
if (!appearance) {
|
||||
|
@ -30,6 +32,7 @@ const TitleIcon = ({ appearance }: { appearance?: 'danger' | 'warning' }) => {
|
|||
type Props = {
|
||||
id: string,
|
||||
appearance?: 'danger' | 'warning',
|
||||
classes: Object,
|
||||
heading: string,
|
||||
hideCloseIconButton: boolean,
|
||||
onClose: Function,
|
||||
|
@ -39,6 +42,40 @@ type Props = {
|
|||
t: Function
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the styles for the component.
|
||||
*
|
||||
* @param {Object} theme - The current UI theme.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
const styles = theme => {
|
||||
return {
|
||||
closeButton: {
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
cursor: 'pointer',
|
||||
padding: 13,
|
||||
|
||||
[theme.breakpoints.down('480')]: {
|
||||
background: theme.palette.action02
|
||||
},
|
||||
|
||||
'&:hover': {
|
||||
background: theme.palette.action02
|
||||
}
|
||||
},
|
||||
header: {
|
||||
boxShadow: 'none',
|
||||
|
||||
'& h4': {
|
||||
...withPixelLineHeight(theme.typography.heading5),
|
||||
color: theme.palette.text01
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A default header for modal-dialog components
|
||||
*
|
||||
|
@ -90,6 +127,7 @@ class ModalHeader extends React.Component<Props> {
|
|||
const {
|
||||
id,
|
||||
appearance,
|
||||
classes,
|
||||
heading,
|
||||
hideCloseIconButton,
|
||||
onClose,
|
||||
|
@ -104,7 +142,9 @@ class ModalHeader extends React.Component<Props> {
|
|||
}
|
||||
|
||||
return (
|
||||
<Header showKeyline = { showKeyline }>
|
||||
<Header
|
||||
className = { classes.header }
|
||||
showKeyline = { showKeyline }>
|
||||
<Title>
|
||||
<TitleIcon appearance = { appearance } />
|
||||
<TitleText
|
||||
|
@ -116,16 +156,21 @@ class ModalHeader extends React.Component<Props> {
|
|||
</Title>
|
||||
|
||||
{
|
||||
!hideCloseIconButton && <Icon
|
||||
ariaLabel = { t('dialog.close') }
|
||||
onClick = { onClose }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'
|
||||
src = { IconClose }
|
||||
tabIndex = { 0 } />
|
||||
!hideCloseIconButton
|
||||
&& <div
|
||||
className = { classes.closeButton }
|
||||
id = 'modal-header-close-button'
|
||||
onClick = { onClose }>
|
||||
<Icon
|
||||
ariaLabel = { t('dialog.close') }
|
||||
onKeyPress = { this._onKeyPress }
|
||||
role = 'button'
|
||||
src = { IconClose }
|
||||
tabIndex = { 0 } />
|
||||
</div>
|
||||
}
|
||||
</Header>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default translate(ModalHeader);
|
||||
export default translate(withStyles(styles)(ModalHeader));
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import ButtonGroup from '@atlaskit/button/button-group';
|
||||
import Button from '@atlaskit/button/standard-button';
|
||||
import Modal, { ModalFooter } from '@atlaskit/modal-dialog';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
|
@ -30,6 +31,11 @@ const OK_BUTTON_ID = 'modal-dialog-ok-button';
|
|||
*/
|
||||
type Props = DialogProps & {
|
||||
|
||||
/**
|
||||
* An object containing the CSS classes.
|
||||
*/
|
||||
classes: Object,
|
||||
|
||||
/**
|
||||
* Custom dialog header that replaces the standard heading.
|
||||
*/
|
||||
|
@ -101,6 +107,19 @@ type Props = DialogProps & {
|
|||
width: string
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the styles for the component.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
const styles = () => {
|
||||
return {
|
||||
footer: {
|
||||
boxShadow: 'none'
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Web dialog that uses atlaskit modal-dialog to display dialogs.
|
||||
*/
|
||||
|
@ -205,7 +224,9 @@ class StatelessDialog extends Component<Props> {
|
|||
}
|
||||
|
||||
return (
|
||||
<ModalFooter showKeyline = { propsFromModalFooter.showKeyline } >
|
||||
<ModalFooter
|
||||
className = { this.props.classes.footer }
|
||||
showKeyline = { propsFromModalFooter.showKeyline } >
|
||||
{
|
||||
|
||||
/**
|
||||
|
@ -367,4 +388,4 @@ class StatelessDialog extends Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
export default translate(StatelessDialog);
|
||||
export default translate(withStyles(styles)(StatelessDialog));
|
||||
|
|
|
@ -9,5 +9,11 @@ export default createWebTheme({
|
|||
colorMap,
|
||||
spacing,
|
||||
shape,
|
||||
typography
|
||||
typography,
|
||||
breakpoints: {
|
||||
values: {
|
||||
'0': 0,
|
||||
'480': 480
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
// @flow
|
||||
|
||||
import { createStyles, makeStyles } from '@material-ui/core';
|
||||
|
||||
import { commonStyles, getGlobalStyles } from '../constants';
|
||||
import { formatCommonClasses } from '../functions';
|
||||
|
||||
/**
|
||||
* Creates all the global styles.
|
||||
*
|
||||
* @param {Object} theme - The current UI theme.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
const useStyles = makeStyles(theme =>
|
||||
createStyles({
|
||||
'@global': {
|
||||
...formatCommonClasses(commonStyles),
|
||||
...getGlobalStyles(theme)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* A component generating all the global styles.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
function GlobalStyles() {
|
||||
useStyles();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default GlobalStyles;
|
|
@ -0,0 +1,41 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* An object containing all the class names for common CSS.
|
||||
* Add a new name here and the styles to {@code commonStyles} object.
|
||||
*
|
||||
*/
|
||||
export const commonClassName = {
|
||||
emptyList: 'empty-list'
|
||||
};
|
||||
|
||||
/**
|
||||
* An object containing the declaration of the common, reusable CSS classes.
|
||||
*/
|
||||
export const commonStyles = {
|
||||
// '.empty-list'
|
||||
[commonClassName.emptyList]: {
|
||||
listStyleType: 'none',
|
||||
margin: 0,
|
||||
padding: 0
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the global styles.
|
||||
*
|
||||
* @param {Object} theme - The Jitsi theme.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const getGlobalStyles = (theme: Object) => {
|
||||
return {
|
||||
// @atlaskit/modal-dialog OVERRIDES
|
||||
'.atlaskit-portal div[role=dialog]': {
|
||||
// override dialog background
|
||||
'& > div': {
|
||||
background: theme.palette.ui02,
|
||||
color: theme.palette.text01
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
|
@ -30,3 +30,19 @@ export function createWebTheme({ font, colors, colorMap, shape, spacing, typogra
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the common styles object to be interpreted as proper CSS.
|
||||
*
|
||||
* @param {Object} stylesObj - The styles object.
|
||||
* @returns {Object}
|
||||
*/
|
||||
export function formatCommonClasses(stylesObj: Object) {
|
||||
const formatted = {};
|
||||
|
||||
for (const [ key, value ] of Object.entries(stylesObj)) {
|
||||
formatted[`.${key}`] = value;
|
||||
}
|
||||
|
||||
return formatted;
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ const useStyles = makeStyles(() => {
|
|||
return {
|
||||
speakerStatsSearch: {
|
||||
position: 'absolute',
|
||||
right: '50px',
|
||||
top: '-5px'
|
||||
right: '80px',
|
||||
top: '8px'
|
||||
}
|
||||
};
|
||||
});
|
||||
|
@ -70,4 +70,3 @@ function SpeakerStatsSearch({ onSearch }: Props) {
|
|||
}
|
||||
|
||||
export default SpeakerStatsSearch;
|
||||
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
// @flow
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
import { commonClassName } from '../../base/ui/constants';
|
||||
|
||||
type Props = {
|
||||
|
||||
/**
|
||||
* The 'aria-label' text.
|
||||
*/
|
||||
ariaLabel: string,
|
||||
|
||||
/**
|
||||
* The maximum value for slider value.
|
||||
*/
|
||||
max: number,
|
||||
|
||||
/**
|
||||
* The minimum value for slider value.
|
||||
*/
|
||||
min: number,
|
||||
|
||||
/**
|
||||
* Callback invoked on change.
|
||||
*/
|
||||
onChange: Function,
|
||||
|
||||
/**
|
||||
* The granularity that the value must adhere to.
|
||||
*/
|
||||
step: number,
|
||||
|
||||
/**
|
||||
* The current value where the knob is positioned.
|
||||
*/
|
||||
value: number
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
// keep the same hight for all elements:
|
||||
// input, input track & fake track(div)
|
||||
const height = 6;
|
||||
|
||||
const inputTrack = {
|
||||
background: 'transparent',
|
||||
height
|
||||
};
|
||||
const inputThumb = {
|
||||
background: theme.palette.text01,
|
||||
border: 0,
|
||||
borderRadius: '50%',
|
||||
height: 24,
|
||||
width: 24
|
||||
};
|
||||
|
||||
const focused = {
|
||||
outline: `1px solid ${theme.palette.action03Focus}`
|
||||
};
|
||||
|
||||
return {
|
||||
sliderContainer: {
|
||||
cursor: 'pointer',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
textAlign: 'center'
|
||||
|
||||
},
|
||||
knobContainer: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginLeft: 2,
|
||||
marginRight: 2,
|
||||
position: 'absolute',
|
||||
width: '100%'
|
||||
},
|
||||
knob: {
|
||||
background: theme.palette.text01,
|
||||
borderRadius: '50%',
|
||||
display: 'inline-block',
|
||||
height,
|
||||
width: 6
|
||||
},
|
||||
track: {
|
||||
background: theme.palette.ui02,
|
||||
borderRadius: theme.shape.borderRadius / 2,
|
||||
height
|
||||
},
|
||||
slider: {
|
||||
// Use an additional class here to override global CSS specificity
|
||||
'&.custom-slider': {
|
||||
'-webkit-appearance': 'none',
|
||||
background: 'transparent',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
width: '100%',
|
||||
|
||||
'&:focus': {
|
||||
// override global styles in order to use our own color
|
||||
outline: 'none !important',
|
||||
|
||||
'&::-webkit-slider-runnable-track': focused,
|
||||
'&::ms-track': focused,
|
||||
'&::-moz-range-track': focused
|
||||
},
|
||||
|
||||
'&::-webkit-slider-runnable-track': {
|
||||
'-webkit-appearance': 'none',
|
||||
...inputTrack
|
||||
},
|
||||
'&::-webkit-slider-thumb': {
|
||||
'-webkit-appearance': 'none',
|
||||
position: 'relative',
|
||||
top: -6,
|
||||
...inputThumb
|
||||
},
|
||||
|
||||
'&::ms-track': {
|
||||
...inputTrack
|
||||
},
|
||||
'&::-ms-thumb': {
|
||||
...inputThumb
|
||||
},
|
||||
|
||||
'&::-moz-range-track': {
|
||||
...inputTrack
|
||||
},
|
||||
'&::-moz-range-thumb': {
|
||||
...inputThumb
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Custom slider.
|
||||
*
|
||||
* @returns {ReactElement}
|
||||
*/
|
||||
function Slider({ ariaLabel, max, min, onChange, step, value }: Props) {
|
||||
const classes = useStyles();
|
||||
const knobs = [ ...Array(Math.floor((max - min) / step) + 1) ];
|
||||
|
||||
return (
|
||||
<div className = { classes.sliderContainer }>
|
||||
<ul className = { clsx(commonClassName.emptyList, classes.knobContainer) }>
|
||||
{knobs.map((_, i) => (
|
||||
<li
|
||||
className = { classes.knob }
|
||||
key = { `knob-${i}` } />))}
|
||||
</ul>
|
||||
<div className = { classes.track } />
|
||||
<input
|
||||
aria-label = { ariaLabel }
|
||||
className = { clsx(classes.slider, 'custom-slider') }
|
||||
max = { max }
|
||||
min = { min }
|
||||
onChange = { onChange }
|
||||
step = { step }
|
||||
type = 'range'
|
||||
value = { value } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Slider;
|
|
@ -21,8 +21,8 @@ export default class VideoQualityDialog extends Component {
|
|||
return (
|
||||
<Dialog
|
||||
hideCancelButton = { true }
|
||||
okKey = 'dialog.done'
|
||||
titleKey = 'videoStatus.callQuality'
|
||||
submitDisabled = { true }
|
||||
titleKey = 'videoStatus.performanceSettings'
|
||||
width = 'small'>
|
||||
<VideoQualitySlider />
|
||||
</Dialog>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import clsx from 'clsx';
|
||||
import React, { Component } from 'react';
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
|
@ -8,10 +9,13 @@ import { setAudioOnly } from '../../base/audio-only';
|
|||
import { translate } from '../../base/i18n';
|
||||
import { setLastN, getLastNForQualityLevel } from '../../base/lastn';
|
||||
import { connect } from '../../base/redux';
|
||||
import { withPixelLineHeight } from '../../base/styles/functions.web';
|
||||
import { setPreferredVideoQuality } from '../actions';
|
||||
import { DEFAULT_LAST_N, VIDEO_QUALITY_LEVELS } from '../constants';
|
||||
import logger from '../logger';
|
||||
|
||||
import Slider from './Slider';
|
||||
|
||||
const {
|
||||
ULTRA,
|
||||
HIGH,
|
||||
|
@ -61,6 +65,11 @@ type Props = {
|
|||
*/
|
||||
_sendrecvVideoQuality: Number,
|
||||
|
||||
/**
|
||||
* An object containing the CSS classes.
|
||||
*/
|
||||
classes: Object,
|
||||
|
||||
/**
|
||||
* Invoked to request toggling of audio only mode.
|
||||
*/
|
||||
|
@ -72,6 +81,38 @@ type Props = {
|
|||
t: Function
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates the styles for the component.
|
||||
*
|
||||
* @param {Object} theme - The current UI theme.
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
const styles = theme => {
|
||||
return {
|
||||
dialog: {
|
||||
color: theme.palette.text01
|
||||
},
|
||||
dialogDetails: {
|
||||
...withPixelLineHeight(theme.typography.bodyShortRegularLarge),
|
||||
marginBottom: 16
|
||||
},
|
||||
dialogContents: {
|
||||
background: theme.palette.ui01,
|
||||
padding: '16px 16px 48px 16px'
|
||||
},
|
||||
sliderDescription: {
|
||||
...withPixelLineHeight(theme.typography.heading6),
|
||||
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginBottom: 40
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a slider for selecting a
|
||||
* new receive video quality.
|
||||
|
@ -139,76 +180,29 @@ class VideoQualitySlider extends Component<Props> {
|
|||
* @returns {ReactElement}
|
||||
*/
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { classes, t } = this.props;
|
||||
const activeSliderOption = this._mapCurrentQualityToSliderValue();
|
||||
|
||||
return (
|
||||
<div className = { 'video-quality-dialog' }>
|
||||
<h3 className = 'video-quality-dialog-title'>
|
||||
{ t('videoStatus.callQuality') }
|
||||
</h3>
|
||||
<div className = 'video-quality-dialog-contents'>
|
||||
<div className = 'video-quality-dialog-slider-container'>
|
||||
{ /* FIXME: onChange and onMouseUp are both used for
|
||||
* compatibility with IE11. This workaround can be
|
||||
* removed after upgrading to React 16.
|
||||
*/ }
|
||||
<input
|
||||
aria-label = { t('videoStatus.callQuality') }
|
||||
className = 'video-quality-dialog-slider'
|
||||
max = { this._sliderOptions.length - 1 }
|
||||
min = '0'
|
||||
onChange = { this._onSliderChange }
|
||||
onMouseUp = { this._onSliderChange }
|
||||
step = '1'
|
||||
type = 'range'
|
||||
value
|
||||
= { activeSliderOption } />
|
||||
|
||||
</div>
|
||||
<div className = 'video-quality-dialog-labels'>
|
||||
{ this._createLabels(activeSliderOption) }
|
||||
<div className = { clsx('video-quality-dialog', classes.dialog) }>
|
||||
<div className = { classes.dialogDetails }>{t('videoStatus.adjustFor')}</div>
|
||||
<div className = { classes.dialogContents }>
|
||||
<div className = { classes.sliderDescription }>
|
||||
<span>{t('videoStatus.bestPerformance')}</span>
|
||||
<span>{t('videoStatus.highestQuality')}</span>
|
||||
</div>
|
||||
<Slider
|
||||
ariaLabel = { t('videoStatus.callQuality') }
|
||||
max = { this._sliderOptions.length - 1 }
|
||||
min = { 0 }
|
||||
onChange = { this._onSliderChange }
|
||||
step = { 1 }
|
||||
value = { activeSliderOption } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates React Elements to display mock tick marks with associated labels.
|
||||
*
|
||||
* @param {number} activeLabelIndex - Which of the sliderOptions should
|
||||
* display as currently active.
|
||||
* @private
|
||||
* @returns {ReactElement[]}
|
||||
*/
|
||||
_createLabels(activeLabelIndex) {
|
||||
const labelsCount = this._sliderOptions.length;
|
||||
const maxWidthOfLabel = `${100 / labelsCount}%`;
|
||||
|
||||
return this._sliderOptions.map((sliderOption, index) => {
|
||||
const style = {
|
||||
maxWidth: maxWidthOfLabel,
|
||||
left: `${(index * 100) / (labelsCount - 1)}%`
|
||||
};
|
||||
|
||||
const isActiveClass = activeLabelIndex === index ? 'active' : '';
|
||||
const className
|
||||
= `video-quality-dialog-label-container ${isActiveClass}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className = { className }
|
||||
key = { index }
|
||||
style = { style }>
|
||||
<div className = 'video-quality-dialog-label'>
|
||||
{ this.props.t(sliderOption.textKey) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
_enableAudioOnly: () => void;
|
||||
|
||||
/**
|
||||
|
@ -386,4 +380,4 @@ function _mapStateToProps(state) {
|
|||
};
|
||||
}
|
||||
|
||||
export default translate(connect(_mapStateToProps)(VideoQualitySlider));
|
||||
export default translate(connect(_mapStateToProps)(withStyles(styles)(VideoQualitySlider)));
|
||||
|
|
Loading…
Reference in New Issue