Merge pull request #15930 from KDSBrowne/26-con-status-a11y
Connection Status Modal A11y Updates
This commit is contained in:
commit
bdf8dad8ee
@ -158,7 +158,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
|
||||
this.help = Service.getHelp();
|
||||
this.state = {
|
||||
selectedTab: '1',
|
||||
selectedTab: 0,
|
||||
dataPage: '1',
|
||||
dataSaving: props.dataSaving,
|
||||
hasNetworkData: false,
|
||||
@ -187,6 +187,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
this.audioDownloadLabel = intl.formatMessage(intlMessages.audioDownloadRate);
|
||||
this.videoUploadLabel = intl.formatMessage(intlMessages.videoUploadRate);
|
||||
this.videoDownloadLabel = intl.formatMessage(intlMessages.videoDownloadRate);
|
||||
this.handleSelectTab = this.handleSelectTab.bind(this);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@ -197,12 +198,24 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
Meteor.clearInterval(this.rateInterval);
|
||||
}
|
||||
|
||||
handleSelectTab(tab) {
|
||||
this.setState({
|
||||
selectedTab: tab,
|
||||
});
|
||||
}
|
||||
|
||||
handleDataSavingChange(key) {
|
||||
const { dataSaving } = this.state;
|
||||
dataSaving[key] = !dataSaving[key];
|
||||
this.setState(dataSaving);
|
||||
}
|
||||
|
||||
setButtonMessage(msg) {
|
||||
this.setState({
|
||||
copyButtonText: msg,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring the network data.
|
||||
* @return {Promise} A Promise that resolves when process started.
|
||||
@ -262,6 +275,43 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
}, NETWORK_MONITORING_INTERVAL_MS);
|
||||
}
|
||||
|
||||
displaySettingsStatus(status) {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<Styled.ToggleLabel>
|
||||
{status ? intl.formatMessage(intlMessages.on)
|
||||
: intl.formatMessage(intlMessages.off)}
|
||||
</Styled.ToggleLabel>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy network data to clipboard
|
||||
* @return {Promise} A Promise that is resolved after data is copied.
|
||||
*
|
||||
*
|
||||
*/
|
||||
async copyNetworkData() {
|
||||
const { intl } = this.props;
|
||||
const {
|
||||
networkData,
|
||||
hasNetworkData,
|
||||
} = this.state;
|
||||
|
||||
if (!hasNetworkData) return;
|
||||
|
||||
this.setButtonMessage(intl.formatMessage(intlMessages.copied));
|
||||
|
||||
const data = JSON.stringify(networkData, null, 2);
|
||||
|
||||
await navigator.clipboard.writeText(data);
|
||||
|
||||
this.copyNetworkDataTimeout = setTimeout(() => {
|
||||
this.setButtonMessage(intl.formatMessage(intlMessages.copy));
|
||||
}, MIN_TIMEOUT);
|
||||
}
|
||||
|
||||
renderEmpty() {
|
||||
const { intl } = this.props;
|
||||
|
||||
@ -278,52 +328,6 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
displaySettingsStatus(status) {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<Styled.ToggleLabel>
|
||||
{status ? intl.formatMessage(intlMessages.on)
|
||||
: intl.formatMessage(intlMessages.off)}
|
||||
</Styled.ToggleLabel>
|
||||
);
|
||||
}
|
||||
|
||||
setButtonMessage(msg) {
|
||||
this.setState({
|
||||
copyButtonText: msg,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy network data to clipboard
|
||||
* @param {Object} e Event object from click event
|
||||
* @return {Promise} A Promise that is resolved after data is copied.
|
||||
*
|
||||
*
|
||||
*/
|
||||
async copyNetworkData(e) {
|
||||
const { intl } = this.props;
|
||||
const {
|
||||
networkData,
|
||||
hasNetworkData,
|
||||
} = this.state;
|
||||
|
||||
if (!hasNetworkData) return;
|
||||
|
||||
const { target: copyButton } = e;
|
||||
|
||||
this.setButtonMessage(intl.formatMessage(intlMessages.copied));
|
||||
|
||||
const data = JSON.stringify(networkData, null, 2);
|
||||
|
||||
await navigator.clipboard.writeText(data);
|
||||
|
||||
this.copyNetworkDataTimeout = setTimeout(() => {
|
||||
this.setButtonMessage(intl.formatMessage(intlMessages.copy));
|
||||
}, MIN_TIMEOUT);
|
||||
}
|
||||
|
||||
renderConnections() {
|
||||
const {
|
||||
connectionStatus,
|
||||
@ -345,7 +349,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
|
||||
return (
|
||||
<Styled.Item
|
||||
key={index}
|
||||
key={`${conn?.name}-${dateTime}`}
|
||||
last={(index + 1) === connections.length}
|
||||
data-test="connectionStatusItemUser"
|
||||
>
|
||||
@ -521,6 +525,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
<Styled.Prev>
|
||||
<Styled.ButtonLeft
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
disabled={dataPage === '1'}
|
||||
aria-label={`${intl.formatMessage(intlMessages.prev)} ${intl.formatMessage(intlMessages.ariaTitle)}`}
|
||||
onClick={handlePaginationClick.bind(this, 'prev')}
|
||||
@ -585,6 +590,7 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
<Styled.Next>
|
||||
<Styled.ButtonRight
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
disabled={dataPage === '2'}
|
||||
aria-label={`${intl.formatMessage(intlMessages.next)} ${intl.formatMessage(intlMessages.ariaTitle)}`}
|
||||
onClick={handlePaginationClick.bind(this, 'next')}
|
||||
@ -619,81 +625,23 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { intl } = this.props;
|
||||
|
||||
const { hasNetworkData } = this.state;
|
||||
const { hasNetworkData, copyButtonText } = this.state;
|
||||
return (
|
||||
<Styled.CopyContainer aria-live="polite">
|
||||
<Styled.Copy
|
||||
disabled={!hasNetworkData}
|
||||
role="button"
|
||||
data-test="copyStats"
|
||||
data-test="copyStats"
|
||||
onClick={this.copyNetworkData.bind(this)}
|
||||
onKeyPress={this.copyNetworkData.bind(this)}
|
||||
tabIndex={0}
|
||||
>
|
||||
{this.state.copyButtonText}
|
||||
{copyButtonText}
|
||||
</Styled.Copy>
|
||||
</Styled.CopyContainer>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The navigation bar.
|
||||
* @returns {Object} The component to be renderized.
|
||||
*/
|
||||
renderNavigation() {
|
||||
const { intl } = this.props;
|
||||
|
||||
const handleTabClick = (event) => {
|
||||
const activeTabElement = document.querySelector('.activeConnectionStatusTab');
|
||||
const { target } = event;
|
||||
|
||||
if (activeTabElement) {
|
||||
activeTabElement.classList.remove('activeConnectionStatusTab');
|
||||
}
|
||||
|
||||
target.classList.add('activeConnectionStatusTab');
|
||||
this.setState({
|
||||
selectedTab: target.dataset.tab,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Styled.Navigation>
|
||||
<div
|
||||
data-tab="1"
|
||||
className="activeConnectionStatusTab"
|
||||
onClick={handleTabClick}
|
||||
onKeyDown={handleTabClick}
|
||||
role="button"
|
||||
>
|
||||
{intl.formatMessage(intlMessages.connectionStats)}
|
||||
</div>
|
||||
<div
|
||||
data-tab="2"
|
||||
onClick={handleTabClick}
|
||||
onKeyDown={handleTabClick}
|
||||
role="button"
|
||||
>
|
||||
{intl.formatMessage(intlMessages.myLogs)}
|
||||
</div>
|
||||
{Service.isModerator()
|
||||
&& (
|
||||
<div
|
||||
data-tab="3"
|
||||
onClick={handleTabClick}
|
||||
onKeyDown={handleTabClick}
|
||||
role="button"
|
||||
>
|
||||
{intl.formatMessage(intlMessages.sessionLogs)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Styled.Navigation>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
closeModal,
|
||||
@ -715,18 +663,43 @@ class ConnectionStatusComponent extends PureComponent {
|
||||
{intl.formatMessage(intlMessages.title)}
|
||||
</Styled.Title>
|
||||
</Styled.Header>
|
||||
{this.renderNavigation()}
|
||||
<Styled.Main>
|
||||
<Styled.Body>
|
||||
{selectedTab === '1'
|
||||
? this.renderNetworkData()
|
||||
: this.renderConnections()
|
||||
|
||||
<Styled.ConnectionTabs
|
||||
onSelect={this.handleSelectTab}
|
||||
selectedIndex={selectedTab}
|
||||
>
|
||||
<Styled.ConnectionTabList>
|
||||
<Styled.ConnectionTabSelector selectedClassName="is-selected">
|
||||
<span id="connection-status-tab">{intl.formatMessage(intlMessages.title)}</span>
|
||||
</Styled.ConnectionTabSelector>
|
||||
<Styled.ConnectionTabSelector selectedClassName="is-selected">
|
||||
<span id="my-logs-tab">{intl.formatMessage(intlMessages.myLogs)}</span>
|
||||
</Styled.ConnectionTabSelector>
|
||||
{Service.isModerator()
|
||||
&& (
|
||||
<Styled.ConnectionTabSelector selectedClassName="is-selected">
|
||||
<span id="session-logs-tab">{intl.formatMessage(intlMessages.sessionLogs)}</span>
|
||||
</Styled.ConnectionTabSelector>
|
||||
)
|
||||
}
|
||||
</Styled.Body>
|
||||
{selectedTab === '1' &&
|
||||
this.renderCopyDataButton()
|
||||
</Styled.ConnectionTabList>
|
||||
<Styled.ConnectionTabPanel selectedClassName="is-selected">
|
||||
<div>
|
||||
{this.renderNetworkData()}
|
||||
{this.renderCopyDataButton()}
|
||||
</div>
|
||||
</Styled.ConnectionTabPanel>
|
||||
<Styled.ConnectionTabPanel selectedClassName="is-selected">
|
||||
<div>{this.renderConnections()}</div>
|
||||
</Styled.ConnectionTabPanel>
|
||||
{Service.isModerator()
|
||||
&& (
|
||||
<Styled.ConnectionTabPanel selectedClassName="is-selected">
|
||||
<div>{this.renderConnections()}</div>
|
||||
</Styled.ConnectionTabPanel>
|
||||
)
|
||||
}
|
||||
</Styled.Main>
|
||||
</Styled.ConnectionTabs>
|
||||
</Styled.Container>
|
||||
</Styled.ConnectionStatusModal>
|
||||
);
|
||||
|
@ -7,12 +7,13 @@ import {
|
||||
colorGrayLabel,
|
||||
colorGrayLightest,
|
||||
colorPrimary,
|
||||
colorWhite,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
import {
|
||||
smPaddingX,
|
||||
smPaddingY,
|
||||
mdPaddingY,
|
||||
lgPaddingY,
|
||||
lgPaddingX,
|
||||
titlePositionLeft,
|
||||
mdPaddingX,
|
||||
borderSizeLarge,
|
||||
@ -26,7 +27,11 @@ import {
|
||||
hasPhoneDimentions,
|
||||
mediumDown,
|
||||
hasPhoneWidth,
|
||||
smallOnly,
|
||||
} from '/imports/ui/stylesheets/styled-components/breakpoints';
|
||||
import {
|
||||
Tab, Tabs, TabList, TabPanel,
|
||||
} from 'react-tabs';
|
||||
|
||||
const Item = styled.div`
|
||||
display: flex;
|
||||
@ -173,6 +178,7 @@ const NetworkDataContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
padding-bottom: 1.25rem;
|
||||
|
||||
@media ${mediumDown} {
|
||||
justify-content: space-between;
|
||||
@ -202,6 +208,8 @@ const CopyContainer = styled.div`
|
||||
|
||||
const ConnectionStatusModal = styled(Modal)`
|
||||
padding: 1rem;
|
||||
height: 28rem;
|
||||
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
@ -271,11 +279,12 @@ const Helper = styled.div`
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
|
||||
@media ${mediumDown} {
|
||||
${({ page }) => page === '1'
|
||||
${({ page }) => (page === '1'
|
||||
? 'display: flex;'
|
||||
: 'display: none;'}
|
||||
: 'display: none;')}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -286,9 +295,9 @@ const NetworkDataContent = styled.div`
|
||||
flex-grow: 1;
|
||||
|
||||
@media ${mediumDown} {
|
||||
${({ page }) => page === '2'
|
||||
${({ page }) => (page === '2'
|
||||
? 'display: flex;'
|
||||
: 'display: none;'}
|
||||
: 'display: none;')}
|
||||
}
|
||||
`;
|
||||
|
||||
@ -316,42 +325,6 @@ const Body = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const Navigation = styled.div`
|
||||
display: flex;
|
||||
border: none;
|
||||
border-bottom: 1px solid ${colorOffWhite};
|
||||
user-select: none;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& :not(:last-child) {
|
||||
margin: 0;
|
||||
margin-right: ${lgPaddingX};
|
||||
}
|
||||
|
||||
.activeConnectionStatusTab {
|
||||
border: none;
|
||||
border-bottom: 2px solid ${colorPrimary};
|
||||
color: ${colorPrimary};
|
||||
}
|
||||
|
||||
& * {
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[dir="rtl"] & {
|
||||
& :not(:last-child) {
|
||||
margin: 0;
|
||||
margin-left: ${lgPaddingX};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Prev = styled.div`
|
||||
display: none;
|
||||
margin: 0 .5rem 0 .25rem;
|
||||
@ -394,8 +367,9 @@ const Button = styled.button`
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
outline: none;
|
||||
outline: 2px solid ${colorPrimary};
|
||||
}
|
||||
|
||||
@media ${hasPhoneWidth} {
|
||||
@ -431,6 +405,106 @@ const Chevron = styled.svg`
|
||||
}
|
||||
`;
|
||||
|
||||
const ConnectionTabs = styled(Tabs)`
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
@media ${smallOnly} {
|
||||
width: 100%;
|
||||
flex-flow: column;
|
||||
}
|
||||
`;
|
||||
|
||||
const ConnectionTabList = styled(TabList)`
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
margin: 0;
|
||||
margin-bottom: .5rem;
|
||||
border: none;
|
||||
padding: 0;
|
||||
width: calc(100% / 3);
|
||||
|
||||
@media ${smallOnly} {
|
||||
width: 100%;
|
||||
flex-flow: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const ConnectionTabPanel = styled(TabPanel)`
|
||||
display: none;
|
||||
margin: 0 0 0 1rem;
|
||||
height: 13rem;
|
||||
|
||||
[dir="rtl"] & {
|
||||
margin: 0 1rem 0 0;
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
}
|
||||
|
||||
@media ${smallOnly} {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const ConnectionTabSelector = styled(Tab)`
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
font-size: 0.9rem;
|
||||
flex: 0 0 auto;
|
||||
justify-content: flex-start;
|
||||
border: none !important;
|
||||
padding: ${mdPaddingY} ${mdPaddingX};
|
||||
|
||||
border-radius: .2rem;
|
||||
cursor: pointer;
|
||||
margin-bottom: ${smPaddingY};
|
||||
align-items: center;
|
||||
flex-grow: 0;
|
||||
min-width: 0;
|
||||
|
||||
& > span {
|
||||
min-width: 0;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media ${smallOnly} {
|
||||
max-width: 100%;
|
||||
margin: 0 ${smPaddingX} 0 0;
|
||||
& > i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[dir="rtl"] & {
|
||||
margin: 0 0 0 ${smPaddingX};
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
border-bottom: 2px solid ${colorWhite};
|
||||
}
|
||||
|
||||
&.is-selected {
|
||||
border: none;
|
||||
color: ${colorPrimary};
|
||||
|
||||
span {
|
||||
border-bottom: 2px solid ${colorPrimary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
Item,
|
||||
Left,
|
||||
@ -463,7 +537,6 @@ export default {
|
||||
NetworkDataContent,
|
||||
Main,
|
||||
Body,
|
||||
Navigation,
|
||||
FullName,
|
||||
DataColumn,
|
||||
Prev,
|
||||
@ -471,4 +544,8 @@ export default {
|
||||
ButtonLeft,
|
||||
ButtonRight,
|
||||
Chevron,
|
||||
ConnectionTabs,
|
||||
ConnectionTabList,
|
||||
ConnectionTabSelector,
|
||||
ConnectionTabPanel,
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user