Improve the front-end part for managing breakout duration
.
This commit is contained in:
parent
ae13fb3593
commit
a6f16083b5
@ -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);
|
@ -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);
|
||||
|
@ -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,
|
||||
};
|
@ -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)}
|
||||
/>
|
||||
|
||||
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
@ -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] {
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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}
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user