import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import { withModalMounter } from '/imports/ui/components/modal/service'; import _ from 'lodash'; import { Session } from 'meteor/session'; import cx from 'classnames'; import Button from '/imports/ui/components/button/component'; import Toggle from '/imports/ui/components/switch/component'; import LiveResult from './live-result/component'; import { styles } from './styles.scss'; import { PANELS, ACTIONS } from '../layout/enums'; import DragAndDrop from './dragAndDrop/component'; import { alertScreenReader } from '/imports/utils/dom-utils'; 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', }, 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: '', }, yna: { id: 'app.poll.yna', description: '', }, yes: { id: 'app.poll.y', description: '', }, no: { id: 'app.poll.n', description: '', }, abstention: { id: 'app.poll.abstention', description: '', }, 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', }, }); const POLL_SETTINGS = Meteor.settings.public.poll; const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom; const MAX_INPUT_CHARS = POLL_SETTINGS.maxTypedAnswerLength; const QUESTION_MAX_INPUT_CHARS = 400; const FILE_DRAG_AND_DROP_ENABLED = POLL_SETTINGS.allowDragAndDropFile; const validateInput = (i) => { let _input = i; if (/^\s/.test(_input)) _input = ''; return _input; }; class Poll extends Component { constructor(props) { super(props); this.state = { isPolling: false, question: '', optList: [], error: null, secretPoll: false, }; 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.displayToggleStatus = this.displayToggleStatus.bind(this); } componentDidMount() { const { props } = this.hideBtn; const { className } = props; const hideBtn = document.getElementsByClassName(`${className}`); if (hideBtn[0]) hideBtn[0].focus(); } componentDidUpdate() { const { amIPresenter, layoutContextDispatch } = this.props; if (Session.equals('resetPollPanel', true)) { this.handleBackClick(); } if (!amIPresenter) { 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(); }); } handleInputTextChange(index, text) { const { optList } = this.state; // 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 }); } handleInputChange(e, index) { const { optList, type, error } = this.state; const { pollTypes } = this.props; const list = [...optList]; const validatedVal = validateInput(e.target.value).replace(/\s{2,}/g, ' '); const clearError = validatedVal.length > 0 && type !== pollTypes.Response; list[index] = { val: validatedVal }; this.setState({ optList: list, error: clearError ? null : error }); } handleTextareaChange(e) { const { type, error } = this.state; const { pollTypes } = this.props; const validatedQuestion = validateInput(e.target.value); const clearError = validatedQuestion.length > 0 && type === pollTypes.Response; this.setState({ question: validateInput(e.target.value), error: clearError ? null : error }); } handlePollValuesText(text) { if (text && text.length > 0) { this.pushToCustomPollValues(text); } } handleRemoveOption(index) { const { intl } = this.props; const { optList } = this.state; const list = [...optList]; const removed = list[index]; list.splice(index, 1); this.setState({ optList: list }, () => { alertScreenReader(`${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 }); } 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; } } } displayToggleStatus(status) { const { intl } = this.props; return ( {status ? intl.formatMessage(intlMessages.on) : intl.formatMessage(intlMessages.off)} ); } 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; const { optList, type, error } = this.state; let hasVal = false; return optList.map((o, i) => { if (o.val.length > 0) hasVal = true; const pollOptionKey = `poll-option-${i}`; return (
this.handleInputChange(e, i)} maxLength={MAX_INPUT_CHARS} /> {i > 1 ? ( <>