bigbluebutton-Github/bigbluebutton-html5/imports/ui/components/poll/component.jsx

388 lines
10 KiB
React
Raw Normal View History

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import PresentationUploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container';
import { withModalMounter } from '/imports/ui/components/modal/service';
import _ from 'lodash';
2018-10-18 22:31:17 +08:00
import { Session } from 'meteor/session';
2018-09-15 01:50:18 +08:00
import Button from '/imports/ui/components/button/component';
2018-10-29 23:27:50 +08:00
import LiveResult from './live-result/component';
2018-09-15 01:50:18 +08:00
import { styles } from './styles.scss';
const intlMessages = defineMessages({
pollPaneTitle: {
id: 'app.poll.pollPaneTitle',
description: 'heading label for the poll menu',
},
2018-10-30 23:57:10 +08:00
closeLabel: {
id: 'app.poll.closeLabel',
description: 'label for poll pane close button',
},
hidePollDesc: {
id: 'app.poll.hidePollDesc',
description: 'aria label description for hide poll button',
},
customPollLabel: {
id: 'app.poll.customPollLabel',
description: 'label for custom poll button',
},
startCustomLabel: {
id: 'app.poll.startCustomLabel',
description: 'label for button to start custom poll',
},
customPollInstruction: {
id: 'app.poll.customPollInstruction',
description: 'instructions for using custom poll',
},
quickPollInstruction: {
id: 'app.poll.quickPollInstruction',
description: 'instructions for using pre configured polls',
},
2018-09-25 06:43:54 +08:00
activePollInstruction: {
id: 'app.poll.activePollInstruction',
description: 'instructions displayed when a poll is active',
},
ariaInputCount: {
id: 'app.poll.ariaInputCount',
description: 'aria label for custom poll input field',
},
customPlaceholder: {
id: 'app.poll.customPlaceholder',
description: 'custom poll input field placeholder text',
},
noPresentationSelected: {
id: 'app.poll.noPresentationSelected',
description: 'no presentation label',
},
clickHereToSelect: {
id: 'app.poll.clickHereToSelect',
description: 'open uploader modal button label',
},
tf: {
id: 'app.poll.tf',
description: 'label for true / false poll',
},
yn: {
id: 'app.poll.yn',
description: 'label for Yes / No poll',
},
a2: {
id: 'app.poll.a2',
description: 'label for A / B poll',
},
a3: {
id: 'app.poll.a3',
description: 'label for A / B / C poll',
},
a4: {
id: 'app.poll.a4',
description: 'label for A / B / C / D poll',
},
a5: {
id: 'app.poll.a5',
description: 'label for A / B / C / D / E poll',
},
});
2018-10-03 07:59:45 +08:00
const MAX_CUSTOM_FIELDS = Meteor.settings.public.poll.max_custom;
2019-04-02 12:07:45 +08:00
const MAX_INPUT_CHARS = 45;
2018-10-03 07:59:45 +08:00
class Poll extends Component {
2018-09-15 01:50:18 +08:00
constructor(props) {
super(props);
this.state = {
customPollReq: false,
2018-09-25 06:43:54 +08:00
isPolling: false,
customPollValues: [],
2018-09-15 01:50:18 +08:00
};
this.inputEditor = [];
2018-09-15 01:50:18 +08:00
this.toggleCustomFields = this.toggleCustomFields.bind(this);
this.renderQuickPollBtns = this.renderQuickPollBtns.bind(this);
2018-10-31 21:18:33 +08:00
this.renderCustomView = this.renderCustomView.bind(this);
this.renderInputFields = this.renderInputFields.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleBackClick = this.handleBackClick.bind(this);
}
2019-05-07 04:18:23 +08:00
componentDidMount() {
const { props } = this.hideBtn;
const { className } = props;
const hideBtn = document.getElementsByClassName(`${className}`);
if (hideBtn[0]) hideBtn[0].focus();
2019-05-07 04:18:23 +08:00
}
2018-11-23 12:08:48 +08:00
componentDidUpdate() {
const { amIPresenter } = this.props;
2018-11-23 12:08:48 +08:00
if (Session.equals('resetPollPanel', true)) {
2019-03-15 03:34:53 +08:00
this.handleBackClick();
}
if (!amIPresenter) {
2018-11-23 12:08:48 +08:00
Session.set('openPanel', 'userlist');
Session.set('forcePollOpen', false);
}
}
handleInputChange(index, event) {
// This regex will replace any instance of 2 or more consecutive white spaces
// with a single white space character.
const option = event.target.value.replace(/\s{2,}/g, ' ').trim();
this.inputEditor[index] = option === '' ? '' : option;
this.setState({ customPollValues: this.inputEditor });
}
2018-12-18 23:15:51 +08:00
handleBackClick() {
const { stopPoll } = this.props;
Session.set('resetPollPanel', false);
2018-09-24 06:20:20 +08:00
2018-12-18 23:15:51 +08:00
stopPoll();
this.inputEditor = [];
this.setState({
isPolling: false,
customPollValues: this.inputEditor,
}, document.activeElement.blur());
}
2018-09-15 01:50:18 +08:00
toggleCustomFields() {
const { customPollReq } = this.state;
2018-10-31 21:18:33 +08:00
return this.setState({ customPollReq: !customPollReq });
}
renderQuickPollBtns() {
const {
isMeteorConnected, pollTypes, startPoll, intl,
} = this.props;
const btns = pollTypes.map((type) => {
if (type === 'custom') return false;
2018-09-24 06:20:20 +08:00
const label = intl.formatMessage(
// regex removes the - to match the message id
2018-12-18 23:15:51 +08:00
intlMessages[type.replace(/-/g, '').toLowerCase()],
);
return (
<Button
disabled={!isMeteorConnected}
label={label}
color="default"
className={styles.pollBtn}
key={_.uniqueId('quick-poll-')}
onClick={() => {
Session.set('pollInitiated', true);
2018-12-18 23:15:51 +08:00
this.setState({ isPolling: true }, () => startPoll(type));
}}
/>);
});
return btns;
2018-09-15 01:50:18 +08:00
}
2018-10-31 21:18:33 +08:00
renderCustomView() {
2018-09-24 06:20:20 +08:00
const { intl, startCustomPoll } = this.props;
const isDisabled = _.compact(this.inputEditor).length < 1;
return (
2018-09-24 06:20:20 +08:00
<div className={styles.customInputWrapper}>
2018-10-31 21:18:33 +08:00
{this.renderInputFields()}
<Button
2018-09-24 06:20:20 +08:00
onClick={() => {
if (this.inputEditor.length > 0) {
Session.set('pollInitiated', true);
this.setState({ isPolling: true }, () => startCustomPoll('custom', _.compact(this.inputEditor)));
2018-09-24 06:20:20 +08:00
}
}}
label={intl.formatMessage(intlMessages.startCustomLabel)}
2018-09-24 06:20:20 +08:00
color="primary"
aria-disabled={isDisabled}
disabled={isDisabled}
2018-09-24 06:20:20 +08:00
className={styles.btn}
/>
</div>
);
2018-09-15 01:50:18 +08:00
}
2018-12-18 23:15:51 +08:00
renderInputFields() {
const { intl } = this.props;
const { customPollValues } = this.state;
let items = [];
2018-10-24 22:17:13 +08:00
2018-12-18 23:15:51 +08:00
items = _.range(1, MAX_CUSTOM_FIELDS + 1).map((ele, index) => {
const id = index;
return (
<div key={`custom-poll-${id}`} className={styles.pollInput}>
<input
aria-label={intl.formatMessage(
intlMessages.ariaInputCount, { 0: id + 1, 1: MAX_CUSTOM_FIELDS },
)}
placeholder={intl.formatMessage(intlMessages.customPlaceholder)}
className={styles.input}
onChange={event => this.handleInputChange(id, event)}
defaultValue={customPollValues[id]}
maxLength={MAX_INPUT_CHARS}
/>
</div>
2018-12-18 23:15:51 +08:00
);
});
return items;
2018-10-24 22:17:13 +08:00
}
2018-09-25 06:43:54 +08:00
renderActivePollOptions() {
const {
2019-05-23 02:00:44 +08:00
intl,
isMeteorConnected,
2019-05-23 02:00:44 +08:00
stopPoll,
currentPoll,
pollAnswerIds,
sendGroupMessage,
2018-09-25 06:43:54 +08:00
} = this.props;
return (
<div>
2018-10-11 02:25:35 +08:00
<div className={styles.instructions}>
{intl.formatMessage(intlMessages.activePollInstruction)}
</div>
2018-10-29 23:27:50 +08:00
<LiveResult
{...{
isMeteorConnected,
2018-10-29 23:27:50 +08:00
stopPoll,
currentPoll,
2019-05-23 02:00:44 +08:00
pollAnswerIds,
sendGroupMessage,
2018-10-29 23:27:50 +08:00
}}
handleBackClick={this.handleBackClick}
2018-10-29 23:27:50 +08:00
/>
2018-09-25 06:43:54 +08:00
</div>
);
}
renderPollOptions() {
const { isMeteorConnected, intl } = this.props;
const { customPollReq } = this.state;
2018-09-15 01:50:18 +08:00
return (
2018-09-24 06:20:20 +08:00
<div>
<div className={styles.instructions}>
{intl.formatMessage(intlMessages.quickPollInstruction)}
2018-09-15 01:50:18 +08:00
</div>
<div className={styles.grid}>
{this.renderQuickPollBtns()}
</div>
<div className={styles.instructions}>
{intl.formatMessage(intlMessages.customPollInstruction)}
</div>
<Button
disabled={!isMeteorConnected}
className={styles.customBtn}
color="default"
onClick={this.toggleCustomFields}
label={intl.formatMessage(intlMessages.customPollLabel)}
2019-01-28 21:33:50 +08:00
aria-expanded={customPollReq}
/>
2018-10-31 21:18:33 +08:00
{!customPollReq ? null : this.renderCustomView()}
2018-09-15 01:50:18 +08:00
</div>
);
}
2018-09-25 06:43:54 +08:00
2019-03-15 03:34:53 +08:00
renderNoSlidePanel() {
const { mountModal, intl } = this.props;
return (
<div className={styles.noSlidePanelContainer}>
<h4>{intl.formatMessage(intlMessages.noPresentationSelected)}</h4>
<Button
label={intl.formatMessage(intlMessages.clickHereToSelect)}
color="primary"
onClick={() => mountModal(<PresentationUploaderContainer />)}
className={styles.pollBtn}
/>
</div>
);
}
2019-03-15 03:34:53 +08:00
renderPollPanel() {
const { isPolling } = this.state;
2018-09-25 06:43:54 +08:00
const {
currentPoll,
currentSlide,
2018-09-25 06:43:54 +08:00
} = this.props;
if (!currentSlide) return this.renderNoSlidePanel();
if (isPolling || (!isPolling && currentPoll)) {
return this.renderActivePollOptions();
}
return this.renderPollOptions();
}
render() {
const {
intl,
stopPoll,
currentPoll,
amIPresenter,
} = this.props;
2018-12-18 23:15:51 +08:00
if (!amIPresenter) return null;
2018-11-23 12:08:48 +08:00
2018-09-25 06:43:54 +08:00
return (
<div>
<header className={styles.header}>
2018-10-18 22:31:17 +08:00
<Button
2019-05-07 04:18:23 +08:00
ref={(node) => { this.hideBtn = node; }}
2018-10-18 22:31:17 +08:00
tabIndex={0}
label={intl.formatMessage(intlMessages.pollPaneTitle)}
icon="left_arrow"
2018-09-25 06:43:54 +08:00
aria-label={intl.formatMessage(intlMessages.hidePollDesc)}
2018-10-18 22:31:17 +08:00
className={styles.hideBtn}
2018-09-25 06:43:54 +08:00
onClick={() => {
2018-11-20 07:29:48 +08:00
Session.set('openPanel', 'userlist');
2018-09-25 06:43:54 +08:00
}}
/>
2018-10-24 22:17:13 +08:00
<Button
2018-10-30 23:57:10 +08:00
label={intl.formatMessage(intlMessages.closeLabel)}
aria-label={`${intl.formatMessage(intlMessages.closeLabel)} ${intl.formatMessage(intlMessages.pollPaneTitle)}`}
2018-10-24 22:17:13 +08:00
onClick={() => {
2018-12-18 23:15:51 +08:00
if (currentPoll) {
stopPoll();
}
Session.set('openPanel', 'userlist');
Session.set('forcePollOpen', false);
}}
2018-10-24 22:17:13 +08:00
className={styles.closeBtn}
2018-10-30 00:14:05 +08:00
icon="close"
size="sm"
2018-10-30 23:57:10 +08:00
hideLabel
2018-10-24 22:17:13 +08:00
/>
2018-09-25 06:43:54 +08:00
</header>
{
this.renderPollPanel()
2018-09-25 06:43:54 +08:00
}
</div>
);
}
2018-09-15 01:50:18 +08:00
}
export default withModalMounter(injectIntl(Poll));
Poll.propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
amIPresenter: PropTypes.bool.isRequired,
pollTypes: PropTypes.instanceOf(Array).isRequired,
startPoll: PropTypes.func.isRequired,
startCustomPoll: PropTypes.func.isRequired,
stopPoll: PropTypes.func.isRequired,
};