import React, { Component, createRef } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import { Session } from 'meteor/session';
import Checkbox from '/imports/ui/components/common/checkbox/component';
import DraggableTextArea from '/imports/ui/components/poll/dragAndDrop/component';
import LiveResult from '/imports/ui/components/poll/live-result/component';
import Styled from './styles';
import Toggle from '/imports/ui/components/common/switch/component';
import { PANELS, ACTIONS } from '../layout/enums';
import { addNewAlert } from '../screenreader-alert/service';
import Header from '/imports/ui/components/common/control-header/component';
const intlMessages = defineMessages({
pollPaneTitle: {
id: 'app.poll.pollPaneTitle',
description: 'heading label for the poll menu',
},
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',
},
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',
},
questionErr: {
id: 'app.poll.questionErr',
description: 'question text area error label',
},
questionAndOptionsPlaceholder: {
id: 'app.poll.questionAndoptions.label',
description: 'poll input questions and options label',
},
customInputToggleLabel: {
id: 'app.poll.customInput.label',
description: 'poll custom input toogle button label',
},
customInputInstructionsLabel: {
id: 'app.poll.customInputInstructions.label',
description: 'poll custom input instructions label',
},
maxOptionsWarning: {
id: 'app.poll.maxOptionsWarning.label',
description: 'poll max options error',
},
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: '',
},
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: '',
},
e: {
id: 'app.poll.answer.e',
description: '',
},
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',
},
startPollDesc: {
id: 'app.poll.startPollDesc',
description: '',
},
showRespDesc: {
id: 'app.poll.showRespDesc',
description: '',
},
addRespDesc: {
id: 'app.poll.addRespDesc',
description: '',
},
deleteRespDesc: {
id: 'app.poll.deleteRespDesc',
description: '',
},
on: {
id: 'app.switch.onLabel',
description: 'label for toggle switch on state',
},
off: {
id: 'app.switch.offLabel',
description: 'label for toggle switch off state',
},
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',
},
pollingQuestion: {
id: 'app.polling.pollQuestionTitle',
description: 'polling question header',
}
});
const POLL_SETTINGS = Meteor.settings.public.poll;
const ALLOW_CUSTOM_INPUT = POLL_SETTINGS.allowCustomResponseInput;
const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom;
const MAX_INPUT_CHARS = POLL_SETTINGS.maxTypedAnswerLength;
const MIN_OPTIONS_LENGTH = 2;
const QUESTION_MAX_INPUT_CHARS = 1200;
class Poll extends Component {
constructor(props) {
super(props);
this.state = {
isPolling: false,
question: '',
questionAndOptions: '',
optList: [],
error: null,
isMultipleResponse: false,
secretPoll: false,
customInput: false,
warning: null,
isPasting: false,
type: null,
};
this.textarea = createRef();
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);
this.toggleIsMultipleResponse = this.toggleIsMultipleResponse.bind(this);
this.displayToggleStatus = this.displayToggleStatus.bind(this);
this.displayAutoOptionToggleStatus = this.displayAutoOptionToggleStatus.bind(this);
this.setQuestionAndOptions = this.setQuestionAndOptions.bind(this);
}
componentDidMount() {
if (this.textarea.current) {
this.textarea.current.focus();
}
}
componentDidUpdate() {
const { amIPresenter, layoutContextDispatch, sidebarContentPanel } = this.props;
if (Session.equals('resetPollPanel', true)) {
this.handleBackClick();
}
if (!amIPresenter && sidebarContentPanel === PANELS.POLL) {
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
value: false,
});
layoutContextDispatch({
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
value: PANELS.NONE,
});
}
}
componentWillUnmount() {
Session.set('secretPoll', false);
}
handleBackClick() {
const { stopPoll } = this.props;
this.setState({
isPolling: false,
error: null,
}, () => {
stopPoll();
Session.set('resetPollPanel', false);
document.activeElement.blur();
});
}
/**
*
* @param {Event} e
* @param {Number} index
*/
handleInputChange(e, index) {
const { optList, type, error, questionAndOptions } = this.state;
const { pollTypes, validateInput } = this.props;
const list = [...optList];
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;
let questionAndOptionsList = [];
list[index] = { val: validatedVal };
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;
});
}
/**
*
* @param {Event} e
* @returns {void}
*/
handleTextareaChange(e) {
const { type, error, customInput } = this.state;
const { pollTypes, validateInput } = this.props;
const validatedInput = validateInput(e.target.value);
const clearError = validatedInput.length > 0 && type === pollTypes.Response;
if (!customInput) {
this.setState({
question: validatedInput,
error: clearError ? null : error,
});
} else {
this.setQuestionAndOptions(validatedInput);
}
}
/**
*
* @param {String} input Validated string containing question and options.
* @returns {void}
*/
setQuestionAndOptions(input) {
const { intl, pollTypes, getSplittedQuestionAndOptions } = this.props;
const { warning, optList, isPasting, type, error } = this.state;
const { splittedQuestion, optionsList } = getSplittedQuestionAndOptions(input);
const optionsListLength = optionsList.length;
let maxOptionsWarning = warning;
const clearWarning = maxOptionsWarning && optionsListLength <= MAX_CUSTOM_FIELDS;
const clearError = input.length > 0 && type === pollTypes.Response;
if (optionsListLength > MAX_CUSTOM_FIELDS && optList[MAX_CUSTOM_FIELDS] === undefined) {
this.setState({ warning: intl.formatMessage(intlMessages.maxOptionsWarning) });
if (!isPasting) return null;
maxOptionsWarning = intl.formatMessage(intlMessages.maxOptionsWarning);
this.setState({ isPasting: false });
}
this.setState({
questionAndOptions: input,
optList: optionsList,
question: splittedQuestion,
error: clearError ? null : error,
warning: clearWarning ? null : maxOptionsWarning,
});
}
handlePollValuesText(text) {
const { validateInput } = this.props;
if (text && text.length > 0) {
const validatedInput = validateInput(text);
this.setQuestionAndOptions(validatedInput);
}
}
/**
*
* @param {Number} index
*/
handleRemoveOption(index) {
const { intl } = this.props;
const { optList, questionAndOptions, customInput, warning } = this.state;
const list = [...optList];
const removed = list[index];
let questionAndOptionsList = [];
let clearWarning = false;
list.splice(index, 1);
// If customInput then removing text from input field.
if (customInput) {
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,
}, () => {
addNewAlert(`${intl.formatMessage(intlMessages.removePollOpt,
{ 0: removed.val || intl.formatMessage(intlMessages.emptyPollOpt) })}`);
});
}
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 });
}
handleAutoOptionToogle() {
const { customInput, questionAndOptions, question } = this.state;
const { intl, removeEmptyLineSpaces, getSplittedQuestionAndOptions } = this.props;
const toggledValue = !customInput;
if (customInput === true && toggledValue === false) {
const questionAndOptionsList = removeEmptyLineSpaces(questionAndOptions);
this.setState({
question: questionAndOptionsList.join('\n'),
customInput: toggledValue,
optList: [],
type: null,
});
} else {
const inputList = removeEmptyLineSpaces(question);
const { splittedQuestion, optionsList } = getSplittedQuestionAndOptions(inputList);
const clearWarning = optionsList.length > MAX_CUSTOM_FIELDS
? intl.formatMessage(intlMessages.maxOptionsWarning) : null;
this.handlePollLetterOptions();
this.setState({
questionAndOptions: inputList.join('\n'),
optList: optionsList,
customInput: 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: '' },
],
});
}
}
toggleIsMultipleResponse() {
const { isMultipleResponse } = this.state;
return this.setState({ isMultipleResponse: !isMultipleResponse });
}
/**
*
* @param {Boolean} status
* @returns
*/
displayToggleStatus(status) {
const { intl } = this.props;
return (