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

1060 lines
34 KiB
React
Raw Normal View History

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
2022-02-15 23:54:55 +08:00
import { withModalMounter } from '/imports/ui/components/common/modal/service';
import _ from 'lodash';
2018-10-18 22:31:17 +08:00
import { Session } from 'meteor/session';
import Checkbox from '/imports/ui/components/common/checkbox/component';
2022-02-15 23:58:28 +08:00
import Toggle from '/imports/ui/components/common/switch/component';
2018-10-29 23:27:50 +08:00
import LiveResult from './live-result/component';
2021-11-04 22:19:38 +08:00
import Styled from './styles';
2021-05-18 04:25:07 +08:00
import { PANELS, ACTIONS } from '../layout/enums';
import DragAndDrop from './dragAndDrop/component';
2021-09-13 08:03:03 +08:00
import { alertScreenReader } from '/imports/utils/dom-utils';
2018-09-15 01:50:18 +08:00
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',
},
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',
},
dragDropPollInstruction: {
id: 'app.poll.dragDropPollInstruction',
description: 'instructions for upload poll options via drag and drop',
},
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',
},
2020-09-22 06:52:38 +08:00
questionErr: {
id: 'app.poll.questionErr',
description: 'question text area error label',
},
2022-03-22 01:58:11 +08:00
questionAndOptionsPlaceholder: {
id: 'app.poll.questionAndoptions.label',
description: 'poll input questions and options label',
},
autoOptionToggleLabel: {
id: 'app.poll.autoOption.label',
description: 'poll auto optioning toogle button label',
},
autoOptionInstructionsLabel: {
id: 'app.poll.autoOptionInstructions.label',
description: 'poll auto optioning instructions label',
},
maxOptionsWarning: {
id: 'app.poll.maxOptionsWarning.label',
2022-03-22 01:58:11 +08:00
description: 'poll max options error',
},
2020-09-22 06:52:38 +08:00
optionErr: {
id: 'app.poll.optionErr',
description: 'poll input error label',
},
tf: {
id: 'app.poll.tf',
description: 'label for true / false poll',
},
a4: {
id: 'app.poll.a4',
description: 'label for A / B / C / D poll',
},
delete: {
id: 'app.poll.optionDelete.label',
description: '',
},
questionLabel: {
id: 'app.poll.question.label',
description: '',
},
2021-10-01 02:55:46 +08:00
optionalQuestionLabel: {
id: 'app.poll.optionalQuestion.label',
description: '',
},
userResponse: {
id: 'app.poll.userResponse.label',
description: '',
},
responseChoices: {
id: 'app.poll.responseChoices.label',
description: '',
},
typedResponseDesc: {
id: 'app.poll.typedResponse.desc',
description: '',
},
responseTypesLabel: {
id: 'app.poll.responseTypes.label',
description: '',
},
addOptionLabel: {
id: 'app.poll.addItem.label',
description: '',
},
startPollLabel: {
id: 'app.poll.start.label',
description: '',
},
secretPollLabel: {
id: 'app.poll.secretPoll.label',
description: '',
},
isSecretPollLabel: {
id: 'app.poll.secretPoll.isSecretLabel',
description: '',
},
true: {
id: 'app.poll.answer.true',
description: '',
},
false: {
id: 'app.poll.answer.false',
description: '',
},
a: {
id: 'app.poll.answer.a',
description: '',
},
b: {
id: 'app.poll.answer.b',
description: '',
},
c: {
id: 'app.poll.answer.c',
description: '',
},
d: {
id: 'app.poll.answer.d',
description: '',
},
2022-03-22 01:58:11 +08:00
e: {
id: 'app.poll.answer.e',
description: '',
},
2021-02-19 00:06:21 +08:00
yna: {
id: 'app.poll.yna',
description: '',
},
yes: {
id: 'app.poll.y',
description: '',
},
no: {
id: 'app.poll.n',
description: '',
},
abstention: {
id: 'app.poll.abstention',
description: '',
},
enableMultipleResponseLabel: {
id: 'app.poll.enableMultipleResponseLabel',
description: 'label for checkbox to enable multiple choice',
},
2021-08-11 12:10:41 +08:00
startPollDesc: {
id: 'app.poll.startPollDesc',
description: '',
},
showRespDesc: {
id: 'app.poll.showRespDesc',
description: '',
},
addRespDesc: {
id: 'app.poll.addRespDesc',
description: '',
},
deleteRespDesc: {
id: 'app.poll.deleteRespDesc',
description: '',
},
2021-08-19 04:02:46 +08:00
on: {
id: 'app.switch.onLabel',
description: 'label for toggle switch on state',
},
off: {
id: 'app.switch.offLabel',
description: 'label for toggle switch off state',
},
2021-09-13 08:03:03 +08:00
removePollOpt: {
id: 'app.poll.removePollOpt',
description: 'screen reader alert for removed poll option',
},
emptyPollOpt: {
id: 'app.poll.emptyPollOpt',
description: 'screen reader for blank poll option',
},
});
const POLL_SETTINGS = Meteor.settings.public.poll;
const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom;
const MAX_INPUT_CHARS = POLL_SETTINGS.maxTypedAnswerLength;
2022-03-22 01:58:11 +08:00
const QUESTION_MAX_INPUT_CHARS = 1200;
const FILE_DRAG_AND_DROP_ENABLED = POLL_SETTINGS.allowDragAndDropFile;
2022-03-22 01:58:11 +08:00
const AUTO_OPTIONING = POLL_SETTINGS.autoOptioning;
const POLL_OPTIONS_PLACEHOLDERS = [
{ val: intlMessages.a },
{ val: intlMessages.b },
{ val: intlMessages.c },
{ val: intlMessages.d },
{ val: intlMessages.e },
];
const MIN_OPTIONS_LENGTH = 2;
2018-10-03 07:59:45 +08:00
const validateInput = (i) => {
let _input = i;
while (/^\s/.test(_input)) _input = _input.substring(1);
return _input;
};
2022-03-22 01:58:11 +08:00
const removeEmptyLineSpaces = (inputText) => {
const filteredInput = inputText.split('\n').filter((val) => val.trim() !== '');
return filteredInput;
};
const getSplittedQuestionAndOptions = (questionAndOptions) => {
const inputList = Array.isArray(questionAndOptions)
? questionAndOptions : questionAndOptions.split('\n');
const splittedQuestion = inputList.length > 0 ? inputList[0] : questionAndOptions;
const optionsList = inputList.slice(1);
optionsList.forEach((val, i) => { optionsList[i] = { val }; });
return {
splittedQuestion,
optionsList,
};
};
class Poll extends Component {
2018-09-15 01:50:18 +08:00
constructor(props) {
super(props);
this.state = {
2018-09-25 06:43:54 +08:00
isPolling: false,
question: '',
2022-03-22 01:58:11 +08:00
questionAndOptions: '',
optList: [],
2020-09-22 06:52:38 +08:00
error: null,
isMultipleResponse: false,
secretPoll: false,
2022-03-22 01:58:11 +08:00
autoOptioning: false,
warning: null,
isPasting: false,
type: null,
2018-09-15 01:50:18 +08:00
};
this.handleBackClick = this.handleBackClick.bind(this);
this.handleAddOption = this.handleAddOption.bind(this);
this.handleRemoveOption = this.handleRemoveOption.bind(this);
this.handleTextareaChange = this.handleTextareaChange.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
2021-03-07 22:12:49 +08:00
this.toggleIsMultipleResponse = this.toggleIsMultipleResponse.bind(this);
2021-08-19 04:02:46 +08:00
this.displayToggleStatus = this.displayToggleStatus.bind(this);
2022-03-22 01:58:11 +08:00
this.displayAutoOptionToggleStatus = this.displayAutoOptionToggleStatus.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, layoutContextDispatch, sidebarContentPanel } = this.props;
if (Session.equals('resetPollPanel', true)) {
2019-03-15 03:34:53 +08:00
this.handleBackClick();
}
if (!amIPresenter && sidebarContentPanel === PANELS.POLL) {
2021-08-05 19:03:24 +08:00
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
2021-08-05 19:03:24 +08:00
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.NONE,
});
}
2018-11-23 12:08:48 +08:00
}
2021-12-09 00:49:43 +08:00
componentWillUnmount() {
Session.set('secretPoll', false);
}
2018-12-18 23:15:51 +08:00
handleBackClick() {
const { stopPoll } = this.props;
this.setState({
isPolling: false,
2020-09-22 06:52:38 +08:00
error: null,
}, () => {
stopPoll();
Session.set('resetPollPanel', false);
document.activeElement.blur();
});
}
handleInputTextChange(index, text) {
const { optList, type } = this.state;
const { pollTypes } = this.props;
// This regex will replace any instance of 2 or more consecutive white spaces
// with a single white space character.
const option = text.replace(/\s{2,}/g, ' ').trim();
if (index < optList.length) optList[index].val = option === '' ? '' : option;
this.setState({ optList, type: type || pollTypes.Letter });
}
handleInputChange(e, index) {
2022-03-22 01:58:11 +08:00
const {
optList, type, error, questionAndOptions,
} = this.state;
const { pollTypes } = this.props;
const list = [...optList];
2020-09-22 06:52:38 +08:00
const validatedVal = validateInput(e.target.value).replace(/\s{2,}/g, ' ');
const charsRemovedCount = e.target.value.length - validatedVal.length;
const clearError = validatedVal.length > 0 && type !== pollTypes.Response;
const input = e.target;
const caretStart = e.target.selectionStart;
const caretEnd = e.target.selectionEnd;
2020-09-22 06:52:38 +08:00
list[index] = { val: validatedVal };
2022-03-22 01:58:11 +08:00
let questionAndOptionsList = [];
if (questionAndOptions.length > 0) {
questionAndOptionsList = questionAndOptions.split('\n');
questionAndOptionsList[index + 1] = validatedVal;
}
this.setState({
optList: list,
questionAndOptions: questionAndOptionsList.length > 0
? questionAndOptionsList.join('\n') : '',
error: clearError ? null : error,
},
() => {
input.focus();
input.selectionStart = caretStart - charsRemovedCount;
input.selectionEnd = caretEnd - charsRemovedCount;
});
}
handleTextareaChange(e) {
2022-03-22 01:58:11 +08:00
const {
type, error, autoOptioning, isPasting,
} = this.state;
const { pollTypes, intl } = this.props;
const validatedInput = validateInput(e.target.value);
const clearError = validatedInput.length > 0 && type === pollTypes.Response;
if (!autoOptioning) {
this.setState({
question: validatedInput,
error: clearError ? null : error,
});
} else {
const { warning, optList } = this.state;
let maxOptionsWarning = warning;
2022-03-22 01:58:11 +08:00
const { splittedQuestion, optionsList } = getSplittedQuestionAndOptions(validatedInput);
const optionsListLength = optionsList.length;
const clearWarning = maxOptionsWarning && optionsListLength <= MAX_CUSTOM_FIELDS;
2022-03-22 01:58:11 +08:00
if ((optionsListLength) > MAX_CUSTOM_FIELDS && optList[MAX_CUSTOM_FIELDS] === undefined) {
this.setState({ warning: intl.formatMessage(intlMessages.maxOptionsWarning) });
2022-03-22 01:58:11 +08:00
if (!isPasting) { return null; }
maxOptionsWarning = intl.formatMessage(intlMessages.maxOptionsWarning);
2022-03-22 01:58:11 +08:00
this.setState({ isPasting: false });
}
this.setState({
questionAndOptions: validatedInput,
optList: optionsList,
question: splittedQuestion,
error: clearError ? null : error,
warning: clearWarning ? null : maxOptionsWarning,
2022-03-22 01:58:11 +08:00
});
}
return null;
}
handlePollValuesText(text) {
if (text && text.length > 0) {
this.pushToCustomPollValues(text);
}
}
handleRemoveOption(index) {
2021-09-13 08:03:03 +08:00
const { intl } = this.props;
2022-03-22 01:58:11 +08:00
const {
optList, questionAndOptions, autoOptioning, warning,
} = this.state;
const list = [...optList];
2021-09-13 08:03:03 +08:00
const removed = list[index];
list.splice(index, 1);
2022-03-22 01:58:11 +08:00
// if autoOptioning then removing text from input field
let questionAndOptionsList = [];
let clearWarning = false;
if (autoOptioning) {
questionAndOptionsList = questionAndOptions.split('\n');
delete questionAndOptionsList[index + 1];
questionAndOptionsList = questionAndOptionsList.filter((val) => val !== undefined);
clearWarning = warning && list.length <= MAX_CUSTOM_FIELDS;
}
this.setState({
optList: list,
questionAndOptions: questionAndOptionsList.length > 0
? questionAndOptionsList.join('\n') : [],
warning: clearWarning ? null : warning,
}, () => {
alertScreenReader(`${intl.formatMessage(intlMessages.removePollOpt,
{ 0: removed.val || intl.formatMessage(intlMessages.emptyPollOpt) })}`);
2021-09-13 08:03:03 +08:00
});
}
handleAddOption() {
const { optList } = this.state;
this.setState({ optList: [...optList, { val: '' }] });
}
handleToggle() {
const { secretPoll } = this.state;
const toggledValue = !secretPoll;
Session.set('secretPoll', toggledValue);
this.setState({ secretPoll: toggledValue });
}
2022-03-22 01:58:11 +08:00
handleAutoOptionToogle() {
const { autoOptioning, questionAndOptions, question } = this.state;
const { intl } = this.props;
const toggledValue = !autoOptioning;
if (autoOptioning === true && toggledValue === false) {
const questionAndOptionsList = removeEmptyLineSpaces(questionAndOptions);
this.setState({
question: questionAndOptionsList.join('\n'),
autoOptioning: toggledValue,
optList: [],
type: null,
});
} else {
this.handlePollLetterOptions();
const inputList = removeEmptyLineSpaces(question);
const { splittedQuestion, optionsList } = getSplittedQuestionAndOptions(inputList);
const clearWarning = optionsList.length > MAX_CUSTOM_FIELDS
? intl.formatMessage(intlMessages.maxOptionsWarning) : null;
2022-03-22 01:58:11 +08:00
this.setState({
questionAndOptions: inputList.join('\n'),
optList: optionsList,
autoOptioning: toggledValue,
question: splittedQuestion,
warning: clearWarning,
});
}
}
handlePollLetterOptions() {
const { pollTypes } = this.props;
const { optList } = this.state;
if (optList.length === 0) {
this.setState({
type: pollTypes.Letter,
optList: [
{ val: '' },
{ val: '' },
{ val: '' },
{ val: '' },
],
});
}
}
setOptListLength(len) {
const { optList } = this.state;
let diff = len > MAX_CUSTOM_FIELDS
? MAX_CUSTOM_FIELDS - optList.length
: len - optList.length;
if (diff > 0) {
while (diff > 0) {
this.handleAddOption();
diff -= 1;
}
} else {
while (diff < 0) {
this.handleRemoveOption();
diff += 1;
}
}
}
2022-03-22 01:58:11 +08:00
toggleIsMultipleResponse() {
const { isMultipleResponse } = this.state;
return this.setState({ isMultipleResponse: !isMultipleResponse });
}
2021-08-19 04:02:46 +08:00
displayToggleStatus(status) {
const { intl } = this.props;
return (
2021-11-04 22:19:38 +08:00
<Styled.ToggleLabel>
2021-08-19 04:02:46 +08:00
{status ? intl.formatMessage(intlMessages.on)
: intl.formatMessage(intlMessages.off)}
2021-11-04 22:19:38 +08:00
</Styled.ToggleLabel>
2021-08-19 04:02:46 +08:00
);
}
2022-03-22 01:58:11 +08:00
displayAutoOptionToggleStatus(status) {
const { intl } = this.props;
return (
<Styled.ToggleLabel>
{status ? intl.formatMessage(intlMessages.on)
: intl.formatMessage(intlMessages.off)}
</Styled.ToggleLabel>
);
}
pushToCustomPollValues(text) {
const lines = text.split('\n');
this.setOptListLength(lines.length);
for (let i = 0; i < MAX_CUSTOM_FIELDS; i += 1) {
let line = '';
if (i < lines.length) {
line = lines[i];
line = line.length > MAX_INPUT_CHARS ? line.substring(0, MAX_INPUT_CHARS) : line;
}
this.handleInputTextChange(i, line);
}
}
renderInputs() {
const { intl, pollTypes } = this.props;
2020-09-22 06:52:38 +08:00
const { optList, type, error } = this.state;
let hasVal = false;
2022-03-22 01:58:11 +08:00
return optList.slice(0, MAX_CUSTOM_FIELDS).map((o, i) => {
if (o.val && o.val.length > 0) hasVal = true;
const pollOptionKey = `poll-option-${i}`;
2018-12-18 23:15:51 +08:00
return (
2021-04-09 02:42:06 +08:00
<span key={pollOptionKey}>
2020-09-22 06:52:38 +08:00
<div
style={{
display: 'flex',
justifyContent: 'spaceBetween',
}}
>
2021-11-04 22:19:38 +08:00
<Styled.PollOptionInput
2020-09-22 06:52:38 +08:00
type="text"
value={o.val}
2022-03-22 01:58:11 +08:00
placeholder={`${i < MAX_CUSTOM_FIELDS ? `${intl.formatMessage(POLL_OPTIONS_PLACEHOLDERS[i].val)}. ` : ''}
${intl.formatMessage(intlMessages.customPlaceholder)}`}
data-test="pollOptionItem"
onChange={(e) => this.handleInputChange(e, i)}
2020-09-22 06:52:38 +08:00
maxLength={MAX_INPUT_CHARS}
/>
2022-03-22 01:58:11 +08:00
{
optList.length > MIN_OPTIONS_LENGTH && (
<Styled.DeletePollOptionButton
label={intl.formatMessage(intlMessages.delete)}
aria-describedby={`option-${i}`}
icon="delete"
data-test="deletePollOption"
hideLabel
circle
color="default"
onClick={() => {
this.handleRemoveOption(i);
}}
/>
)
2022-03-22 01:58:11 +08:00
}
<span className="sr-only" id={`option-${i}`}>
{intl.formatMessage(intlMessages.deleteRespDesc,
{ 0: (o.val || intl.formatMessage(intlMessages.emptyPollOpt)) })}
</span>
2020-09-22 06:52:38 +08:00
</div>
{!hasVal && type !== pollTypes.Response && error ? (
2021-11-04 22:19:38 +08:00
<Styled.InputError>{error}</Styled.InputError>
2020-09-22 06:52:38 +08:00
) : (
2021-11-04 22:19:38 +08:00
<Styled.ErrorSpacer>&nbsp;</Styled.ErrorSpacer>
2020-09-22 06:52:38 +08:00
)}
</span>
2018-12-18 23:15:51 +08:00
);
});
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,
usernames,
isDefaultPoll,
2018-09-25 06:43:54 +08:00
} = this.props;
return (
<div>
2021-11-04 22:19:38 +08:00
<Styled.Instructions>
2018-10-11 02:25:35 +08:00
{intl.formatMessage(intlMessages.activePollInstruction)}
2021-11-04 22:19:38 +08:00
</Styled.Instructions>
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,
usernames,
isDefaultPoll,
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() {
2020-09-22 06:52:38 +08:00
const {
2022-03-22 01:58:11 +08:00
type, secretPoll, optList, questionAndOptions, error,
isMultipleResponse, question, autoOptioning, warning,
2020-09-22 06:52:38 +08:00
} = this.state;
const {
startPoll,
startCustomPoll,
intl,
pollTypes,
isDefaultPoll,
checkPollType,
smallSidebar,
} = this.props;
const defaultPoll = isDefaultPoll(type);
2021-10-01 02:55:46 +08:00
const questionPlaceholder = (type === pollTypes.Response)
? intlMessages.questionLabel
: intlMessages.optionalQuestionLabel;
2022-03-22 01:58:11 +08:00
const questionsAndOptionsPlaceholder = intlMessages.questionAndOptionsPlaceholder;
const hasQuestionError = (type === pollTypes.Response
&& questionAndOptions.length === 0 && error);
const hasOptionError = (autoOptioning && optList.length === 0 && error);
const hasWarning = (autoOptioning && warning);
2018-09-15 01:50:18 +08:00
return (
2018-09-24 06:20:20 +08:00
<div>
2022-03-22 01:58:11 +08:00
{AUTO_OPTIONING
&& (
<Styled.AutoOptioningRow>
<Styled.Col aria-hidden="true">
<Styled.SectionHeading>
{intl.formatMessage(intlMessages.autoOptionToggleLabel)}
</Styled.SectionHeading>
</Styled.Col>
<Styled.Col>
<Styled.Toggle>
{this.displayAutoOptionToggleStatus(autoOptioning)}
<Toggle
icons={false}
disabled={FILE_DRAG_AND_DROP_ENABLED}
defaultChecked={autoOptioning}
onChange={() => this.handleAutoOptionToogle()}
ariaLabel={intl.formatMessage(intlMessages.autoOptionToggleLabel)}
showToggleLabel={false}
data-test="autoOptioningPollBtn"
/>
</Styled.Toggle>
</Styled.Col>
</Styled.AutoOptioningRow>
2022-03-22 01:58:11 +08:00
)}
{autoOptioning
&& (
<Styled.PollParagraph style={{ marginBottom: '0.9rem' }}>
{intl.formatMessage(intlMessages.autoOptionInstructionsLabel)}
</Styled.PollParagraph>
)}
<div>
2021-11-04 22:19:38 +08:00
<Styled.PollQuestionArea
2022-03-22 01:58:11 +08:00
hasError={hasQuestionError || hasOptionError}
data-test="pollQuestionArea"
2022-03-22 01:58:11 +08:00
value={autoOptioning ? questionAndOptions : question}
onChange={(e) => this.handleTextareaChange(e)}
2022-03-22 01:58:11 +08:00
onPaste={() => this.setState({ isPasting: true })}
onKeyPress={(event) => {
if (event.key === 'Enter' && autoOptioning) {
this.handlePollLetterOptions();
}
}}
rows="5"
cols="35"
2021-04-24 03:10:43 +08:00
maxLength={QUESTION_MAX_INPUT_CHARS}
2022-03-22 01:58:11 +08:00
aria-label={intl.formatMessage(autoOptioning ? questionsAndOptionsPlaceholder
: questionPlaceholder)}
placeholder={intl.formatMessage(autoOptioning ? questionsAndOptionsPlaceholder
: questionPlaceholder)}
/>
2022-03-22 01:58:11 +08:00
{hasQuestionError || hasOptionError ? (
2021-11-04 22:19:38 +08:00
<Styled.InputError>{error}</Styled.InputError>
2020-09-22 06:52:38 +08:00
) : (
2021-11-04 22:19:38 +08:00
<Styled.ErrorSpacer>&nbsp;</Styled.ErrorSpacer>
2020-09-22 06:52:38 +08:00
)}
2022-03-22 01:58:11 +08:00
{hasWarning ? (
<Styled.Warning>{warning}</Styled.Warning>
) : null}
</div>
2022-03-22 01:58:11 +08:00
{!autoOptioning
&& (
<div data-test="responseTypes">
<Styled.SectionHeading>
{intl.formatMessage(intlMessages.responseTypesLabel)}
</Styled.SectionHeading>
<Styled.ResponseType>
<Styled.PollConfigButton
selected={type === pollTypes.TrueFalse}
small={!smallSidebar}
label={intl.formatMessage(intlMessages.tf)}
aria-describedby="poll-config-button"
color="default"
onClick={() => {
this.setState({
type: pollTypes.TrueFalse,
optList: [
{ val: intl.formatMessage(intlMessages.true) },
{ val: intl.formatMessage(intlMessages.false) },
],
});
}}
/>
<Styled.PollConfigButton
selected={type === pollTypes.Letter}
small={!smallSidebar}
label={intl.formatMessage(intlMessages.a4)}
aria-describedby="poll-config-button"
data-test="pollLetterAlternatives"
color="default"
onClick={() => {
if (!autoOptioning) {
this.setState({
type: pollTypes.Letter,
optList: [
{ val: intl.formatMessage(intlMessages.a) },
{ val: intl.formatMessage(intlMessages.b) },
{ val: intl.formatMessage(intlMessages.c) },
{ val: intl.formatMessage(intlMessages.d) },
],
});
}
}}
/>
<Styled.PollConfigButton
selected={type === pollTypes.YesNoAbstention}
small={false}
full
label={intl.formatMessage(intlMessages.yna)}
aria-describedby="poll-config-button"
data-test="pollYesNoAbstentionBtn"
color="default"
onClick={() => {
this.setState({
type: pollTypes.YesNoAbstention,
optList: [
{ val: intl.formatMessage(intlMessages.yes) },
{ val: intl.formatMessage(intlMessages.no) },
{ val: intl.formatMessage(intlMessages.abstention) },
],
});
}}
/>
<Styled.PollConfigButton
selected={type === pollTypes.Response}
small={false}
full
label={intl.formatMessage(intlMessages.userResponse)}
aria-describedby="poll-config-button"
data-test="userResponseBtn"
color="default"
onClick={() => { this.setState({ type: pollTypes.Response }); }}
/>
</Styled.ResponseType>
</div>
)}
{((!autoOptioning && type) || (questionAndOptions && autoOptioning))
&& (
<div data-test="responseChoices">
2021-11-04 22:19:38 +08:00
<Styled.SectionHeading>
{intl.formatMessage(intlMessages.responseChoices)}
</Styled.SectionHeading>
{
type === pollTypes.Response
&& (
2021-11-04 22:19:38 +08:00
<Styled.PollParagraph>
<span>{intl.formatMessage(intlMessages.typedResponseDesc)}</span>
2021-11-04 22:19:38 +08:00
</Styled.PollParagraph>
)
}
2022-03-22 01:58:11 +08:00
{
(defaultPoll || type === pollTypes.Response)
&& (
<div style={{
display: 'flex',
flexFlow: 'wrap',
flexDirection: 'column',
}}
>
{defaultPoll
&& (
2021-03-07 22:12:49 +08:00
<div>
2021-11-06 01:29:46 +08:00
<Styled.PollCheckbox>
<Checkbox
onChange={this.toggleIsMultipleResponse}
checked={isMultipleResponse}
ariaLabelledBy="multipleResponseCheckboxLabel"
/>
</Styled.PollCheckbox>
2021-11-04 22:19:38 +08:00
<Styled.InstructionsLabel id="multipleResponseCheckboxLabel">
2021-03-07 22:12:49 +08:00
{intl.formatMessage(intlMessages.enableMultipleResponseLabel)}
2021-11-04 22:19:38 +08:00
</Styled.InstructionsLabel>
2021-03-07 22:12:49 +08:00
</div>
2022-03-22 01:58:11 +08:00
)}
{defaultPoll && this.renderInputs()}
{defaultPoll
&& (
2021-11-04 22:19:38 +08:00
<Styled.AddItemButton
data-test="addPollItem"
label={intl.formatMessage(intlMessages.addOptionLabel)}
2021-08-11 12:10:41 +08:00
aria-describedby="add-item-button"
color="default"
icon="add"
disabled={optList.length >= MAX_CUSTOM_FIELDS}
onClick={() => this.handleAddOption()}
/>
)}
2021-11-04 22:19:38 +08:00
<Styled.Row>
<Styled.Col aria-hidden="true">
<Styled.SectionHeading>
2021-08-09 22:24:02 +08:00
{intl.formatMessage(intlMessages.secretPollLabel)}
2021-11-04 22:19:38 +08:00
</Styled.SectionHeading>
</Styled.Col>
<Styled.Col>
2021-08-09 22:24:02 +08:00
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
2021-11-04 22:19:38 +08:00
<Styled.Toggle>
2021-08-19 04:02:46 +08:00
{this.displayToggleStatus(secretPoll)}
<Toggle
icons={false}
defaultChecked={secretPoll}
onChange={() => this.handleToggle()}
ariaLabel={intl.formatMessage(intlMessages.secretPollLabel)}
2021-08-19 04:02:46 +08:00
showToggleLabel={false}
2022-01-20 21:03:18 +08:00
data-test="anonymousPollBtn"
/>
2021-11-04 22:19:38 +08:00
</Styled.Toggle>
</Styled.Col>
</Styled.Row>
{secretPoll
&& (
2021-11-04 22:19:38 +08:00
<Styled.PollParagraph>
2022-03-22 01:58:11 +08:00
{intl.formatMessage(intlMessages.isSecretPollLabel)}
2021-11-04 22:19:38 +08:00
</Styled.PollParagraph>
)}
2021-11-04 22:19:38 +08:00
<Styled.StartPollBtn
data-test="startPoll"
label={intl.formatMessage(intlMessages.startPollLabel)}
color="primary"
onClick={() => {
2022-03-22 01:58:11 +08:00
const optionsList = optList.slice(0, MAX_CUSTOM_FIELDS);
let hasVal = false;
2022-03-22 01:58:11 +08:00
optionsList.forEach((o) => {
if (o.val.trim().length > 0) hasVal = true;
});
let err = null;
2021-08-09 22:24:02 +08:00
if (type === pollTypes.Response && question.length === 0) {
err = intl.formatMessage(intlMessages.questionErr);
}
if (!hasVal && type !== pollTypes.Response) {
err = intl.formatMessage(intlMessages.optionErr);
}
if (err) return this.setState({ error: err });
return this.setState({ isPolling: true }, () => {
const verifiedPollType = checkPollType(
type,
2022-03-22 01:58:11 +08:00
optionsList,
intl.formatMessage(intlMessages.yes),
intl.formatMessage(intlMessages.no),
intl.formatMessage(intlMessages.abstention),
intl.formatMessage(intlMessages.true),
2021-08-09 22:24:02 +08:00
intl.formatMessage(intlMessages.false),
);
2022-03-22 01:58:11 +08:00
const verifiedOptions = optionsList.map((o) => {
if (o.val.trim().length > 0) return o.val;
return null;
2020-09-22 06:52:38 +08:00
});
if (verifiedPollType === pollTypes.Custom) {
startCustomPoll(
verifiedPollType,
secretPoll,
question,
isMultipleResponse,
_.compact(verifiedOptions),
);
} else {
startPoll(verifiedPollType, secretPoll, question, isMultipleResponse);
}
});
}}
/>
</div>
)
}
</div>
)}
{
FILE_DRAG_AND_DROP_ENABLED
&& type !== pollTypes.Response
&& this.renderDragDrop()
}
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 { intl } = this.props;
return (
2021-11-04 22:19:38 +08:00
<Styled.NoSlidePanelContainer>
<Styled.SectionHeading>
{intl.formatMessage(intlMessages.noPresentationSelected)}
</Styled.SectionHeading>
<Styled.PollButton
label={intl.formatMessage(intlMessages.clickHereToSelect)}
color="primary"
onClick={() => Session.set('showUploadPresentationView', true)}
/>
2021-11-04 22:19:38 +08:00
</Styled.NoSlidePanelContainer>
);
}
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();
2021-08-27 21:47:58 +08:00
if (isPolling || currentPoll) {
return this.renderActivePollOptions();
}
return this.renderPollOptions();
}
renderDragDrop() {
const { intl } = this.props;
return (
<div>
2021-11-04 22:19:38 +08:00
<Styled.Instructions>
{intl.formatMessage(intlMessages.dragDropPollInstruction)}
2021-11-04 22:19:38 +08:00
</Styled.Instructions>
<DragAndDrop
{...{ intl, MAX_INPUT_CHARS }}
handlePollValuesText={(e) => this.handlePollValuesText(e)}
>
2021-11-04 22:19:38 +08:00
<Styled.DragAndDropPollContainer />
</DragAndDrop>
</div>
);
}
render() {
const {
intl,
stopPoll,
currentPoll,
2021-08-05 19:03:24 +08:00
layoutContextDispatch,
} = this.props;
2018-12-18 23:15:51 +08:00
2018-09-25 06:43:54 +08:00
return (
<div>
2021-11-04 22:19:38 +08:00
<Styled.Header>
<Styled.PollHideButton
2019-05-07 04:18:23 +08:00
ref={(node) => { this.hideBtn = node; }}
2020-03-20 22:42:04 +08:00
data-test="hidePollDesc"
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)}
onClick={() => {
2021-08-05 19:03:24 +08:00
layoutContextDispatch({
2021-05-18 04:25:07 +08:00
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
2021-08-05 19:03:24 +08:00
layoutContextDispatch({
2021-05-18 04:25:07 +08:00
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.NONE,
});
}}
/>
2021-11-04 22:19:38 +08:00
<Styled.PollCloseButton
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={() => {
if (currentPoll) stopPoll();
2021-08-05 19:03:24 +08:00
layoutContextDispatch({
2021-05-18 04:25:07 +08:00
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
2021-08-05 19:03:24 +08:00
layoutContextDispatch({
2021-05-18 04:25:07 +08:00
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.NONE,
});
2018-12-18 23:15:51 +08:00
Session.set('forcePollOpen', false);
Session.set('pollInitiated', false);
2018-12-18 23:15:51 +08:00
}}
2018-10-30 00:14:05 +08:00
icon="close"
size="sm"
2018-10-30 23:57:10 +08:00
hideLabel
2022-01-20 21:03:18 +08:00
data-test="closePolling"
2018-10-24 22:17:13 +08:00
/>
2021-11-04 22:19:38 +08:00
</Styled.Header>
{this.renderPollPanel()}
2021-08-11 12:10:41 +08:00
<span className="sr-only" id="poll-config-button">{intl.formatMessage(intlMessages.showRespDesc)}</span>
<span className="sr-only" id="add-item-button">{intl.formatMessage(intlMessages.addRespDesc)}</span>
<span className="sr-only" id="start-poll-button">{intl.formatMessage(intlMessages.startPollDesc)}</span>
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(Object).isRequired,
startPoll: PropTypes.func.isRequired,
startCustomPoll: PropTypes.func.isRequired,
stopPoll: PropTypes.func.isRequired,
};