import React, { useEffect, useMemo, useRef, useState, } from 'react'; import { useMutation } from '@apollo/client'; import { defineMessages, useIntl } from 'react-intl'; import Checkbox from '/imports/ui/components/common/checkbox/component'; import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser'; import { POLL_SUBMIT_TYPED_VOTE, POLL_SUBMIT_VOTE, } from '/imports/ui/components/poll/mutations'; import { hasPendingPoll, HasPendingPollResponse, } from './queries'; import Service from './service'; import Styled from './styles'; import useDeduplicatedSubscription from '../../core/hooks/useDeduplicatedSubscription'; import { useIsPollingEnabled } from '../../services/features'; const intlMessages = defineMessages({ pollingTitleLabel: { id: 'app.polling.pollingTitle', }, pollAnswerLabel: { id: 'app.polling.pollAnswerLabel', }, pollAnswerDesc: { id: 'app.polling.pollAnswerDesc', }, pollQuestionTitle: { id: 'app.polling.pollQuestionTitle', }, responseIsSecret: { id: 'app.polling.responseSecret', }, responseNotSecret: { id: 'app.polling.responseNotSecret', }, submitLabel: { id: 'app.polling.submitLabel', }, submitAriaLabel: { id: 'app.polling.submitAriaLabel', }, responsePlaceholder: { id: 'app.polling.responsePlaceholder', }, }); const validateInput = (i: string) => { let input = i; if (/^\s/.test(input)) input = ''; return input; }; interface PollingGraphqlProps { handleTypedVote: (pollId: string, answer: string) => void; handleVote: (pollId: string, answerIds: Array) => void; pollAnswerIds: Record; pollTypes: Record; isDefaultPoll: (pollType: string) => boolean; playAlert: () => void; poll: { pollId: string; multipleResponses: boolean; type: string; stackOptions: boolean; questionText: string; secret: boolean; options: Array<{ optionDesc: string; optionId: number; pollId: string; }>; }; } const PollingGraphql: React.FC = (props) => { const { handleTypedVote, handleVote, poll, pollAnswerIds, pollTypes, isDefaultPoll, playAlert, } = props; const [typedAns, setTypedAns] = useState(''); const [checkedAnswers, setCheckedAnswers] = useState>([]); const intl = useIntl(); const responseInput = useRef(null); const pollingContainer = useRef(null); useEffect(() => { playAlert(); if (pollingContainer.current) { pollingContainer.current.focus(); } }, []); const handleUpdateResponseInput = (e: React.ChangeEvent) => { if (responseInput.current) { responseInput.current.value = validateInput(e.target.value); setTypedAns(responseInput.current.value); } }; const handleSubmit = (pollId: string) => { handleVote(pollId, checkedAnswers); }; const handleCheckboxChange = (answerId: number) => { if (checkedAnswers.includes(answerId)) { checkedAnswers.splice(checkedAnswers.indexOf(answerId), 1); } else { checkedAnswers.push(answerId); } checkedAnswers.sort(); setCheckedAnswers([...checkedAnswers]); }; const handleMessageKeyDown = (e: React.KeyboardEvent) => { if (e.keyCode === 13 && typedAns.length > 0) { handleTypedVote(poll.pollId, typedAns); } }; const renderButtonAnswers = () => { const { stackOptions, options, questionText, type, } = poll; const defaultPoll = isDefaultPoll(type); const MAX_INPUT_CHARS = window.meetingClientSettings.public.poll.maxTypedAnswerLength; return (
{poll.type !== pollTypes.Response && ( {questionText.length === 0 && ( {intl.formatMessage(intlMessages.pollingTitleLabel)} )} {options.map((option) => { const formattedMessageIndex = option.optionDesc.toLowerCase(); let label = option.optionDesc; if ( (defaultPoll || type.includes('CUSTOM')) && pollAnswerIds[formattedMessageIndex] ) { label = intl.formatMessage( pollAnswerIds[formattedMessageIndex], ); } return ( handleVote(poll.pollId, [option.optionId])} aria-labelledby={`pollAnswerLabel${option.optionDesc}`} aria-describedby={`pollAnswerDesc${option.optionDesc}`} data-test="pollAnswerOption" /> {intl.formatMessage(intlMessages.pollAnswerLabel, { 0: label, })} {intl.formatMessage(intlMessages.pollAnswerDesc, { 0: label, })} ); })} )} {poll.type === pollTypes.Response && ( { handleUpdateResponseInput(e); }} onKeyDown={(e) => { handleMessageKeyDown(e); }} type="text" placeholder={intl.formatMessage(intlMessages.responsePlaceholder)} maxLength={MAX_INPUT_CHARS} ref={responseInput} onPaste={(e) => { e.stopPropagation(); }} onCut={(e) => { e.stopPropagation(); }} onCopy={(e) => { e.stopPropagation(); }} /> { handleTypedVote(poll.pollId, typedAns); }} /> )} {intl.formatMessage( poll.secret ? intlMessages.responseIsSecret : intlMessages.responseNotSecret, )}
); }; const renderCheckboxAnswers = () => { return (
{poll.questionText.length === 0 && ( {intl.formatMessage(intlMessages.pollingTitleLabel)} )} {poll.options.map((option) => { const formattedMessageIndex = option.optionDesc.toLowerCase(); let label = option.optionDesc; if (pollAnswerIds[formattedMessageIndex]) { label = intl.formatMessage(pollAnswerIds[formattedMessageIndex]); } return ( {/* eslint-disable-next-line */} handleCheckboxChange(option.optionId)} checked={checkedAnswers.includes(option.optionId)} ariaLabelledBy={`pollAnswerLabel${option.optionDesc}`} ariaDescribedBy={`pollAnswerDesc${option.optionDesc}`} /> {intl.formatMessage(intlMessages.pollAnswerDesc, { 0: label, })} ); })}
handleSubmit(poll.pollId)} data-test="submitAnswersMultiple" />
); }; return ( {poll.questionText.length > 0 && ( {intl.formatMessage(intlMessages.pollQuestionTitle)} {poll.questionText} )} {poll.multipleResponses ? renderCheckboxAnswers() : renderButtonAnswers()} ); }; const PollingGraphqlContainer: React.FC = () => { const { data: currentUserData } = useCurrentUser((u) => ({ userId: u.userId, presenter: u.presenter, })); const { data: hasPendingPollData, error, loading } = useDeduplicatedSubscription( hasPendingPoll, { variables: { userId: currentUserData?.userId }, skip: !currentUserData, }, ); const [pollSubmitUserTypedVote] = useMutation(POLL_SUBMIT_TYPED_VOTE); const [pollSubmitUserVote] = useMutation(POLL_SUBMIT_VOTE); const isPollingEnabled = useIsPollingEnabled(); const meetingData = hasPendingPollData && hasPendingPollData.meeting[0]; const pollData = meetingData && meetingData.polls[0]; const userData = pollData && pollData.users[0]; const pollExists = !!userData; const showPolling = pollExists && !currentUserData?.presenter && isPollingEnabled; const stackOptions = useMemo( () => !!pollData && Service.shouldStackOptions(pollData.options.map((o) => o.optionDesc)), [pollData], ); const handleTypedVote = (pollId: string, answer: string) => { pollSubmitUserTypedVote({ variables: { pollId, answer, }, }); }; const handleVote = (pollId: string, answerIds: Array) => { pollSubmitUserVote({ variables: { pollId, answerIds, }, }); }; if (!showPolling || error || loading) return null; return ( ); }; export default PollingGraphqlContainer;