Improve the front-end part for managing breakout duration

.
This commit is contained in:
Joao Victor 2022-03-01 10:13:23 -03:00
parent ae13fb3593
commit a6f16083b5
8 changed files with 271 additions and 91 deletions

View File

@ -0,0 +1,104 @@
import React, { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import BBBMenu from "/imports/ui/components/common/menu/component";
import Button from '/imports/ui/components/common/button/component';
const intlMessages = defineMessages({
options: {
id: 'app.breakout.dropdown.options',
description: 'Breakout options label',
},
manageDuration: {
id: 'app.breakout.dropdown.manageDuration',
description: 'Manage duration label',
},
destroy: {
id: 'app.breakout.dropdown.destroyAll',
description: 'Destroy breakouts label',
},
});
class BreakoutDropdown extends PureComponent {
constructor(props) {
super(props);
}
getAvailableActions() {
const {
intl,
openBreakoutTimeManager,
endAllBreakouts,
isMeteorConnected,
amIModerator,
} = this.props;
this.menuItems = [];
this.menuItems.push(
{
key: 'breakoutTimeManager',
dataTest: 'openBreakoutTimeManager',
label: intl.formatMessage(intlMessages.manageDuration),
onClick: () => {
openBreakoutTimeManager();
}
}
);
if (amIModerator) {
this.menuItems.push(
{
key: 'endAllBreakouts',
dataTest: 'endAllBreakouts',
label: intl.formatMessage(intlMessages.destroy),
disabled: !isMeteorConnected,
onClick: () => {
endAllBreakouts();
}
}
);
}
return this.menuItems;
}
render() {
const {
intl,
} = this.props;
return (
<>
<BBBMenu
trigger={
<Button
data-test="breakoutOptionsMenu"
icon="more"
size="sm"
ghost
circle
hideLabel
color="dark"
label={intl.formatMessage(intlMessages.options)}
aria-label={intl.formatMessage(intlMessages.options)}
onClick={() => null}
/>
}
opts={{
id: "default-dropdown-menu",
keepMounted: true,
transitionDuration: 0,
elevation: 3,
getContentAnchorEl: null,
fullwidth: "true",
anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
transformorigin: { vertical: 'bottom', horizontal: 'left' },
}}
actions={this.getAvailableActions()}
/>
</>
);
}
}
export default injectIntl(BreakoutDropdown);

View File

@ -5,6 +5,7 @@ import injectNotify from '/imports/ui/components/common/toast/inject-notify/comp
import humanizeSeconds from '/imports/utils/humanizeSeconds';
import _ from 'lodash';
import BreakoutRemainingTimeComponent from './component';
import { Text, Time } from './styles';
const intlMessages = defineMessages({
failedMessage: {
@ -47,10 +48,19 @@ class breakoutRemainingTimeContainer extends React.Component {
}
render() {
const { message } = this.props;
const { message, bold } = this.props;
if (_.isEmpty(message)) {
return null;
}
if (bold) {
return (
<BreakoutRemainingTimeComponent>
<Text>{message.split(' ')[0]}</Text>
<br />
<Time>{message.split(' ')[1]}</Time>
</BreakoutRemainingTimeComponent>
);
}
return (
<BreakoutRemainingTimeComponent>
{message}
@ -87,6 +97,7 @@ export default injectNotify(injectIntl(withTracker(({
timeEndedMessage,
alertMessage,
alertUnderMinutes,
fromBreakoutPanel,
}) => {
const data = {};
if (breakoutRoom) {
@ -115,6 +126,7 @@ export default injectNotify(injectIntl(withTracker(({
notify(alertMessage, 'info', 'rooms');
}
data.message = intl.formatMessage(messageDuration, { 0: humanizeSeconds(time) });
if (fromBreakoutPanel) data.bold = true;
} else {
clearInterval(timeRemainingInterval);
data.message = intl.formatMessage(timeEndedMessage || intlMessages.breakoutWillClose);

View File

@ -0,0 +1,26 @@
import styled from 'styled-components';
import {
colorGray,
} from '/imports/ui/stylesheets/styled-components/palette';
import {
fontSizeSmaller,
fontSizeXL,
} from '/imports/ui/stylesheets/styled-components/typography';
const Text = styled.span`
text-transform: uppercase;
color: ${colorGray};
font-size: ${fontSizeSmaller};
font-weight: 600;
`;
const Time = styled.span`
font-size: ${fontSizeXL};
font-weight: 600;
color: ${colorGray};
`;
export {
Text,
Time,
};

View File

@ -13,6 +13,7 @@ import { PANELS, ACTIONS } from '../layout/enums';
import { screenshareHasEnded } from '/imports/ui/components/screenshare/service';
import AudioManager from '/imports/ui/services/audio-manager';
import Settings from '/imports/ui/services/settings';
import BreakoutDropdown from '/imports/ui/components/breakout-room/breakout-dropdown/component';
const intlMessages = defineMessages({
breakoutTitle: {
@ -110,6 +111,7 @@ class BreakoutRoom extends PureComponent {
this.renderUserActions = this.renderUserActions.bind(this);
this.returnBackToMeeeting = this.returnBackToMeeeting.bind(this);
this.closePanel = this.closePanel.bind(this);
this.handleClickOutsideDurationContainer = this.handleClickOutsideDurationContainer.bind(this);
this.state = {
requestedBreakoutId: '',
waiting: false,
@ -224,8 +226,19 @@ class BreakoutRoom extends PureComponent {
this.setState({ newTime: newSetTime >= 0 ? newSetTime : 0 });
}
handleClickOutsideDurationContainer(e) {
if (this.durationContainerRef) {
const { x, right, y, bottom } = this.durationContainerRef.getBoundingClientRect();
if (e.clientX < x || e.clientX > right || e.clientY < y || e.clientY > bottom) {
this.resetSetTimeForm();
}
}
}
showSetTimeForm() {
this.setState({ visibleSetTimeForm: true });
window.addEventListener('click', this.handleClickOutsideDurationContainer);
}
showSetTimeHigherThanMeetingTimeError(show) {
@ -234,6 +247,7 @@ class BreakoutRoom extends PureComponent {
resetSetTimeForm() {
this.setState({ visibleSetTimeForm: false, newTime: 5 });
window.removeEventListener('click', this.handleClickOutsideDurationContainer);
}
transferUserToBreakoutRoom(breakoutId) {
@ -444,75 +458,57 @@ class BreakoutRoom extends PureComponent {
visibleSetTimeHigherThanMeetingTimeError,
} = this.state;
return (
<Styled.DurationContainer>
<Styled.DurationContainer
centeredText={!visibleSetTimeForm}
ref={(ref) => this.durationContainerRef = ref}
>
<Styled.Duration>
<BreakoutRoomContainer
messageDuration={intlMessages.breakoutDuration}
breakoutRoom={breakoutRooms[0]}
fromBreakoutPanel
/>
</Styled.Duration>
{amIModerator && visibleSetTimeForm ? (
<Styled.SetTimeContainer>
<label htmlFor="inputSetTimeSelector" >
{intl.formatMessage(intlMessages.setTimeInMinutes)}
</label>
<br />
<Styled.SetDurationInput
id="inputSetTimeSelector"
type="number"
min="1"
value={newTime}
onChange={this.changeSetTime}
aria-label={intl.formatMessage(intlMessages.setTimeInMinutes)}
/>
<br />
<br />
<Styled.FlexRow>
<Styled.SetDurationInput
id="inputSetTimeSelector"
type="number"
min="1"
value={newTime}
onChange={this.changeSetTime}
aria-label={intl.formatMessage(intlMessages.setTimeInMinutes)}
/>
&nbsp;
&nbsp;
<Styled.EndButton
color="primary"
disabled={!isMeteorConnected}
size="sm"
label={intl.formatMessage(intlMessages.setTimeLabel)}
onClick={() => {
this.showSetTimeHigherThanMeetingTimeError(false);
if (isNewTimeHigherThanMeetingRemaining(newTime)) {
this.showSetTimeHigherThanMeetingTimeError(true);
} else if (setBreakoutsTime(newTime)) {
this.resetSetTimeForm();
}
}}
/>
</Styled.FlexRow>
{visibleSetTimeHigherThanMeetingTimeError ? (
<Styled.WithError>
{intl.formatMessage(intlMessages.setTimeHigherThanMeetingTimeError)}
<br />
<br />
</Styled.WithError>
) : null}
<Styled.EndButton
color="default"
disabled={!isMeteorConnected}
size="sm"
label={intl.formatMessage(intlMessages.setTimeCancel)}
onClick={this.resetSetTimeForm}
/>
<Styled.EndButton
color="primary"
disabled={!isMeteorConnected}
size="sm"
label={intl.formatMessage(intlMessages.setTimeLabel)}
onClick={() => {
this.showSetTimeHigherThanMeetingTimeError(false);
if (isNewTimeHigherThanMeetingRemaining(newTime)) {
this.showSetTimeHigherThanMeetingTimeError(true);
} else if (setBreakoutsTime(newTime)) {
this.resetSetTimeForm();
}
}}
/>
</Styled.SetTimeContainer>
) : null}
<Styled.Duration>
<BreakoutRoomContainer
messageDuration={intlMessages.breakoutDuration}
breakoutRoom={breakoutRooms[0]}
/>
{amIModerator && !visibleSetTimeForm
? (
<Button
onClick={this.showSetTimeForm}
color="default"
icon="more"
circle
hideLabel
size="sm"
label={intl.formatMessage(intlMessages.setTimeInMinutes)}
aria-label={intl.formatMessage(intlMessages.setTimeInMinutes)}
disabled={!isMeteorConnected}
/>
)
: null}
</Styled.Duration>
</Styled.DurationContainer>
);
}
@ -526,14 +522,28 @@ class BreakoutRoom extends PureComponent {
} = this.props;
return (
<Styled.Panel ref={(n) => this.panel = n}>
<Styled.HeaderButton
icon="left_arrow"
label={intl.formatMessage(intlMessages.breakoutTitle)}
aria-label={intl.formatMessage(intlMessages.breakoutAriaTitle)}
onClick={() => {
this.closePanel();
}}
/>
<Styled.Header>
<Styled.HeaderButton
icon="left_arrow"
label={intl.formatMessage(intlMessages.breakoutTitle)}
aria-label={intl.formatMessage(intlMessages.breakoutAriaTitle)}
onClick={() => {
this.closePanel();
}}
/>
{ amIModerator && (
<BreakoutDropdown
openBreakoutTimeManager={this.showSetTimeForm}
endAllBreakouts={() => {
this.closePanel();
endAllBreakouts();
}}
isMeteorConnected={isMeteorConnected}
amIModerator={amIModerator}
/>
) }
</Styled.Header>
{this.renderDuration()}
{amIModerator
? (
<MessageFormContainer
@ -549,23 +559,6 @@ class BreakoutRoom extends PureComponent {
) : null }
{amIModerator ? <Styled.Separator /> : null }
{this.renderBreakoutRooms()}
{this.renderDuration()}
{
amIModerator
? (
<Styled.EndButton
color="primary"
disabled={!isMeteorConnected}
size="lg"
label={intl.formatMessage(intlMessages.endAllBreakouts)}
data-test="endBreakoutRoomsButton"
onClick={() => {
this.closePanel();
endAllBreakouts();
}}
/>
) : null
}
</Styled.Panel>
);
}

View File

@ -47,7 +47,6 @@ const Input = styled(TextareaAutosize)`
min-height: 2.5rem;
max-height: 10rem;
border: 1px solid ${colorGrayLighter};
box-shadow: 0 0 0 1px ${colorGrayLighter};
&:disabled,
&[disabled] {

View File

@ -4,6 +4,8 @@ import {
mdPaddingX,
borderSize,
listItemBgHover, borderSizeSmall,
borderRadius,
jumboPaddingY,
} from '/imports/ui/stylesheets/styled-components/general';
import {
colorPrimary,
@ -11,7 +13,10 @@ import {
colorDanger,
colorGrayDark,
userListBg,
colorWhite, colorGrayLighter,
colorWhite,
colorGrayLighter,
colorGrayLightest,
colorBlueLight
} from '/imports/ui/stylesheets/styled-components/palette';
import {
headingsFontWeight,
@ -151,23 +156,46 @@ const BreakoutScrollableList = styled(ScrollboxVertical)`
`;
const DurationContainer = styled.div`
text-align: center;
${({ centeredText }) => centeredText && `
text-align: center;
`}
border-radius: ${borderRadius};
margin-bottom: ${jumboPaddingY};
padding: 10px;
box-shadow: 0 0 1px 1px ${colorGrayLightest};
`;
const SetTimeContainer = styled.div`
border-top: 1px solid ${systemMessageBorderColor};
border-bottom: 1px solid ${systemMessageBorderColor};
padding: 10px 0px;
margin: .5rem 0 0 0;
`;
const SetDurationInput = styled.input`
flex: 1;
border: 1px solid ${colorGrayLighter};
width: 50%;
text-align: center;
padding: .25rem;
border-radius: ${borderRadius};
background-clip: padding-box;
outline: none;
&::placeholder {
color: ${colorGray};
opacity: 1;
}
&:focus {
border-radius: ${borderSize};
box-shadow: 0 0 0 ${borderSize} ${colorBlueLight}, inset 0 0 0 1px ${colorPrimary};
}
&:disabled,
&[disabled] {
cursor: not-allowed;
opacity: .75;
background-color: rgba(167,179,189,0.25);
}
`;
const WithError = styled.span`
@ -184,7 +212,6 @@ const EndButton = styled(Button)`
const Duration = styled.span`
display: inline-block;
align-self: center;
margin: .5rem 0 .5rem 0;
`;
const Panel = styled(ScrollboxVertical)`
@ -207,7 +234,6 @@ const HeaderButton = styled(Button)`
flex-direction: row;
justify-content: space-between;
position: relative;
margin: 0 auto 2rem 0;
padding-left: 0;
padding-right: inherit;
background: none !important;
@ -239,6 +265,18 @@ const Separator = styled.div`
margin: 30px 0px;
`;
const Header = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: ${jumboPaddingY};
`;
const FlexRow = styled.div`
display: flex;
flex-wrap: nowrap;
`;
export default {
BreakoutActions,
AlreadyConnected,
@ -261,4 +299,6 @@ export default {
Panel,
HeaderButton,
Separator,
Header,
FlexRow,
};

View File

@ -90,6 +90,7 @@ class BBBMenu extends React.Component {
const close = !key.includes('setstatus') && !key.includes('back');
// prevent menu close for sub menu actions
if (close) this.handleClose(event);
event.stopPropagation();
}}>
<div style={{ display: 'flex', flexFlow: 'row', width: '100%' }}>
{a.icon ? <Icon iconName={a.icon} key="icon" /> : null}

View File

@ -6,7 +6,7 @@
"app.chat.disconnected": "You are disconnected, messages can't be sent",
"app.chat.locked": "Chat is locked, messages can't be sent",
"app.chat.inputLabel": "Message input for chat {0}",
"app.chat.inputPlaceholder": "Send message to {0}",
"app.chat.inputPlaceholder": "Message {0}",
"app.chat.titlePublic": "Public Chat",
"app.chat.titlePrivate": "Private Chat with {0}",
"app.chat.partnerDisconnected": "{0} has left the meeting",
@ -160,6 +160,8 @@
"app.meeting.alertMeetingEndsUnderMinutesPlural": "Meeting is closing in {0} minutes.",
"app.meeting.alertBreakoutEndsUnderMinutesPlural": "Breakout is closing in {0} minutes.",
"app.meeting.alertBreakoutEndsUnderMinutesSingular": "Breakout is closing in one minute.",
"app.meeting.alertBreakoutTimeExtended.mainRoom": "Breakout rooms time has been extended to {0} minutes, and a notification has been sent.",
"app.meeting.alertBreakoutTimeExtended.breakout": "Breakout time is now {0}:00. Keep collaborating.",
"app.presentation.hide": "Hide presentation",
"app.presentation.notificationLabel": "Current presentation",
"app.presentation.downloadLabel": "Download",
@ -504,6 +506,9 @@
"app.breakoutJoinConfirmation.freeJoinMessage": "Choose a breakout room to join",
"app.breakoutTimeRemainingMessage": "Breakout room time remaining: {0}",
"app.breakoutWillCloseMessage": "Time ended. Breakout room will close soon",
"app.breakout.dropdown.manageDuration": "Manage duration",
"app.breakout.dropdown.destroyAll": "Destroy breakouts",
"app.breakout.dropdown.options": "Breakout Options",
"app.calculatingBreakoutTimeRemaining": "Calculating remaining time ...",
"app.audioModal.ariaTitle": "Join audio modal",
"app.audioModal.microphoneLabel": "Microphone",