Merge remote-tracking branch 'upstream/v3.0.x-release' into migrate-breakout-invitation
This commit is contained in:
commit
901d2a7fbb
@ -71,6 +71,7 @@ object PluginDataChannelMessageDAO {
|
|||||||
.filter(_.meetingId === meetingId)
|
.filter(_.meetingId === meetingId)
|
||||||
.filter(_.pluginName === pluginName)
|
.filter(_.pluginName === pluginName)
|
||||||
.filter(_.dataChannel === dataChannel)
|
.filter(_.dataChannel === dataChannel)
|
||||||
|
.filter(_.deletedAt.isEmpty)
|
||||||
.map(u => (u.deletedAt))
|
.map(u => (u.deletedAt))
|
||||||
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
|
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
|
||||||
).onComplete {
|
).onComplete {
|
||||||
|
@ -15,7 +15,9 @@ type ActivitiesOverviewObj struct {
|
|||||||
Completed int64
|
Completed int64
|
||||||
DataReceived int64
|
DataReceived int64
|
||||||
DataSizeAvg int64
|
DataSizeAvg int64
|
||||||
|
DataSizeMax int64
|
||||||
DataCountAvg int64
|
DataCountAvg int64
|
||||||
|
DataCountMax int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var ActivitiesOverviewEnabled = false
|
var ActivitiesOverviewEnabled = false
|
||||||
@ -36,7 +38,9 @@ func ActivitiesOverviewStarted(index string) {
|
|||||||
Completed: 0,
|
Completed: 0,
|
||||||
DataReceived: 0,
|
DataReceived: 0,
|
||||||
DataSizeAvg: 0,
|
DataSizeAvg: 0,
|
||||||
|
DataSizeMax: 0,
|
||||||
DataCountAvg: 0,
|
DataCountAvg: 0,
|
||||||
|
DataCountMax: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,21 +65,6 @@ func ActivitiesOverviewDataReceived(index string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ActivitiesOverviewDataSize(index string, dataSize int64, dataCount int64) {
|
|
||||||
if !ActivitiesOverviewEnabled {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
activitiesOverviewMux.Lock()
|
|
||||||
defer activitiesOverviewMux.Unlock()
|
|
||||||
|
|
||||||
if updatedValues, exists := activitiesOverview[index]; exists {
|
|
||||||
updatedValues.DataSizeAvg = ((updatedValues.DataSizeAvg*updatedValues.DataReceived - 1) + dataSize) / updatedValues.DataReceived
|
|
||||||
updatedValues.DataCountAvg = ((updatedValues.DataCountAvg * (updatedValues.DataReceived - 1)) + dataCount) / updatedValues.DataReceived
|
|
||||||
activitiesOverview[index] = updatedValues
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ActivitiesOverviewCompleted(index string) {
|
func ActivitiesOverviewCompleted(index string) {
|
||||||
if !ActivitiesOverviewEnabled {
|
if !ActivitiesOverviewEnabled {
|
||||||
return
|
return
|
||||||
@ -89,7 +78,27 @@ func ActivitiesOverviewCompleted(index string) {
|
|||||||
|
|
||||||
activitiesOverview[index] = updatedValues
|
activitiesOverview[index] = updatedValues
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ActivitiesOverviewDataSize(index string, dataSize int64, dataCount int64) {
|
||||||
|
if !ActivitiesOverviewEnabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activitiesOverviewMux.Lock()
|
||||||
|
defer activitiesOverviewMux.Unlock()
|
||||||
|
|
||||||
|
if updatedValues, exists := activitiesOverview[index]; exists {
|
||||||
|
updatedValues.DataSizeAvg = ((updatedValues.DataSizeAvg * (updatedValues.DataReceived - 1)) + dataSize) / updatedValues.DataReceived
|
||||||
|
if dataSize > updatedValues.DataSizeMax {
|
||||||
|
updatedValues.DataSizeMax = dataSize
|
||||||
|
}
|
||||||
|
updatedValues.DataCountAvg = ((updatedValues.DataCountAvg * (updatedValues.DataReceived - 1)) + dataCount) / updatedValues.DataReceived
|
||||||
|
if dataCount > updatedValues.DataCountMax {
|
||||||
|
updatedValues.DataCountMax = dataCount
|
||||||
|
}
|
||||||
|
activitiesOverview[index] = updatedValues
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetActivitiesOverview() map[string]ActivitiesOverviewObj {
|
func GetActivitiesOverview() map[string]ActivitiesOverviewObj {
|
||||||
@ -114,8 +123,10 @@ func ActivitiesOverviewLogRoutine() {
|
|||||||
if strings.HasPrefix(index, "_") ||
|
if strings.HasPrefix(index, "_") ||
|
||||||
item.Started > hasuraConnections*3 ||
|
item.Started > hasuraConnections*3 ||
|
||||||
item.DataReceived > hasuraConnections*5 ||
|
item.DataReceived > hasuraConnections*5 ||
|
||||||
item.DataSizeAvg > 3000 ||
|
item.DataSizeAvg > 4000 ||
|
||||||
item.DataCountAvg > 5 {
|
item.DataSizeMax > 50000 ||
|
||||||
|
item.DataCountAvg > 5 ||
|
||||||
|
(item.DataCountMax > 10 && item.DataCountMax >= hasuraConnections) {
|
||||||
topMessages[index] = item
|
topMessages[index] = item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,8 +31,8 @@ func HasuraClient(browserConnection *common.BrowserConnection, cookies []*http.C
|
|||||||
//Remove subscriptions from ActivitiesOverview here once Hasura-Reader will ignore "complete" msg for them
|
//Remove subscriptions from ActivitiesOverview here once Hasura-Reader will ignore "complete" msg for them
|
||||||
browserConnection.ActiveSubscriptionsMutex.RLock()
|
browserConnection.ActiveSubscriptionsMutex.RLock()
|
||||||
for _, subscription := range browserConnection.ActiveSubscriptions {
|
for _, subscription := range browserConnection.ActiveSubscriptions {
|
||||||
common.ActivitiesOverviewStarted(string(subscription.Type) + "-" + subscription.OperationName)
|
common.ActivitiesOverviewCompleted(string(subscription.Type) + "-" + subscription.OperationName)
|
||||||
common.ActivitiesOverviewStarted("_Sum-" + string(subscription.Type))
|
common.ActivitiesOverviewCompleted("_Sum-" + string(subscription.Type))
|
||||||
}
|
}
|
||||||
browserConnection.ActiveSubscriptionsMutex.RUnlock()
|
browserConnection.ActiveSubscriptionsMutex.RUnlock()
|
||||||
}()
|
}()
|
||||||
|
@ -61,3 +61,4 @@ select_permissions:
|
|||||||
filter:
|
filter:
|
||||||
meetingId:
|
meetingId:
|
||||||
_eq: X-Hasura-MeetingId
|
_eq: X-Hasura-MeetingId
|
||||||
|
allow_aggregations: true
|
||||||
|
@ -42,3 +42,4 @@ select_permissions:
|
|||||||
filter:
|
filter:
|
||||||
meetingId:
|
meetingId:
|
||||||
_eq: X-Hasura-PresenterInMeeting
|
_eq: X-Hasura-PresenterInMeeting
|
||||||
|
allow_aggregations: true
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import Button from '/imports/ui/components/common/button/component';
|
import Button from '/imports/ui/components/common/button/component';
|
||||||
import ConnectionStatusModalComponent from '/imports/ui/components/connection-status/modal/component';
|
import ConnectionStatusModalComponent from '/imports/ui/components/connection-status/modal/container';
|
||||||
import ConnectionStatusService from '/imports/ui/components/connection-status/service';
|
import ConnectionStatusService from '/imports/ui/components/connection-status/service';
|
||||||
import Icon from '/imports/ui/components/connection-status/icon/component';
|
import Icon from '/imports/ui/components/connection-status/icon/component';
|
||||||
import Styled from './styles';
|
import Styled from './styles';
|
||||||
@ -40,17 +40,12 @@ class ConnectionStatusButton extends PureComponent {
|
|||||||
setModalIsOpen = (isOpen) => this.setState({ isModalOpen: isOpen });
|
setModalIsOpen = (isOpen) => this.setState({ isModalOpen: isOpen });
|
||||||
|
|
||||||
renderModal(isModalOpen) {
|
renderModal(isModalOpen) {
|
||||||
const {
|
|
||||||
connectionData,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isModalOpen ?
|
isModalOpen ?
|
||||||
<ConnectionStatusModalComponent
|
<ConnectionStatusModalComponent
|
||||||
{...{
|
{...{
|
||||||
isModalOpen,
|
isModalOpen,
|
||||||
setModalIsOpen: this.setModalIsOpen,
|
setModalIsOpen: this.setModalIsOpen,
|
||||||
connectionData,
|
|
||||||
}}
|
}}
|
||||||
/> : null
|
/> : null
|
||||||
)
|
)
|
||||||
@ -85,17 +80,11 @@ class ConnectionStatusButton extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
connectionData,
|
myCurrentStatus,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const ownConnectionData = connectionData.filter((curr) => curr.user.userId === Auth.userID);
|
|
||||||
|
|
||||||
const currentStatus = ownConnectionData && ownConnectionData.length > 0
|
|
||||||
? ownConnectionData[0].currentStatus
|
|
||||||
: 'normal';
|
|
||||||
|
|
||||||
let color;
|
let color;
|
||||||
switch (currentStatus) {
|
switch (myCurrentStatus) {
|
||||||
case 'warning':
|
case 'warning':
|
||||||
color = 'success';
|
color = 'success';
|
||||||
break;
|
break;
|
||||||
@ -114,7 +103,7 @@ class ConnectionStatusButton extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<Styled.ButtonWrapper>
|
<Styled.ButtonWrapper>
|
||||||
<Button
|
<Button
|
||||||
customIcon={this.renderIcon(currentStatus)}
|
customIcon={this.renderIcon(myCurrentStatus)}
|
||||||
label={intl.formatMessage(intlMessages.label)}
|
label={intl.formatMessage(intlMessages.label)}
|
||||||
hideLabel
|
hideLabel
|
||||||
aria-label={intl.formatMessage(intlMessages.description)}
|
aria-label={intl.formatMessage(intlMessages.description)}
|
||||||
|
@ -3,15 +3,18 @@ import { Meteor } from 'meteor/meteor';
|
|||||||
import { withTracker } from 'meteor/react-meteor-data';
|
import { withTracker } from 'meteor/react-meteor-data';
|
||||||
import { useSubscription } from '@apollo/client';
|
import { useSubscription } from '@apollo/client';
|
||||||
import ConnectionStatusButtonComponent from './component';
|
import ConnectionStatusButtonComponent from './component';
|
||||||
import Service from '../service';
|
import { USER_CURRENT_STATUS_SUBSCRIPTION } from '../queries';
|
||||||
import { CONNECTION_STATUS_REPORT_SUBSCRIPTION } from '../queries';
|
import Auth from '/imports/ui/services/auth';
|
||||||
|
|
||||||
const connectionStatusButtonContainer = (props) => {
|
const connectionStatusButtonContainer = (props) => {
|
||||||
const { data } = useSubscription(CONNECTION_STATUS_REPORT_SUBSCRIPTION);
|
const { data } = useSubscription(USER_CURRENT_STATUS_SUBSCRIPTION, {
|
||||||
|
variables: { userId: Auth.userID },
|
||||||
|
});
|
||||||
|
const myCurrentStatus = data && data.length > 0
|
||||||
|
? data[0].currentStatus
|
||||||
|
: 'normal';
|
||||||
|
|
||||||
const connectionData = data ? Service.sortConnectionData(data.user_connectionStatusReport) : [];
|
return <ConnectionStatusButtonComponent myCurrentStatus={myCurrentStatus} {...props} />;
|
||||||
|
|
||||||
return <ConnectionStatusButtonComponent connectionData={connectionData} {...props} />;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTracker(() => {
|
export default withTracker(() => {
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useSubscription } from '@apollo/client';
|
||||||
|
import { CONNECTION_STATUS_REPORT_SUBSCRIPTION } from '../queries';
|
||||||
|
import Service from '../service';
|
||||||
|
import Component from './component';
|
||||||
|
|
||||||
|
const ConnectionStatusContainer = (props) => {
|
||||||
|
const { data } = useSubscription(CONNECTION_STATUS_REPORT_SUBSCRIPTION);
|
||||||
|
const connectionData = data ? Service.sortConnectionData(data.user_connectionStatusReport) : [];
|
||||||
|
return (
|
||||||
|
<Component
|
||||||
|
connectionData={connectionData}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConnectionStatusContainer;
|
@ -1,7 +1,13 @@
|
|||||||
import { gql } from '@apollo/client';
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
export const CONNECTION_STATUS_REPORT_SUBSCRIPTION = gql`subscription ConnStatusReport {
|
export const CONNECTION_STATUS_REPORT_SUBSCRIPTION = gql`subscription ConnStatusReport {
|
||||||
user_connectionStatusReport {
|
user_connectionStatusReport(
|
||||||
|
where: {
|
||||||
|
_or: [
|
||||||
|
{ clientNotResponding: { _eq: true } },
|
||||||
|
{ lastUnstableStatus: { _is_null: false } }
|
||||||
|
]
|
||||||
|
}) {
|
||||||
user {
|
user {
|
||||||
userId
|
userId
|
||||||
name
|
name
|
||||||
@ -17,6 +23,20 @@ export const CONNECTION_STATUS_REPORT_SUBSCRIPTION = gql`subscription ConnStatus
|
|||||||
}
|
}
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
|
export const USER_CURRENT_STATUS_SUBSCRIPTION = gql`
|
||||||
|
subscription CurrentUserConnStatus($userId: String!) {
|
||||||
|
user_connectionStatusReport(
|
||||||
|
where: {
|
||||||
|
user: {
|
||||||
|
userId: { _eq: $userId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
currentStatus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const CONNECTION_STATUS_SUBSCRIPTION = gql`subscription ConnStatus {
|
export const CONNECTION_STATUS_SUBSCRIPTION = gql`subscription ConnStatus {
|
||||||
user_connectionStatus {
|
user_connectionStatus {
|
||||||
connectionAliveAt
|
connectionAliveAt
|
||||||
|
@ -8,6 +8,7 @@ import Auth from '/imports/ui/services/auth';
|
|||||||
import { UsersContext } from '../components-data/users-context/context';
|
import { UsersContext } from '../components-data/users-context/context';
|
||||||
import { layoutDispatch, layoutSelectInput } from '../layout/context';
|
import { layoutDispatch, layoutSelectInput } from '../layout/context';
|
||||||
import { POLL_PUBLISH_RESULT, POLL_CANCEL, POLL_CREATE } from './mutations';
|
import { POLL_PUBLISH_RESULT, POLL_CANCEL, POLL_CREATE } from './mutations';
|
||||||
|
import PollCreationPanelContainer from './poll-graphql/component';
|
||||||
import { ACTIONS, PANELS } from '../layout/enums';
|
import { ACTIONS, PANELS } from '../layout/enums';
|
||||||
|
|
||||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||||
@ -87,7 +88,7 @@ const PollContainer = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTracker(({ amIPresenter, currentSlideId }) => {
|
withTracker(({ amIPresenter, currentSlideId }) => {
|
||||||
const isPollSecret = Session.get('secretPoll') || false;
|
const isPollSecret = Session.get('secretPoll') || false;
|
||||||
|
|
||||||
Meteor.subscribe('current-poll', isPollSecret, amIPresenter);
|
Meteor.subscribe('current-poll', isPollSecret, amIPresenter);
|
||||||
@ -109,3 +110,5 @@ export default withTracker(({ amIPresenter, currentSlideId }) => {
|
|||||||
getSplittedQuestionAndOptions: Service.getSplittedQuestionAndOptions,
|
getSplittedQuestionAndOptions: Service.getSplittedQuestionAndOptions,
|
||||||
};
|
};
|
||||||
})(PollContainer);
|
})(PollContainer);
|
||||||
|
|
||||||
|
export default PollCreationPanelContainer;
|
||||||
|
@ -0,0 +1,572 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Session } from 'meteor/session';
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||||
|
import Header from '/imports/ui/components/common/control-header/component';
|
||||||
|
import { useMutation, useSubscription } from '@apollo/client';
|
||||||
|
import { Input } from '../../layout/layoutTypes';
|
||||||
|
import { layoutDispatch, layoutSelectInput } from '../../layout/context';
|
||||||
|
import { addNewAlert } from '../../screenreader-alert/service';
|
||||||
|
import { PANELS, ACTIONS } from '../../layout/enums';
|
||||||
|
import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
||||||
|
import { POLL_CANCEL } from './mutation';
|
||||||
|
import { GetHasCurrentPresentationResponse, getHasCurrentPresentation } from './queries';
|
||||||
|
import EmptySlideArea from './components/EmptySlideArea';
|
||||||
|
import { getSplittedQuestionAndOptions, pollTypes, validateInput } from './service';
|
||||||
|
import Toggle from '/imports/ui/components/common/switch/component';
|
||||||
|
import Styled from '../styles';
|
||||||
|
import ResponseChoices from './components/ResponseChoices';
|
||||||
|
import ResponseTypes from './components/ResponseTypes';
|
||||||
|
import PollQuestionArea from './components/PollQuestionArea';
|
||||||
|
import LiveResultContainer from './components/LiveResult';
|
||||||
|
|
||||||
|
const POLL_SETTINGS = Meteor.settings.public.poll;
|
||||||
|
const ALLOW_CUSTOM_INPUT = POLL_SETTINGS.allowCustomResponseInput;
|
||||||
|
const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom;
|
||||||
|
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface PollCreationPanelProps {
|
||||||
|
layoutContextDispatch: (action: {
|
||||||
|
type: string;
|
||||||
|
value: string | boolean;
|
||||||
|
}) => void;
|
||||||
|
hasPoll: boolean;
|
||||||
|
hasCurrentPresentation: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PollCreationPanel: React.FC<PollCreationPanelProps> = ({
|
||||||
|
layoutContextDispatch,
|
||||||
|
hasPoll,
|
||||||
|
hasCurrentPresentation,
|
||||||
|
}) => {
|
||||||
|
const [stopPoll] = useMutation(POLL_CANCEL);
|
||||||
|
|
||||||
|
const intl = useIntl();
|
||||||
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
const [customInput, setCustomInput] = React.useState(false);
|
||||||
|
const [question, setQuestion] = useState<string[] | string>('');
|
||||||
|
const [questionAndOptions, setQuestionAndOptions] = useState<string[] | string>('');
|
||||||
|
const [optList, setOptList] = useState<Array<{val: string}>>([]);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [isMultipleResponse, setIsMultipleResponse] = useState(false);
|
||||||
|
const [secretPoll, setSecretPoll] = useState(false);
|
||||||
|
const [warning, setWarning] = useState<string | null>('');
|
||||||
|
const [isPasting, setIsPasting] = useState(false);
|
||||||
|
const [type, setType] = useState<string | null>('');
|
||||||
|
|
||||||
|
const handleInputChange = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
index: number,
|
||||||
|
) => {
|
||||||
|
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 ?? 0;
|
||||||
|
const caretEnd = e.target.selectionEnd ?? 0;
|
||||||
|
let questionAndOptionsList: string[] = [];
|
||||||
|
list[index] = { val: validatedVal };
|
||||||
|
|
||||||
|
if (questionAndOptions.length > 0) {
|
||||||
|
const QnO = questionAndOptions as string;
|
||||||
|
questionAndOptionsList = QnO.split('\n');
|
||||||
|
questionAndOptionsList[index + 1] = validatedVal;
|
||||||
|
}
|
||||||
|
setOptList(list);
|
||||||
|
setQuestionAndOptions(questionAndOptionsList.length > 0
|
||||||
|
? questionAndOptionsList.join('\n') : '');
|
||||||
|
setError(clearError ? null : error);
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
input.selectionStart = caretStart - charsRemovedCount;
|
||||||
|
input.selectionEnd = caretEnd - charsRemovedCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setQuestionAndOptionsFn = (input: string[] | string) => {
|
||||||
|
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) {
|
||||||
|
setWarning(intl.formatMessage(intlMessages.maxOptionsWarning));
|
||||||
|
if (isPasting) {
|
||||||
|
maxOptionsWarning = intl.formatMessage(intlMessages.maxOptionsWarning);
|
||||||
|
setIsPasting(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setQuestionAndOptions(input);
|
||||||
|
setOptList(optionsList);
|
||||||
|
setQuestion(splittedQuestion);
|
||||||
|
setError(clearError ? null : error);
|
||||||
|
setWarning(clearWarning ? null : maxOptionsWarning);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
const validatedInput = validateInput(e.target.value);
|
||||||
|
const clearError = validatedInput.length > 0 && type === pollTypes.Response;
|
||||||
|
|
||||||
|
if (!customInput) {
|
||||||
|
setQuestion(validatedInput);
|
||||||
|
setError(clearError ? null : error);
|
||||||
|
} else {
|
||||||
|
setQuestionAndOptionsFn(validatedInput);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveOption = (index: number) => {
|
||||||
|
const list = [...optList];
|
||||||
|
const removed = list[index];
|
||||||
|
let questionAndOptionsList: string[] = [];
|
||||||
|
let clearWarning = false;
|
||||||
|
|
||||||
|
list.splice(index, 1);
|
||||||
|
|
||||||
|
// If customInput then removing text from input field.
|
||||||
|
if (customInput) {
|
||||||
|
const QnO = questionAndOptions as string;
|
||||||
|
questionAndOptionsList = QnO.split('\n');
|
||||||
|
delete questionAndOptionsList[index + 1];
|
||||||
|
questionAndOptionsList = questionAndOptionsList.filter((val: string) => val !== undefined);
|
||||||
|
clearWarning = !!warning && list.length <= MAX_CUSTOM_FIELDS;
|
||||||
|
}
|
||||||
|
setOptList(list);
|
||||||
|
setQuestionAndOptions(questionAndOptionsList.length > 0
|
||||||
|
? questionAndOptionsList.join('\n') : '');
|
||||||
|
setWarning(clearWarning ? null : warning);
|
||||||
|
addNewAlert(`${intl.formatMessage(intlMessages.removePollOpt,
|
||||||
|
{ 0: removed.val || intl.formatMessage(intlMessages.emptyPollOpt) })}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddOption = () => {
|
||||||
|
setOptList([...optList, { val: '' }]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggle = () => {
|
||||||
|
const toggledValue = !secretPoll;
|
||||||
|
Session.set('secretPoll', toggledValue);
|
||||||
|
setSecretPoll(toggledValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePollLetterOptions = () => {
|
||||||
|
if (optList.length === 0) {
|
||||||
|
setType(pollTypes.Letter);
|
||||||
|
setOptList([
|
||||||
|
{ val: '' },
|
||||||
|
{ val: '' },
|
||||||
|
{ val: '' },
|
||||||
|
{ val: '' },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleIsMultipleResponse = () => {
|
||||||
|
setIsMultipleResponse((prev) => !prev);
|
||||||
|
return !isMultipleResponse;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
Session.set('secretPoll', false);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (textareaRef.current) {
|
||||||
|
textareaRef.current?.focus();
|
||||||
|
}
|
||||||
|
}, [textareaRef]);
|
||||||
|
|
||||||
|
const pollOptions = () => {
|
||||||
|
if (!hasCurrentPresentation) return <EmptySlideArea />;
|
||||||
|
if (hasPoll) return <LiveResultContainer />;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
ALLOW_CUSTOM_INPUT && (
|
||||||
|
<Styled.CustomInputRow>
|
||||||
|
<Styled.CustomInputHeadingCol aria-hidden="true">
|
||||||
|
<Styled.CustomInputHeading>
|
||||||
|
{intl.formatMessage(intlMessages.customInputToggleLabel)}
|
||||||
|
</Styled.CustomInputHeading>
|
||||||
|
</Styled.CustomInputHeadingCol>
|
||||||
|
<Styled.CustomInputToggleCol>
|
||||||
|
<Styled.Toggle>
|
||||||
|
<Styled.ToggleLabel>
|
||||||
|
{customInput
|
||||||
|
? intl.formatMessage(intlMessages.on)
|
||||||
|
: intl.formatMessage(intlMessages.off)}
|
||||||
|
</Styled.ToggleLabel>
|
||||||
|
<Toggle
|
||||||
|
// @ts-ignore - JS component wrapped by intl
|
||||||
|
icons={false}
|
||||||
|
defaultChecked={customInput}
|
||||||
|
onChange={() => {
|
||||||
|
setCustomInput(!customInput);
|
||||||
|
setType(pollTypes.Custom);
|
||||||
|
}}
|
||||||
|
ariaLabel={intl.formatMessage(intlMessages.customInputToggleLabel)}
|
||||||
|
showToggleLabel={false}
|
||||||
|
data-test="autoOptioningPollBtn"
|
||||||
|
/>
|
||||||
|
</Styled.Toggle>
|
||||||
|
</Styled.CustomInputToggleCol>
|
||||||
|
</Styled.CustomInputRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{customInput && (
|
||||||
|
<Styled.PollParagraph>
|
||||||
|
{intl.formatMessage(intlMessages.customInputInstructionsLabel)}
|
||||||
|
</Styled.PollParagraph>
|
||||||
|
)}
|
||||||
|
<PollQuestionArea
|
||||||
|
customInput={customInput}
|
||||||
|
question={question}
|
||||||
|
questionAndOptions={questionAndOptions}
|
||||||
|
handleTextareaChange={handleTextareaChange}
|
||||||
|
error={error}
|
||||||
|
type={type}
|
||||||
|
textareaRef={textareaRef}
|
||||||
|
handlePollLetterOptions={handlePollLetterOptions}
|
||||||
|
optList={optList}
|
||||||
|
setIsPasting={setIsPasting}
|
||||||
|
warning={warning}
|
||||||
|
/>
|
||||||
|
<ResponseTypes
|
||||||
|
customInput={customInput}
|
||||||
|
type={type}
|
||||||
|
setOptList={setOptList}
|
||||||
|
setType={setType}
|
||||||
|
/>
|
||||||
|
<ResponseChoices
|
||||||
|
type={type}
|
||||||
|
toggleIsMultipleResponse={toggleIsMultipleResponse}
|
||||||
|
isMultipleResponse={isMultipleResponse}
|
||||||
|
optList={optList}
|
||||||
|
handleAddOption={handleAddOption}
|
||||||
|
secretPoll={secretPoll}
|
||||||
|
question={question}
|
||||||
|
setError={setError}
|
||||||
|
setIsPolling={() => {
|
||||||
|
setType(null);
|
||||||
|
setOptList([]);
|
||||||
|
setQuestion('');
|
||||||
|
setQuestionAndOptions('');
|
||||||
|
}}
|
||||||
|
hasCurrentPresentation={hasCurrentPresentation}
|
||||||
|
handleToggle={handleToggle}
|
||||||
|
error={error}
|
||||||
|
handleInputChange={handleInputChange}
|
||||||
|
handleRemoveOption={handleRemoveOption}
|
||||||
|
customInput={customInput}
|
||||||
|
questionAndOptions={questionAndOptions}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Header
|
||||||
|
data-test="pollPaneTitle"
|
||||||
|
leftButtonProps={{
|
||||||
|
'aria-label': intl.formatMessage(intlMessages.hidePollDesc),
|
||||||
|
'data-test': 'hidePollDesc',
|
||||||
|
label: intl.formatMessage(intlMessages.pollPaneTitle),
|
||||||
|
onClick: () => {
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||||
|
value: false,
|
||||||
|
});
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||||
|
value: PANELS.NONE,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rightButtonProps={{
|
||||||
|
'aria-label': `${intl.formatMessage(intlMessages.closeLabel)} ${intl.formatMessage(intlMessages.pollPaneTitle)}`,
|
||||||
|
'data-test': 'closePolling',
|
||||||
|
icon: 'close',
|
||||||
|
label: intl.formatMessage(intlMessages.closeLabel),
|
||||||
|
onClick: () => {
|
||||||
|
if (hasPoll) stopPoll();
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||||
|
value: false,
|
||||||
|
});
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||||
|
value: PANELS.NONE,
|
||||||
|
});
|
||||||
|
Session.set('forcePollOpen', false);
|
||||||
|
Session.set('pollInitiated', false);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
customRightButton={null}
|
||||||
|
/>
|
||||||
|
{pollOptions()}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PollCreationPanelContainer: React.FC = () => {
|
||||||
|
const sidebarContent = layoutSelectInput((i: Input) => i.sidebarContent);
|
||||||
|
const layoutContextDispatch = layoutDispatch();
|
||||||
|
const { sidebarContentPanel } = sidebarContent;
|
||||||
|
const {
|
||||||
|
data: currentUser,
|
||||||
|
loading: currentUserLoading,
|
||||||
|
} = useCurrentUser((u) => {
|
||||||
|
return {
|
||||||
|
presenter: u?.presenter,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: currentMeeting,
|
||||||
|
loading: currentMeetingLoading,
|
||||||
|
} = useMeeting((m) => {
|
||||||
|
return {
|
||||||
|
componentsFlags: m?.componentsFlags,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: getHasCurrentPresentationData,
|
||||||
|
loading: getHasCurrentPresentationLoading,
|
||||||
|
} = useSubscription<GetHasCurrentPresentationResponse>(getHasCurrentPresentation);
|
||||||
|
|
||||||
|
if (currentUserLoading || !currentUser) return null;
|
||||||
|
if (currentMeetingLoading || !currentMeeting) return null;
|
||||||
|
if (getHasCurrentPresentationLoading || !getHasCurrentPresentationData) return null;
|
||||||
|
|
||||||
|
if (!currentUser.presenter && sidebarContentPanel === PANELS.POLL) {
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||||
|
value: false,
|
||||||
|
});
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||||
|
value: PANELS.NONE,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PollCreationPanel
|
||||||
|
layoutContextDispatch={layoutContextDispatch}
|
||||||
|
hasPoll={currentMeeting.componentsFlags?.hasPoll ?? false}
|
||||||
|
hasCurrentPresentation={getHasCurrentPresentationData.pres_page_aggregate.aggregate.count > 0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PollCreationPanelContainer;
|
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Session } from 'meteor/session';
|
||||||
|
import Styled from '../styles';
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
noPresentationSelected: {
|
||||||
|
id: 'app.poll.noPresentationSelected',
|
||||||
|
description: 'no presentation label',
|
||||||
|
},
|
||||||
|
clickHereToSelect: {
|
||||||
|
id: 'app.poll.clickHereToSelect',
|
||||||
|
description: 'open uploader modal button label',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const EmptySlideArea: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
return (
|
||||||
|
<Styled.NoSlidePanelContainer>
|
||||||
|
<Styled.SectionHeading data-test="noPresentation">
|
||||||
|
{intl.formatMessage(intlMessages.noPresentationSelected)}
|
||||||
|
</Styled.SectionHeading>
|
||||||
|
<Styled.PollButton
|
||||||
|
label={intl.formatMessage(intlMessages.clickHereToSelect)}
|
||||||
|
color="primary"
|
||||||
|
onClick={() => Session.set('showUploadPresentationView', true)}
|
||||||
|
/>
|
||||||
|
</Styled.NoSlidePanelContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmptySlideArea;
|
@ -0,0 +1,255 @@
|
|||||||
|
import { useMutation, useSubscription } from '@apollo/client';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Session } from 'meteor/session';
|
||||||
|
import {
|
||||||
|
Bar, BarChart, ResponsiveContainer, XAxis, YAxis,
|
||||||
|
} from 'recharts';
|
||||||
|
import Styled from '../styles';
|
||||||
|
import {
|
||||||
|
ResponseInfo,
|
||||||
|
UserInfo,
|
||||||
|
getCurrentPollData,
|
||||||
|
getCurrentPollDataResponse,
|
||||||
|
} from '../queries';
|
||||||
|
import logger from '/imports/startup/client/logger';
|
||||||
|
import Settings from '/imports/ui/services/settings';
|
||||||
|
import { POLL_CANCEL, POLL_PUBLISH_RESULT } from '../mutation';
|
||||||
|
import { layoutDispatch } from '../../../layout/context';
|
||||||
|
import { ACTIONS, PANELS } from '../../../layout/enums';
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
usersTitle: {
|
||||||
|
id: 'app.poll.liveResult.usersTitle',
|
||||||
|
description: 'heading label for poll users',
|
||||||
|
},
|
||||||
|
responsesTitle: {
|
||||||
|
id: 'app.poll.liveResult.responsesTitle',
|
||||||
|
description: 'heading label for poll responses',
|
||||||
|
},
|
||||||
|
publishLabel: {
|
||||||
|
id: 'app.poll.publishLabel',
|
||||||
|
description: 'label for the publish button',
|
||||||
|
},
|
||||||
|
cancelPollLabel: {
|
||||||
|
id: 'app.poll.cancelPollLabel',
|
||||||
|
description: 'label for cancel poll button',
|
||||||
|
},
|
||||||
|
backLabel: {
|
||||||
|
id: 'app.poll.backLabel',
|
||||||
|
description: 'label for the return to poll options button',
|
||||||
|
},
|
||||||
|
doneLabel: {
|
||||||
|
id: 'app.createBreakoutRoom.doneLabel',
|
||||||
|
description: 'label shown when all users have responded',
|
||||||
|
},
|
||||||
|
waitingLabel: {
|
||||||
|
id: 'app.poll.waitingLabel',
|
||||||
|
description: 'label shown while waiting for responses',
|
||||||
|
},
|
||||||
|
secretPollLabel: {
|
||||||
|
id: 'app.poll.liveResult.secretLabel',
|
||||||
|
description: 'label shown instead of users in poll responses if poll is secret',
|
||||||
|
},
|
||||||
|
activePollInstruction: {
|
||||||
|
id: 'app.poll.activePollInstruction',
|
||||||
|
description: 'instructions displayed when a poll is active',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface LiveResultProps {
|
||||||
|
questionText: string;
|
||||||
|
responses: Array<ResponseInfo>;
|
||||||
|
usersCount: number;
|
||||||
|
numberOfAnswerCount: number;
|
||||||
|
animations: boolean;
|
||||||
|
pollId: string;
|
||||||
|
users: Array<UserInfo>;
|
||||||
|
isSecret: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
||||||
|
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_group_id;
|
||||||
|
|
||||||
|
const LiveResult: React.FC<LiveResultProps> = ({
|
||||||
|
questionText,
|
||||||
|
responses,
|
||||||
|
usersCount,
|
||||||
|
numberOfAnswerCount,
|
||||||
|
animations,
|
||||||
|
pollId,
|
||||||
|
users,
|
||||||
|
isSecret,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [pollPublishResult] = useMutation(POLL_PUBLISH_RESULT);
|
||||||
|
const [stopPoll] = useMutation(POLL_CANCEL);
|
||||||
|
|
||||||
|
const layoutContextDispatch = layoutDispatch();
|
||||||
|
const publishPoll = useCallback((pId: string) => {
|
||||||
|
pollPublishResult({
|
||||||
|
variables: {
|
||||||
|
pollId: pId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Styled.Instructions>
|
||||||
|
{intl.formatMessage(intlMessages.activePollInstruction)}
|
||||||
|
</Styled.Instructions>
|
||||||
|
<Styled.Stats>
|
||||||
|
{questionText ? <Styled.Title data-test="currentPollQuestion">{questionText}</Styled.Title> : null}
|
||||||
|
<Styled.Status>
|
||||||
|
{usersCount !== numberOfAnswerCount
|
||||||
|
? (
|
||||||
|
<span>
|
||||||
|
{`${intl.formatMessage(intlMessages.waitingLabel, {
|
||||||
|
0: numberOfAnswerCount,
|
||||||
|
1: usersCount,
|
||||||
|
})} `}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
: <span>{intl.formatMessage(intlMessages.doneLabel)}</span>}
|
||||||
|
{usersCount !== numberOfAnswerCount
|
||||||
|
? <Styled.ConnectingAnimation animations={animations} /> : null}
|
||||||
|
</Styled.Status>
|
||||||
|
<ResponsiveContainer width="90%" height={250}>
|
||||||
|
<BarChart
|
||||||
|
data={responses}
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<XAxis type="number" />
|
||||||
|
<YAxis width={70} type="category" dataKey="optionDesc" />
|
||||||
|
<Bar dataKey="optionResponsesCount" fill="#0C57A7" />
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Styled.Stats>
|
||||||
|
{numberOfAnswerCount >= 0
|
||||||
|
? (
|
||||||
|
<Styled.ButtonsActions>
|
||||||
|
<Styled.PublishButton
|
||||||
|
onClick={() => {
|
||||||
|
Session.set('pollInitiated', false);
|
||||||
|
publishPoll(pollId);
|
||||||
|
stopPoll();
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN,
|
||||||
|
value: true,
|
||||||
|
});
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL,
|
||||||
|
value: PANELS.CHAT,
|
||||||
|
});
|
||||||
|
layoutContextDispatch({
|
||||||
|
type: ACTIONS.SET_ID_CHAT_OPEN,
|
||||||
|
value: PUBLIC_CHAT_KEY,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
disabled={numberOfAnswerCount <= 0}
|
||||||
|
label={intl.formatMessage(intlMessages.publishLabel)}
|
||||||
|
data-test="publishPollingLabel"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
<Styled.CancelButton
|
||||||
|
onClick={() => {
|
||||||
|
Session.set('pollInitiated', false);
|
||||||
|
Session.set('resetPollPanel', true);
|
||||||
|
stopPoll();
|
||||||
|
}}
|
||||||
|
label={intl.formatMessage(intlMessages.cancelPollLabel)}
|
||||||
|
data-test="cancelPollLabel"
|
||||||
|
/>
|
||||||
|
</Styled.ButtonsActions>
|
||||||
|
) : (
|
||||||
|
<Styled.LiveResultButton
|
||||||
|
onClick={() => {
|
||||||
|
stopPoll();
|
||||||
|
}}
|
||||||
|
label={intl.formatMessage(intlMessages.backLabel)}
|
||||||
|
color="primary"
|
||||||
|
data-test="restartPoll"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Styled.Separator />
|
||||||
|
{
|
||||||
|
!isSecret
|
||||||
|
? (
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<Styled.THeading>{intl.formatMessage(intlMessages.usersTitle)}</Styled.THeading>
|
||||||
|
<Styled.THeading>{intl.formatMessage(intlMessages.responsesTitle)}</Styled.THeading>
|
||||||
|
</tr>
|
||||||
|
{
|
||||||
|
users.map((user) => (
|
||||||
|
<tr key={user.user.userId}>
|
||||||
|
<Styled.ResultLeft>{user.user.name}</Styled.ResultLeft>
|
||||||
|
<Styled.ResultRight data-test="userVoteLiveResult">{user.optionDescIds.join()}</Styled.ResultRight>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div>
|
||||||
|
{intl.formatMessage(intlMessages.secretPollLabel)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const LiveResultContainer: React.FC = () => {
|
||||||
|
const {
|
||||||
|
data: currentPollData,
|
||||||
|
loading: currentPollLoading,
|
||||||
|
error: currentPollDataError,
|
||||||
|
} = useSubscription<getCurrentPollDataResponse>(getCurrentPollData);
|
||||||
|
|
||||||
|
if (currentPollLoading || !currentPollData) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPollDataError) {
|
||||||
|
logger.error(currentPollDataError);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{JSON.stringify(currentPollDataError)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentPollData.poll.length) return null;
|
||||||
|
// @ts-ignore - JS code
|
||||||
|
const { animations } = Settings.application;
|
||||||
|
const currentPoll = currentPollData.poll[0];
|
||||||
|
const isSecret = currentPoll.secret;
|
||||||
|
const {
|
||||||
|
questionText,
|
||||||
|
responses,
|
||||||
|
pollId,
|
||||||
|
users,
|
||||||
|
} = currentPoll;
|
||||||
|
|
||||||
|
const numberOfAnswerCount = currentPoll.responses_aggregate.aggregate.sum.optionResponsesCount;
|
||||||
|
const numberOfUsersCount = currentPoll.users_aggregate.aggregate.count;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LiveResult
|
||||||
|
questionText={questionText}
|
||||||
|
responses={responses}
|
||||||
|
isSecret={isSecret}
|
||||||
|
usersCount={numberOfUsersCount}
|
||||||
|
numberOfAnswerCount={numberOfAnswerCount ?? 0}
|
||||||
|
animations={animations}
|
||||||
|
pollId={pollId}
|
||||||
|
users={users}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LiveResultContainer;
|
@ -0,0 +1,96 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { pollTypes } from '../service';
|
||||||
|
import Styled from '../styles';
|
||||||
|
|
||||||
|
const POLL_SETTINGS = Meteor.settings.public.poll;
|
||||||
|
const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom;
|
||||||
|
const MAX_INPUT_CHARS = POLL_SETTINGS.maxTypedAnswerLength;
|
||||||
|
const MIN_OPTIONS_LENGTH = 2;
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
customPlaceholder: {
|
||||||
|
id: 'app.poll.customPlaceholder',
|
||||||
|
description: 'custom poll input field placeholder text',
|
||||||
|
},
|
||||||
|
delete: {
|
||||||
|
id: 'app.poll.optionDelete.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
deleteRespDesc: {
|
||||||
|
id: 'app.poll.deleteRespDesc',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
emptyPollOpt: {
|
||||||
|
id: 'app.poll.emptyPollOpt',
|
||||||
|
description: 'screen reader for blank poll option',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface PollInputsProps {
|
||||||
|
optList: Array<{ val: string }>;
|
||||||
|
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>, i: number) => void;
|
||||||
|
handleRemoveOption: (i: number) => void;
|
||||||
|
type: string | null;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PollInputs: React.FC<PollInputsProps> = ({
|
||||||
|
optList,
|
||||||
|
handleInputChange,
|
||||||
|
handleRemoveOption,
|
||||||
|
type,
|
||||||
|
error,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
let hasVal = false;
|
||||||
|
return optList.slice(0, MAX_CUSTOM_FIELDS).map((o: { val: string }, i: number) => {
|
||||||
|
const pollOptionKey = `poll-option-${i}`;
|
||||||
|
if (o.val && o.val.length > 0) hasVal = true;
|
||||||
|
return (
|
||||||
|
<span key={pollOptionKey}>
|
||||||
|
<Styled.OptionWrapper>
|
||||||
|
<Styled.PollOptionInput
|
||||||
|
type="text"
|
||||||
|
value={o.val}
|
||||||
|
placeholder={intl.formatMessage(intlMessages.customPlaceholder)}
|
||||||
|
data-test="pollOptionItem"
|
||||||
|
onChange={(e) => handleInputChange(e, i)}
|
||||||
|
maxLength={MAX_INPUT_CHARS}
|
||||||
|
onPaste={(e) => { e.stopPropagation(); }}
|
||||||
|
onCut={(e) => { e.stopPropagation(); }}
|
||||||
|
onCopy={(e) => { e.stopPropagation(); }}
|
||||||
|
/>
|
||||||
|
{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={() => {
|
||||||
|
handleRemoveOption(i);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span className="sr-only" id={`option-${i}`}>
|
||||||
|
{intl.formatMessage(
|
||||||
|
intlMessages.deleteRespDesc,
|
||||||
|
{ 0: o.val || intl.formatMessage(intlMessages.emptyPollOpt) },
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</Styled.OptionWrapper>
|
||||||
|
{!hasVal && type !== pollTypes.Response && error ? (
|
||||||
|
<Styled.InputError data-test="errorNoValueInput">{error}</Styled.InputError>
|
||||||
|
) : (
|
||||||
|
<Styled.ErrorSpacer> </Styled.ErrorSpacer>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PollInputs;
|
@ -0,0 +1,104 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import DraggableTextArea from '/imports/ui/components/poll/dragAndDrop/component';
|
||||||
|
import { pollTypes } from '../service';
|
||||||
|
import Styled from '../styles';
|
||||||
|
|
||||||
|
const POLL_SETTINGS = Meteor.settings.public.poll;
|
||||||
|
const MAX_INPUT_CHARS = POLL_SETTINGS.maxTypedAnswerLength;
|
||||||
|
|
||||||
|
const QUESTION_MAX_INPUT_CHARS = 1200;
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
questionAndOptionsPlaceholder: {
|
||||||
|
id: 'app.poll.questionAndoptions.label',
|
||||||
|
description: 'poll input questions and options label',
|
||||||
|
},
|
||||||
|
questionLabel: {
|
||||||
|
id: 'app.poll.question.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
optionalQuestionLabel: {
|
||||||
|
id: 'app.poll.optionalQuestion.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface PollQuestionAreaProps {
|
||||||
|
customInput: boolean;
|
||||||
|
optList: Array<{ val: string }>;
|
||||||
|
warning: string | null;
|
||||||
|
type: string | null;
|
||||||
|
error: string | null;
|
||||||
|
questionAndOptions: string | string[];
|
||||||
|
handleTextareaChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||||
|
setIsPasting: (isPasting: boolean) => void;
|
||||||
|
handlePollLetterOptions: () => void;
|
||||||
|
textareaRef: React.RefObject<HTMLTextAreaElement>;
|
||||||
|
question: string | string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const PollQuestionArea: React.FC<PollQuestionAreaProps> = ({
|
||||||
|
customInput,
|
||||||
|
optList,
|
||||||
|
warning,
|
||||||
|
error,
|
||||||
|
type,
|
||||||
|
questionAndOptions,
|
||||||
|
handleTextareaChange,
|
||||||
|
setIsPasting,
|
||||||
|
handlePollLetterOptions,
|
||||||
|
textareaRef,
|
||||||
|
question,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const hasOptionError = (customInput && optList.length === 0 && error);
|
||||||
|
const hasWarning = (customInput && warning);
|
||||||
|
const hasQuestionError = (type === pollTypes.Response
|
||||||
|
&& questionAndOptions.length === 0 && error);
|
||||||
|
const questionsAndOptionsPlaceholder = intlMessages.questionAndOptionsPlaceholder;
|
||||||
|
const questionPlaceholder = (type === pollTypes.Response)
|
||||||
|
? intlMessages.questionLabel
|
||||||
|
: intlMessages.optionalQuestionLabel;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Styled.PollQuestionArea
|
||||||
|
hasError={hasQuestionError || hasOptionError}
|
||||||
|
data-test="pollQuestionArea"
|
||||||
|
value={customInput ? questionAndOptions : question}
|
||||||
|
onChange={(e) => handleTextareaChange(e)}
|
||||||
|
onPaste={(e) => { e.stopPropagation(); setIsPasting(true); }}
|
||||||
|
onCut={(e) => { e.stopPropagation(); }}
|
||||||
|
onCopy={(e) => { e.stopPropagation(); }}
|
||||||
|
onKeyPress={(event) => {
|
||||||
|
if (event.key === 'Enter' && customInput) {
|
||||||
|
handlePollLetterOptions();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
rows="5"
|
||||||
|
cols="35"
|
||||||
|
maxLength={QUESTION_MAX_INPUT_CHARS}
|
||||||
|
aria-label={intl.formatMessage(customInput ? questionsAndOptionsPlaceholder
|
||||||
|
: questionPlaceholder)}
|
||||||
|
placeholder={intl.formatMessage(customInput ? questionsAndOptionsPlaceholder
|
||||||
|
: questionPlaceholder)}
|
||||||
|
{...{ MAX_INPUT_CHARS }}
|
||||||
|
as={customInput ? DraggableTextArea : 'textarea'}
|
||||||
|
ref={textareaRef}
|
||||||
|
/>
|
||||||
|
{hasQuestionError || hasOptionError ? (
|
||||||
|
<Styled.InputError>{error}</Styled.InputError>
|
||||||
|
) : (
|
||||||
|
<Styled.ErrorSpacer> </Styled.ErrorSpacer>
|
||||||
|
)}
|
||||||
|
{hasWarning ? (
|
||||||
|
<Styled.Warning>{warning}</Styled.Warning>
|
||||||
|
) : (
|
||||||
|
<Styled.ErrorSpacer> </Styled.ErrorSpacer>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PollQuestionArea;
|
@ -0,0 +1,162 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import Checkbox from '/imports/ui/components/common/checkbox/component';
|
||||||
|
import Toggle from '/imports/ui/components/common/switch/component';
|
||||||
|
import Styled from '../styles';
|
||||||
|
import { pollTypes, isDefaultPoll } from '../service';
|
||||||
|
import StartPollButton from './StartPollButton';
|
||||||
|
import PollInputs from './PollInputs';
|
||||||
|
|
||||||
|
const POLL_SETTINGS = Meteor.settings.public.poll;
|
||||||
|
const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom;
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
enableMultipleResponseLabel: {
|
||||||
|
id: 'app.poll.enableMultipleResponseLabel',
|
||||||
|
description: 'label for checkbox to enable multiple choice',
|
||||||
|
},
|
||||||
|
addOptionLabel: {
|
||||||
|
id: 'app.poll.addItem.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
secretPollLabel: {
|
||||||
|
id: 'app.poll.secretPoll.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
isSecretPollLabel: {
|
||||||
|
id: 'app.poll.secretPoll.isSecretLabel',
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ResponseAreaProps {
|
||||||
|
type: string | null;
|
||||||
|
toggleIsMultipleResponse: () => void;
|
||||||
|
isMultipleResponse: boolean;
|
||||||
|
optList: Array<{ val: string }>;
|
||||||
|
handleAddOption: () => void;
|
||||||
|
secretPoll: boolean;
|
||||||
|
question: string | string[];
|
||||||
|
setError: (err: string) => void;
|
||||||
|
setIsPolling: (isPolling: boolean) => void;
|
||||||
|
hasCurrentPresentation: boolean;
|
||||||
|
handleToggle: () => void;
|
||||||
|
error: string | null;
|
||||||
|
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>, i: number) => void;
|
||||||
|
handleRemoveOption: (i: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResponseArea: React.FC<ResponseAreaProps> = ({
|
||||||
|
type,
|
||||||
|
toggleIsMultipleResponse,
|
||||||
|
isMultipleResponse,
|
||||||
|
optList,
|
||||||
|
handleAddOption,
|
||||||
|
secretPoll,
|
||||||
|
question,
|
||||||
|
setError,
|
||||||
|
setIsPolling,
|
||||||
|
hasCurrentPresentation,
|
||||||
|
handleToggle,
|
||||||
|
error,
|
||||||
|
handleInputChange,
|
||||||
|
handleRemoveOption,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const defaultPoll = isDefaultPoll(type as string);
|
||||||
|
if (defaultPoll || type === pollTypes.Response) {
|
||||||
|
return (
|
||||||
|
<Styled.ResponseArea>
|
||||||
|
{defaultPoll && (
|
||||||
|
<div>
|
||||||
|
<Styled.PollCheckbox data-test="allowMultiple">
|
||||||
|
<Checkbox
|
||||||
|
onChange={toggleIsMultipleResponse}
|
||||||
|
checked={isMultipleResponse}
|
||||||
|
ariaLabelledBy="multipleResponseCheckboxLabel"
|
||||||
|
label={intl.formatMessage(intlMessages.enableMultipleResponseLabel)}
|
||||||
|
/>
|
||||||
|
</Styled.PollCheckbox>
|
||||||
|
<div id="multipleResponseCheckboxLabel" hidden>
|
||||||
|
{intl.formatMessage(intlMessages.enableMultipleResponseLabel)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{defaultPoll && (
|
||||||
|
<PollInputs
|
||||||
|
error={error}
|
||||||
|
optList={optList}
|
||||||
|
handleInputChange={handleInputChange}
|
||||||
|
handleRemoveOption={handleRemoveOption}
|
||||||
|
type={type}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{defaultPoll && (
|
||||||
|
<Styled.AddItemButton
|
||||||
|
data-test="addPollItem"
|
||||||
|
label={intl.formatMessage(intlMessages.addOptionLabel)}
|
||||||
|
aria-describedby="add-item-button"
|
||||||
|
color="default"
|
||||||
|
icon="add"
|
||||||
|
disabled={optList.length >= MAX_CUSTOM_FIELDS}
|
||||||
|
onClick={() => handleAddOption()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Styled.AnonymousRow>
|
||||||
|
<Styled.AnonymousHeadingCol aria-hidden="true">
|
||||||
|
<Styled.AnonymousHeading>
|
||||||
|
{intl.formatMessage(intlMessages.secretPollLabel)}
|
||||||
|
</Styled.AnonymousHeading>
|
||||||
|
</Styled.AnonymousHeadingCol>
|
||||||
|
<Styled.AnonymousToggleCol>
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
||||||
|
<Styled.Toggle>
|
||||||
|
<Styled.ToggleLabel>
|
||||||
|
{secretPoll
|
||||||
|
? intl.formatMessage(intlMessages.on)
|
||||||
|
: intl.formatMessage(intlMessages.off)}
|
||||||
|
</Styled.ToggleLabel>
|
||||||
|
<Toggle
|
||||||
|
// @ts-ignore - component Wrapped by intl, not reflecting the correct props
|
||||||
|
icons={false}
|
||||||
|
defaultChecked={secretPoll}
|
||||||
|
onChange={() => handleToggle()}
|
||||||
|
ariaLabel={intl.formatMessage(intlMessages.secretPollLabel)}
|
||||||
|
showToggleLabel={false}
|
||||||
|
data-test="anonymousPollBtn"
|
||||||
|
/>
|
||||||
|
</Styled.Toggle>
|
||||||
|
</Styled.AnonymousToggleCol>
|
||||||
|
</Styled.AnonymousRow>
|
||||||
|
{secretPoll && (
|
||||||
|
<Styled.PollParagraph>
|
||||||
|
{intl.formatMessage(intlMessages.isSecretPollLabel)}
|
||||||
|
</Styled.PollParagraph>
|
||||||
|
)}
|
||||||
|
<StartPollButton
|
||||||
|
hasCurrentPresentation={hasCurrentPresentation}
|
||||||
|
question={question}
|
||||||
|
isMultipleResponse={isMultipleResponse}
|
||||||
|
optList={optList}
|
||||||
|
type={type}
|
||||||
|
secretPoll={secretPoll}
|
||||||
|
setError={setError}
|
||||||
|
setIsPolling={setIsPolling}
|
||||||
|
key="startPollButton"
|
||||||
|
/>
|
||||||
|
</Styled.ResponseArea>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResponseArea;
|
@ -0,0 +1,103 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import Styled from '../styles';
|
||||||
|
import { pollTypes } from '../service';
|
||||||
|
import ResponseArea from './ResponseArea';
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
typedResponseDesc: {
|
||||||
|
id: 'app.poll.typedResponse.desc',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
responseChoices: {
|
||||||
|
id: 'app.poll.responseChoices.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
pollingQuestion: {
|
||||||
|
id: 'app.polling.pollQuestionTitle',
|
||||||
|
description: 'polling question header',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ResponseChoicesProps {
|
||||||
|
type: string | null;
|
||||||
|
toggleIsMultipleResponse: () => void;
|
||||||
|
isMultipleResponse: boolean;
|
||||||
|
optList: Array<{ val: string }>;
|
||||||
|
handleAddOption: () => void;
|
||||||
|
secretPoll: boolean;
|
||||||
|
question: string | string[];
|
||||||
|
setError: (err: string) => void;
|
||||||
|
setIsPolling: (isPolling: boolean) => void;
|
||||||
|
hasCurrentPresentation: boolean;
|
||||||
|
handleToggle: () => void;
|
||||||
|
error: string | null;
|
||||||
|
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>, i: number) => void;
|
||||||
|
handleRemoveOption: (i: number) => void;
|
||||||
|
customInput: boolean;
|
||||||
|
questionAndOptions: string[] | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResponseChoices: React.FC<ResponseChoicesProps> = ({
|
||||||
|
type,
|
||||||
|
toggleIsMultipleResponse,
|
||||||
|
isMultipleResponse,
|
||||||
|
optList,
|
||||||
|
handleAddOption,
|
||||||
|
secretPoll,
|
||||||
|
question,
|
||||||
|
setError,
|
||||||
|
setIsPolling,
|
||||||
|
hasCurrentPresentation,
|
||||||
|
handleToggle,
|
||||||
|
error,
|
||||||
|
handleInputChange,
|
||||||
|
handleRemoveOption,
|
||||||
|
customInput,
|
||||||
|
questionAndOptions,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
if ((!customInput && type) || (questionAndOptions && customInput)) {
|
||||||
|
return (
|
||||||
|
<div data-test="responseChoices">
|
||||||
|
{customInput && questionAndOptions && (
|
||||||
|
<Styled.Question>
|
||||||
|
<Styled.SectionHeading>
|
||||||
|
{intl.formatMessage(intlMessages.pollingQuestion)}
|
||||||
|
</Styled.SectionHeading>
|
||||||
|
<Styled.PollParagraph>
|
||||||
|
<span>{question}</span>
|
||||||
|
</Styled.PollParagraph>
|
||||||
|
</Styled.Question>
|
||||||
|
)}
|
||||||
|
<Styled.SectionHeading>
|
||||||
|
{intl.formatMessage(intlMessages.responseChoices)}
|
||||||
|
</Styled.SectionHeading>
|
||||||
|
{type === pollTypes.Response && (
|
||||||
|
<Styled.PollParagraph>
|
||||||
|
<span>{intl.formatMessage(intlMessages.typedResponseDesc)}</span>
|
||||||
|
</Styled.PollParagraph>
|
||||||
|
)}
|
||||||
|
<ResponseArea
|
||||||
|
error={error}
|
||||||
|
type={type}
|
||||||
|
toggleIsMultipleResponse={toggleIsMultipleResponse}
|
||||||
|
isMultipleResponse={isMultipleResponse}
|
||||||
|
optList={optList}
|
||||||
|
handleAddOption={handleAddOption}
|
||||||
|
secretPoll={secretPoll}
|
||||||
|
question={question}
|
||||||
|
setError={setError}
|
||||||
|
setIsPolling={setIsPolling}
|
||||||
|
hasCurrentPresentation={hasCurrentPresentation}
|
||||||
|
handleToggle={handleToggle}
|
||||||
|
handleInputChange={handleInputChange}
|
||||||
|
handleRemoveOption={handleRemoveOption}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResponseChoices;
|
@ -0,0 +1,158 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { pollTypes } from '../service';
|
||||||
|
import Styled from '../styles';
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
responseTypesLabel: {
|
||||||
|
id: 'app.poll.responseTypes.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
tf: {
|
||||||
|
id: 'app.poll.tf',
|
||||||
|
description: 'label for true / false poll',
|
||||||
|
},
|
||||||
|
true: {
|
||||||
|
id: 'app.poll.answer.true',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
false: {
|
||||||
|
id: 'app.poll.answer.false',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
a4: {
|
||||||
|
id: 'app.poll.a4',
|
||||||
|
description: 'label for A / B / C / D poll',
|
||||||
|
},
|
||||||
|
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: '',
|
||||||
|
},
|
||||||
|
userResponse: {
|
||||||
|
id: 'app.poll.userResponse.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ResponseTypesProps {
|
||||||
|
customInput: boolean;
|
||||||
|
setType: (type: string | null) => void;
|
||||||
|
type: string | null;
|
||||||
|
setOptList: (optList: Array<{ val: string }>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResponseTypes: React.FC<ResponseTypesProps> = ({
|
||||||
|
customInput,
|
||||||
|
setType,
|
||||||
|
type,
|
||||||
|
setOptList,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
if (!customInput) {
|
||||||
|
return (
|
||||||
|
<div data-test="responseTypes">
|
||||||
|
<Styled.SectionHeading>
|
||||||
|
{intl.formatMessage(intlMessages.responseTypesLabel)}
|
||||||
|
</Styled.SectionHeading>
|
||||||
|
<Styled.ResponseType>
|
||||||
|
<Styled.PollConfigButton
|
||||||
|
selected={type === pollTypes.TrueFalse}
|
||||||
|
small={false}
|
||||||
|
label={intl.formatMessage(intlMessages.tf)}
|
||||||
|
aria-describedby="poll-config-button"
|
||||||
|
data-test="pollTrueFalse"
|
||||||
|
color="default"
|
||||||
|
onClick={() => {
|
||||||
|
setType(pollTypes.TrueFalse);
|
||||||
|
setOptList([
|
||||||
|
{ val: intl.formatMessage(intlMessages.true) },
|
||||||
|
{ val: intl.formatMessage(intlMessages.false) },
|
||||||
|
]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Styled.PollConfigButton
|
||||||
|
selected={type === pollTypes.Letter}
|
||||||
|
small={false}
|
||||||
|
label={intl.formatMessage(intlMessages.a4)}
|
||||||
|
aria-describedby="poll-config-button"
|
||||||
|
data-test="pollLetterAlternatives"
|
||||||
|
color="default"
|
||||||
|
onClick={() => {
|
||||||
|
if (!customInput) {
|
||||||
|
setType(pollTypes.Letter);
|
||||||
|
setOptList([
|
||||||
|
{ 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={() => {
|
||||||
|
setType(pollTypes.YesNoAbstention);
|
||||||
|
setOptList([
|
||||||
|
{ 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={() => { setType(pollTypes.Response); }}
|
||||||
|
/>
|
||||||
|
</Styled.ResponseType>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResponseTypes;
|
@ -0,0 +1,150 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
import { useMutation } from '@apollo/client';
|
||||||
|
import Styled from '../styles';
|
||||||
|
import { pollTypes, checkPollType } from '../service';
|
||||||
|
import { POLL_CREATE } from '../mutation';
|
||||||
|
|
||||||
|
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||||
|
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
|
||||||
|
|
||||||
|
const POLL_SETTINGS = Meteor.settings.public.poll;
|
||||||
|
const MAX_CUSTOM_FIELDS = POLL_SETTINGS.maxCustom;
|
||||||
|
|
||||||
|
const intlMessages = defineMessages({
|
||||||
|
startPollLabel: {
|
||||||
|
id: 'app.poll.start.label',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
questionErr: {
|
||||||
|
id: 'app.poll.questionErr',
|
||||||
|
description: 'question text area error label',
|
||||||
|
},
|
||||||
|
optionErr: {
|
||||||
|
id: 'app.poll.optionErr',
|
||||||
|
description: 'poll input error label',
|
||||||
|
},
|
||||||
|
yes: {
|
||||||
|
id: 'app.poll.y',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
no: {
|
||||||
|
id: 'app.poll.n',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
abstention: {
|
||||||
|
id: 'app.poll.abstention',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
true: {
|
||||||
|
id: 'app.poll.answer.true',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
false: {
|
||||||
|
id: 'app.poll.answer.false',
|
||||||
|
description: '',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface StartPollButtonProps {
|
||||||
|
optList: Array<{val: string}>;
|
||||||
|
question: string | string[];
|
||||||
|
type: string | null;
|
||||||
|
setError: (err: string) => void;
|
||||||
|
setIsPolling: (isPolling: boolean) => void;
|
||||||
|
secretPoll: boolean;
|
||||||
|
isMultipleResponse: boolean;
|
||||||
|
hasCurrentPresentation: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StartPollButton: React.FC<StartPollButtonProps> = ({
|
||||||
|
optList,
|
||||||
|
question,
|
||||||
|
type,
|
||||||
|
setError,
|
||||||
|
setIsPolling,
|
||||||
|
secretPoll,
|
||||||
|
isMultipleResponse,
|
||||||
|
hasCurrentPresentation,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [createPoll] = useMutation(POLL_CREATE);
|
||||||
|
|
||||||
|
const startPoll = (
|
||||||
|
pollType: string | null,
|
||||||
|
secretPoll: boolean,
|
||||||
|
question: string | string[],
|
||||||
|
isMultipleResponse: boolean,
|
||||||
|
answers: (string | null)[] = [],
|
||||||
|
) => {
|
||||||
|
const pollId = hasCurrentPresentation || PUBLIC_CHAT_KEY;
|
||||||
|
|
||||||
|
createPoll({
|
||||||
|
variables: {
|
||||||
|
pollType,
|
||||||
|
pollId: `${pollId}/${new Date().getTime()}`,
|
||||||
|
secretPoll,
|
||||||
|
question,
|
||||||
|
isMultipleResponse,
|
||||||
|
answers,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.StartPollBtn
|
||||||
|
data-test="startPoll"
|
||||||
|
label={intl.formatMessage(intlMessages.startPollLabel)}
|
||||||
|
color="primary"
|
||||||
|
onClick={() => {
|
||||||
|
const optionsList = optList.slice(0, MAX_CUSTOM_FIELDS);
|
||||||
|
let hasVal = false;
|
||||||
|
optionsList.forEach((o) => {
|
||||||
|
if (o.val.trim().length > 0) hasVal = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
let err = null;
|
||||||
|
if (type === pollTypes.Response && question.length === 0) {
|
||||||
|
err = intl.formatMessage(intlMessages.questionErr);
|
||||||
|
}
|
||||||
|
if (!hasVal && type !== pollTypes.Response) {
|
||||||
|
err = intl.formatMessage(intlMessages.optionErr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
setError(err);
|
||||||
|
} else {
|
||||||
|
setIsPolling(true);
|
||||||
|
const verifiedPollType = checkPollType(
|
||||||
|
type,
|
||||||
|
optionsList,
|
||||||
|
intl.formatMessage(intlMessages.yes),
|
||||||
|
intl.formatMessage(intlMessages.no),
|
||||||
|
intl.formatMessage(intlMessages.abstention),
|
||||||
|
intl.formatMessage(intlMessages.true),
|
||||||
|
intl.formatMessage(intlMessages.false),
|
||||||
|
);
|
||||||
|
const verifiedOptions = optionsList.map((o) => {
|
||||||
|
if (o.val.trim().length > 0) return o.val;
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
if (verifiedPollType === pollTypes.Custom) {
|
||||||
|
startPoll(
|
||||||
|
verifiedPollType,
|
||||||
|
secretPoll,
|
||||||
|
question,
|
||||||
|
isMultipleResponse,
|
||||||
|
verifiedOptions?.filter(Boolean),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
startPoll(verifiedPollType, secretPoll, question, isMultipleResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StartPollButton;
|
@ -0,0 +1,61 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const POLL_PUBLISH_RESULT = gql`
|
||||||
|
mutation PollPublishResult($pollId: String!) {
|
||||||
|
pollPublishResult(
|
||||||
|
pollId: $pollId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const POLL_SUBMIT_TYPED_VOTE = gql`
|
||||||
|
mutation PollSubmitTypedVote($pollId: String!, $answer: String!) {
|
||||||
|
pollSubmitUserTypedVote(
|
||||||
|
pollId: $pollId,
|
||||||
|
answer: $answer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const POLL_SUBMIT_VOTE = gql`
|
||||||
|
mutation PollSubmitVote($pollId: String!, $answerIds: [Int]!) {
|
||||||
|
pollSubmitUserVote(
|
||||||
|
pollId: $pollId,
|
||||||
|
answerIds: $answerIds,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const POLL_CANCEL = gql`
|
||||||
|
mutation PollCancel {
|
||||||
|
pollCancel
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const POLL_CREATE = gql`
|
||||||
|
mutation PollCreate(
|
||||||
|
$pollType: String!,
|
||||||
|
$pollId: String!,
|
||||||
|
$secretPoll: Boolean!,
|
||||||
|
$question: String!,
|
||||||
|
$isMultipleResponse: Boolean!,
|
||||||
|
$answers: [String]!
|
||||||
|
) {
|
||||||
|
pollCreate(
|
||||||
|
pollType: $pollType,
|
||||||
|
pollId: $pollId,
|
||||||
|
secretPoll: $secretPoll,
|
||||||
|
question: $question,
|
||||||
|
isMultipleResponse: $isMultipleResponse,
|
||||||
|
answers: $answers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
POLL_PUBLISH_RESULT,
|
||||||
|
POLL_SUBMIT_TYPED_VOTE,
|
||||||
|
POLL_SUBMIT_VOTE,
|
||||||
|
POLL_CANCEL,
|
||||||
|
POLL_CREATE,
|
||||||
|
};
|
@ -0,0 +1,103 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export interface GetHasCurrentPresentationResponse {
|
||||||
|
pres_page_aggregate: {
|
||||||
|
aggregate: {
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserInfo {
|
||||||
|
user: {
|
||||||
|
name: string;
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
|
optionDescIds: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponseInfo {
|
||||||
|
optionResponsesCount: number;
|
||||||
|
optionDesc: string;
|
||||||
|
pollResponsesCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PollInfo {
|
||||||
|
published: boolean;
|
||||||
|
pollId: string;
|
||||||
|
secret: boolean;
|
||||||
|
questionText: string;
|
||||||
|
ended: boolean;
|
||||||
|
multipleResponses: boolean;
|
||||||
|
users: Array<UserInfo>;
|
||||||
|
responses: Array<ResponseInfo>;
|
||||||
|
users_aggregate: {
|
||||||
|
aggregate: {
|
||||||
|
count: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses_aggregate: {
|
||||||
|
aggregate: {
|
||||||
|
count: number;
|
||||||
|
sum: {
|
||||||
|
optionResponsesCount: number;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface getCurrentPollDataResponse {
|
||||||
|
poll: Array<PollInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getHasCurrentPresentation = gql`
|
||||||
|
subscription getHasCurrentPresentation {
|
||||||
|
pres_page_aggregate(where: {isCurrentPage: {_eq: true}}) {
|
||||||
|
aggregate {
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const getCurrentPollData = gql`
|
||||||
|
subscription getCurrentPollData {
|
||||||
|
poll(order_by: {createdAt: desc}, limit: 1,) {
|
||||||
|
pollId
|
||||||
|
published
|
||||||
|
secret
|
||||||
|
questionText
|
||||||
|
ended
|
||||||
|
multipleResponses
|
||||||
|
users(where: {responded: {_eq: true}}) {
|
||||||
|
user {
|
||||||
|
name
|
||||||
|
userId
|
||||||
|
}
|
||||||
|
optionDescIds
|
||||||
|
}
|
||||||
|
responses {
|
||||||
|
optionResponsesCount
|
||||||
|
optionDesc
|
||||||
|
pollResponsesCount
|
||||||
|
}
|
||||||
|
users_aggregate {
|
||||||
|
aggregate {
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses_aggregate {
|
||||||
|
aggregate {
|
||||||
|
count
|
||||||
|
sum {
|
||||||
|
optionResponsesCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getHasCurrentPresentation,
|
||||||
|
};
|
@ -0,0 +1,118 @@
|
|||||||
|
export const pollTypes = {
|
||||||
|
YesNo: 'YN',
|
||||||
|
YesNoAbstention: 'YNA',
|
||||||
|
TrueFalse: 'TF',
|
||||||
|
Letter: 'A-',
|
||||||
|
A2: 'A-2',
|
||||||
|
A3: 'A-3',
|
||||||
|
A4: 'A-4',
|
||||||
|
A5: 'A-5',
|
||||||
|
Custom: 'CUSTOM',
|
||||||
|
Response: 'R-',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateInput = (input: string) => {
|
||||||
|
let i = input;
|
||||||
|
while (/^\s/.test(i)) i = i.substring(1);
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSplittedQuestionAndOptions = (questionAndOptions: string[] | string) => {
|
||||||
|
const inputList = Array.isArray(questionAndOptions)
|
||||||
|
? questionAndOptions
|
||||||
|
: questionAndOptions.split('\n').filter((val: string) => val !== '');
|
||||||
|
const splittedQuestion = inputList.length > 0 ? inputList[0] : questionAndOptions;
|
||||||
|
const optList = inputList.slice(1);
|
||||||
|
|
||||||
|
const optionsList = optList.map((val) => {
|
||||||
|
const option = validateInput(val);
|
||||||
|
return { val: option };
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
splittedQuestion,
|
||||||
|
optionsList,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeEmptyLineSpaces = (input: string) => {
|
||||||
|
const filteredInput = input.split('\n').filter((val) => val.trim() !== '');
|
||||||
|
return filteredInput;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isDefaultPoll = (pollType: string) => pollType !== pollTypes.Response;
|
||||||
|
|
||||||
|
const matchYesNoPoll = (yesValue: string, noValue: string, contentString: string) => {
|
||||||
|
const ynPollString = `(${yesValue}\\s*\\/\\s*${noValue})|(${noValue}\\s*\\/\\s*${yesValue})`;
|
||||||
|
const ynOptionsRegex = new RegExp(ynPollString, 'gi');
|
||||||
|
const ynPoll = contentString.replace(/\n/g, '').match(ynOptionsRegex) || [];
|
||||||
|
return ynPoll;
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchYesNoAbstentionPoll = (yesValue:string, noValue:string, abstentionValue:string, contentString:string) => {
|
||||||
|
/* eslint max-len: [off] */
|
||||||
|
const ynaPollString = `(${yesValue}\\s*\\/\\s*${noValue}\\s*\\/\\s*${abstentionValue})|(${yesValue}\\s*\\/\\s*${abstentionValue}\\s*\\/\\s*${noValue})|(${abstentionValue}\\s*\\/\\s*${yesValue}\\s*\\/\\s*${noValue})|(${abstentionValue}\\s*\\/\\s*${noValue}\\s*\\/\\s*${yesValue})|(${noValue}\\s*\\/\\s*${yesValue}\\s*\\/\\s*${abstentionValue})|(${noValue}\\s*\\/\\s*${abstentionValue}\\s*\\/\\s*${yesValue})`;
|
||||||
|
const ynaOptionsRegex = new RegExp(ynaPollString, 'gi');
|
||||||
|
const ynaPoll = contentString.replace(/\n/g, '').match(ynaOptionsRegex) || [];
|
||||||
|
return ynaPoll;
|
||||||
|
};
|
||||||
|
|
||||||
|
const matchTrueFalsePoll = (trueValue:string, falseValue:string, contentString:string) => {
|
||||||
|
const tfPollString = `(${trueValue}\\s*\\/\\s*${falseValue})|(${falseValue}\\s*\\/\\s*${trueValue})`;
|
||||||
|
const tgOptionsRegex = new RegExp(tfPollString, 'gi');
|
||||||
|
const tfPoll = contentString.match(tgOptionsRegex) || [];
|
||||||
|
return tfPoll;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const checkPollType = (
|
||||||
|
type: string | null,
|
||||||
|
optList: { val: string }[],
|
||||||
|
yesValue: string,
|
||||||
|
noValue: string,
|
||||||
|
abstentionValue: string,
|
||||||
|
trueValue: string,
|
||||||
|
falseValue: string,
|
||||||
|
) => {
|
||||||
|
/* eslint no-underscore-dangle: "off" */
|
||||||
|
let _type = type;
|
||||||
|
let pollString = '';
|
||||||
|
let defaultMatch: RegExpMatchArray | [] | null = null;
|
||||||
|
let isDefault = null;
|
||||||
|
|
||||||
|
switch (_type) {
|
||||||
|
case pollTypes.Letter:
|
||||||
|
pollString = optList.map((x) => x.val.toUpperCase()).sort().join('');
|
||||||
|
defaultMatch = pollString.match(/^(ABCDEF)|(ABCDE)|(ABCD)|(ABC)|(AB)$/gi);
|
||||||
|
isDefault = defaultMatch && pollString.length === defaultMatch[0].length;
|
||||||
|
_type = isDefault && Array.isArray(defaultMatch) ? `${_type}${defaultMatch[0].length}` : pollTypes.Custom;
|
||||||
|
break;
|
||||||
|
case pollTypes.TrueFalse:
|
||||||
|
pollString = optList.map((x) => x.val).join('/');
|
||||||
|
defaultMatch = matchTrueFalsePoll(trueValue, falseValue, pollString);
|
||||||
|
isDefault = defaultMatch.length > 0 && pollString.length === (defaultMatch[0]?.length);
|
||||||
|
if (!isDefault) _type = pollTypes.Custom;
|
||||||
|
break;
|
||||||
|
case pollTypes.YesNoAbstention:
|
||||||
|
pollString = optList.map((x) => x.val).join('/');
|
||||||
|
defaultMatch = matchYesNoAbstentionPoll(yesValue, noValue, abstentionValue, pollString);
|
||||||
|
isDefault = Array.isArray(defaultMatch) && defaultMatch.length > 0 && pollString.length === defaultMatch[0]?.length;
|
||||||
|
if (!isDefault) {
|
||||||
|
// also try to match only yes/no
|
||||||
|
defaultMatch = matchYesNoPoll(yesValue, noValue, pollString);
|
||||||
|
isDefault = defaultMatch.length > 0 && pollString.length === defaultMatch[0]?.length;
|
||||||
|
_type = isDefault ? pollTypes.YesNo : _type = pollTypes.Custom;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return _type;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
pollTypes,
|
||||||
|
validateInput,
|
||||||
|
getSplittedQuestionAndOptions,
|
||||||
|
removeEmptyLineSpaces,
|
||||||
|
isDefaultPoll,
|
||||||
|
};
|
@ -0,0 +1,634 @@
|
|||||||
|
import styled, { css, keyframes } from 'styled-components';
|
||||||
|
import Button from '/imports/ui/components/common/button/component';
|
||||||
|
import {
|
||||||
|
jumboPaddingY,
|
||||||
|
smPaddingX,
|
||||||
|
smPaddingY,
|
||||||
|
lgPaddingX,
|
||||||
|
borderRadius,
|
||||||
|
borderSize,
|
||||||
|
pollInputHeight,
|
||||||
|
pollSmMargin,
|
||||||
|
pollMdMargin,
|
||||||
|
mdPaddingX,
|
||||||
|
pollStatsElementWidth,
|
||||||
|
pollResultWidth,
|
||||||
|
borderSizeLarge,
|
||||||
|
} from '/imports/ui/stylesheets/styled-components/general';
|
||||||
|
import {
|
||||||
|
colorText,
|
||||||
|
colorBlueLight,
|
||||||
|
colorGray,
|
||||||
|
colorGrayLight,
|
||||||
|
colorGrayLighter,
|
||||||
|
colorGrayLightest,
|
||||||
|
colorDanger,
|
||||||
|
colorWarning,
|
||||||
|
colorHeading,
|
||||||
|
colorPrimary,
|
||||||
|
colorGrayDark,
|
||||||
|
colorWhite,
|
||||||
|
pollBlue,
|
||||||
|
pollStatsBorderColor,
|
||||||
|
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||||
|
import { fontSizeBase, fontSizeSmall } from '/imports/ui/stylesheets/styled-components/typography';
|
||||||
|
|
||||||
|
const ToggleLabel = styled.span`
|
||||||
|
margin-right: ${smPaddingX};
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
margin: 0 0 0 ${smPaddingX};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PollOptionInput = styled.input`
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-radius: ${borderSize};
|
||||||
|
box-shadow: 0 0 0 ${borderSize} ${colorBlueLight}, inset 0 0 0 1px ${colorPrimary};
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
color: ${colorText};
|
||||||
|
-webkit-appearance: none;
|
||||||
|
padding: calc(${smPaddingY} * 2) ${smPaddingX};
|
||||||
|
border-radius: ${borderRadius};
|
||||||
|
font-size: ${fontSizeBase};
|
||||||
|
border: 1px solid ${colorGrayLighter};
|
||||||
|
box-shadow: 0 0 0 1px ${colorGrayLighter};
|
||||||
|
`;
|
||||||
|
// @ts-ignore - Button is a JS Component
|
||||||
|
const DeletePollOptionButton = styled(Button)`
|
||||||
|
font-size: ${fontSizeBase};
|
||||||
|
flex: none;
|
||||||
|
width: 40px;
|
||||||
|
position: relative;
|
||||||
|
& > i {
|
||||||
|
font-size: 150%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ErrorSpacer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
height: 1.25rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const InputError = styled(ErrorSpacer)`
|
||||||
|
color: ${colorDanger};
|
||||||
|
font-size: ${fontSizeSmall};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Instructions = styled.div`
|
||||||
|
margin-bottom: ${lgPaddingX};
|
||||||
|
color: ${colorText};
|
||||||
|
`;
|
||||||
|
|
||||||
|
type PollQuestionAreaProps = {
|
||||||
|
hasError: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const PollQuestionArea = styled.textarea<PollQuestionAreaProps>`
|
||||||
|
resize: none;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-radius: ${borderSize};
|
||||||
|
box-shadow: 0 0 0 ${borderSize} ${colorBlueLight}, inset 0 0 0 1px ${colorPrimary};
|
||||||
|
}
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
color: ${colorText};
|
||||||
|
-webkit-appearance: none;
|
||||||
|
padding: calc(${smPaddingY} * 2) ${smPaddingX};
|
||||||
|
border-radius: ${borderRadius};
|
||||||
|
font-size: ${fontSizeBase};
|
||||||
|
border: 1px solid ${colorGrayLighter};
|
||||||
|
box-shadow: 0 0 0 1px ${colorGrayLighter};
|
||||||
|
|
||||||
|
${({ hasError }) => hasError && `
|
||||||
|
border-color: ${colorDanger};
|
||||||
|
box-shadow: 0 0 0 1px ${colorDanger};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SectionHeading = styled.h4`
|
||||||
|
margin-top: 0;
|
||||||
|
font-weight: 600;
|
||||||
|
color: ${colorHeading};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ResponseType = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-flow: wrap;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: ${lgPaddingX};
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// @ts-ignore - Button is a JS Component
|
||||||
|
const PollConfigButton = styled(Button)`
|
||||||
|
border: solid ${colorGrayLight} 1px;
|
||||||
|
min-height: ${pollInputHeight};
|
||||||
|
font-size: ${fontSizeBase};
|
||||||
|
white-space: pre-wrap;
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
${({ selected }) => selected && `
|
||||||
|
background-color: ${colorGrayLightest};
|
||||||
|
font-size: ${fontSizeBase};
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
background-color: ${colorGrayLightest} !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
|
||||||
|
${({ small }) => small && `
|
||||||
|
width: 49% !important;
|
||||||
|
`}
|
||||||
|
|
||||||
|
${({ full }) => full && `
|
||||||
|
width: 100%;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PollParagraph = styled.div`
|
||||||
|
color: ${colorText};
|
||||||
|
margin-bottom: 0.9rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PollCheckbox = styled.div`
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: ${pollSmMargin};
|
||||||
|
margin-bottom: ${pollMdMargin};
|
||||||
|
`;
|
||||||
|
|
||||||
|
// @ts-ignore - Button is a JS Component
|
||||||
|
const AddItemButton = styled(Button)`
|
||||||
|
top: 1px;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
color: ${colorPrimary};
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
font-size: ${fontSizeBase};
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
& > span {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Row = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 0.7rem;
|
||||||
|
margin-bottom: 0.7rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Warning = styled.div`
|
||||||
|
color: ${colorWarning};
|
||||||
|
font-size: ${fontSizeSmall};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CustomInputRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: nowrap;
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Col = styled.div`
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
flex-flow: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
padding-right: 0.1rem;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Toggle = styled.label`
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// @ts-ignore - Button is a JS Component
|
||||||
|
const StartPollBtn = styled(Button)`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
min-height: ${pollInputHeight};
|
||||||
|
margin-top: 1rem;
|
||||||
|
font-size: ${fontSizeBase};
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
& > span {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const NoSlidePanelContainer = styled.div`
|
||||||
|
color: ${colorGrayDark};
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// @ts-ignore - Button is a JS Component
|
||||||
|
const PollButton = styled(Button)`
|
||||||
|
margin-top: ${smPaddingY};
|
||||||
|
margin-bottom: ${smPaddingY};
|
||||||
|
// background-color: ${colorWhite};
|
||||||
|
box-shadow: 0 0 0 1px ${colorPrimary};
|
||||||
|
color: ${colorWhite};
|
||||||
|
background-color: ${colorPrimary}
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
color: ${colorGray};
|
||||||
|
}
|
||||||
|
|
||||||
|
& > span:hover {
|
||||||
|
color: ${colorWhite};
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: ${colorWhite};
|
||||||
|
box-shadow: 0 0 0 1px ${pollBlue};
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
color: ${pollBlue};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: ${colorWhite};
|
||||||
|
box-shadow: 0 0 0 1px ${pollBlue};
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
color: ${pollBlue};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
margin-right: inherit;
|
||||||
|
margin-left: ${smPaddingY};
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
margin-right: ${smPaddingY};
|
||||||
|
margin-left: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
margin-right: 1rem;
|
||||||
|
margin-left: inherit;
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
margin-right: inherit;
|
||||||
|
margin-left: ${smPaddingY};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 0 1px ${colorWhite};
|
||||||
|
background-color: ${colorWhite};
|
||||||
|
color: ${pollBlue};
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
color: ${pollBlue};
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const DragAndDropPollContainer = styled.div`
|
||||||
|
width: 200px !important;
|
||||||
|
height: 200px !important;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Question = styled.div`
|
||||||
|
margin-bottom: ${lgPaddingX};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const OptionWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ResponseArea = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CustomInputHeading = styled(SectionHeading)`
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding-bottom: ${jumboPaddingY};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CustomInputHeadingCol = styled(Col)`
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CustomInputToggleCol = styled(Col)`
|
||||||
|
flex-shrink: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AnonymousHeading = styled(CustomInputHeading)``;
|
||||||
|
|
||||||
|
const AnonymousHeadingCol = styled(CustomInputHeadingCol)``;
|
||||||
|
|
||||||
|
const AnonymousToggleCol = styled(CustomInputToggleCol)``;
|
||||||
|
|
||||||
|
const AnonymousRow = styled(Row)`
|
||||||
|
flex-flow: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ResultLeft = styled.td`
|
||||||
|
padding: 0 .5rem 0 0;
|
||||||
|
border-bottom: 1px solid ${colorGrayLightest};
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
padding: 0 0 0 .5rem;
|
||||||
|
}
|
||||||
|
padding-bottom: .25rem;
|
||||||
|
word-break: break-all;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ResultRight = styled.td`
|
||||||
|
padding-bottom: .25rem;
|
||||||
|
word-break: break-all;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Main = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Left = styled.div`
|
||||||
|
font-weight: bold;
|
||||||
|
max-width: ${pollResultWidth};
|
||||||
|
min-width: ${pollStatsElementWidth};
|
||||||
|
word-wrap: break-word;
|
||||||
|
flex: 6;
|
||||||
|
|
||||||
|
padding: ${smPaddingY};
|
||||||
|
margin-top: ${pollSmMargin};
|
||||||
|
margin-bottom: ${pollSmMargin};
|
||||||
|
color: ${colorText};
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Center = styled.div`
|
||||||
|
position: relative;
|
||||||
|
flex: 3;
|
||||||
|
border-left: 1px solid ${colorGrayLighter};
|
||||||
|
border-right : none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
border-left: none;
|
||||||
|
border-right: 1px solid ${colorGrayLighter};
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: ${smPaddingY};
|
||||||
|
margin-top: ${pollSmMargin};
|
||||||
|
margin-bottom: ${pollSmMargin};
|
||||||
|
color: ${colorText};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Right = styled.div`
|
||||||
|
text-align: right;
|
||||||
|
max-width: ${pollStatsElementWidth};
|
||||||
|
min-width: ${pollStatsElementWidth};
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: ${smPaddingY};
|
||||||
|
margin-top: ${pollSmMargin};
|
||||||
|
margin-bottom: ${pollSmMargin};
|
||||||
|
color: ${colorText};
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BarShade = styled.div`
|
||||||
|
background-color: ${colorGrayLighter};
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const BarVal = styled.div`
|
||||||
|
position: inherit;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Stats = styled.div`
|
||||||
|
margin-bottom: ${smPaddingX};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid ${pollStatsBorderColor};
|
||||||
|
border-radius: ${borderSizeLarge};
|
||||||
|
padding: ${mdPaddingX};
|
||||||
|
|
||||||
|
& > div {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
& > div:nth-child(even) {
|
||||||
|
position: relative;
|
||||||
|
height: 75%;
|
||||||
|
width: 50%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled.span`
|
||||||
|
font-weight: bold;
|
||||||
|
word-break: break-all;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Status = styled.div`
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ellipsis = keyframes`
|
||||||
|
to {
|
||||||
|
width: 1.25em;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface ConnectingAnimationProps {
|
||||||
|
animations: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConnectingAnimation = styled.span<ConnectingAnimationProps>`
|
||||||
|
&:after {
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: bottom;
|
||||||
|
content: "\\2026"; /* ascii code for the ellipsis character */
|
||||||
|
width: 0;
|
||||||
|
margin: 0 1.25em 0 0;
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
margin: 0 0 0 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
${({ animations }) => animations && css`
|
||||||
|
animation: ${ellipsis} steps(4, end) 900ms infinite;
|
||||||
|
`}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ButtonsActions = styled.div`
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
|
// @ts-ignore - Button is a JS Component
|
||||||
|
const PublishButton = styled(Button)`
|
||||||
|
width: 48%;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const CancelButton = styled(PublishButton)``;
|
||||||
|
|
||||||
|
// @ts-ignore - Button is a JS Component
|
||||||
|
const LiveResultButton = styled(Button)`
|
||||||
|
width: 100%;
|
||||||
|
margin-top: ${smPaddingY};
|
||||||
|
margin-bottom: ${smPaddingY};
|
||||||
|
font-size: ${fontSizeBase};
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Separator = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
height: 1px;
|
||||||
|
min-height: 1px;
|
||||||
|
background-color: ${colorGrayLightest};
|
||||||
|
padding: 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const THeading = styled.th`
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
ToggleLabel,
|
||||||
|
PollOptionInput,
|
||||||
|
DeletePollOptionButton,
|
||||||
|
ErrorSpacer,
|
||||||
|
InputError,
|
||||||
|
Instructions,
|
||||||
|
PollQuestionArea,
|
||||||
|
SectionHeading,
|
||||||
|
ResponseType,
|
||||||
|
PollConfigButton,
|
||||||
|
PollParagraph,
|
||||||
|
PollCheckbox,
|
||||||
|
AddItemButton,
|
||||||
|
Row,
|
||||||
|
Col,
|
||||||
|
Toggle,
|
||||||
|
StartPollBtn,
|
||||||
|
NoSlidePanelContainer,
|
||||||
|
PollButton,
|
||||||
|
DragAndDropPollContainer,
|
||||||
|
Warning,
|
||||||
|
CustomInputRow,
|
||||||
|
Question,
|
||||||
|
OptionWrapper,
|
||||||
|
ResponseArea,
|
||||||
|
CustomInputHeading,
|
||||||
|
CustomInputHeadingCol,
|
||||||
|
CustomInputToggleCol,
|
||||||
|
AnonymousHeading,
|
||||||
|
AnonymousHeadingCol,
|
||||||
|
AnonymousToggleCol,
|
||||||
|
AnonymousRow,
|
||||||
|
ResultLeft,
|
||||||
|
ResultRight,
|
||||||
|
Main,
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
BarShade,
|
||||||
|
BarVal,
|
||||||
|
Stats,
|
||||||
|
Title,
|
||||||
|
Status,
|
||||||
|
ConnectingAnimation,
|
||||||
|
ButtonsActions,
|
||||||
|
PublishButton,
|
||||||
|
CancelButton,
|
||||||
|
LiveResultButton,
|
||||||
|
Separator,
|
||||||
|
THeading,
|
||||||
|
};
|
@ -1,16 +1,15 @@
|
|||||||
import createUseSubscription from './createUseSubscription';
|
import useCreateUseSubscription from './createUseSubscription';
|
||||||
import MEETING_SUBSCRIPTION from '../graphql/queries/meetingSubscription';
|
import MEETING_SUBSCRIPTION from '../graphql/queries/meetingSubscription';
|
||||||
import { Meeting } from '../../Types/meeting';
|
import { Meeting } from '../../Types/meeting';
|
||||||
|
|
||||||
const useMeetingSubscription = createUseSubscription<Meeting>(MEETING_SUBSCRIPTION);
|
const useMeetingSubscription = useCreateUseSubscription<Meeting>(MEETING_SUBSCRIPTION, {}, true);
|
||||||
|
|
||||||
export const useMeeting = (fn: (c: Partial<Meeting>) => Partial<Meeting>) => {
|
export const useMeeting = (fn: (c: Partial<Meeting>) => Partial<Meeting>) => {
|
||||||
const response = useMeetingSubscription(fn);
|
const response = useMeetingSubscription(fn);
|
||||||
const returnObject = {
|
return {
|
||||||
...response,
|
...response,
|
||||||
data: response.data?.[0],
|
data: response.data?.[0],
|
||||||
};
|
};
|
||||||
return returnObject;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useMeeting;
|
export default useMeeting;
|
||||||
|
@ -264,9 +264,7 @@ const pollAnswerOptionDesc = 'div[data-test="optionsAnswers"]';
|
|||||||
exports.firstPollAnswerDescOption = `${pollAnswerOptionDesc} input:nth-child(1)`;
|
exports.firstPollAnswerDescOption = `${pollAnswerOptionDesc} input:nth-child(1)`;
|
||||||
exports.secondPollAnswerDescOption = `${pollAnswerOptionDesc}>>nth=1`;
|
exports.secondPollAnswerDescOption = `${pollAnswerOptionDesc}>>nth=1`;
|
||||||
exports.submitAnswersMultiple = 'button[data-test="submitAnswersMultiple"]';
|
exports.submitAnswersMultiple = 'button[data-test="submitAnswersMultiple"]';
|
||||||
exports.numberVotes = 'div[data-test="numberOfVotes"]';
|
exports.userVoteLiveResult = 'td[data-test="userVoteLiveResult"]';
|
||||||
exports.answer1 = 'div[data-test="numberOfVotes"]>>nth=0';
|
|
||||||
exports.answer2 = 'div[data-test="numberOfVotes"]>>nth=1';
|
|
||||||
exports.errorNoValueInput = 'div[data-test="errorNoValueInput"]';
|
exports.errorNoValueInput = 'div[data-test="errorNoValueInput"]';
|
||||||
exports.smartSlides1 = 'smartSlidesPresentation.pdf';
|
exports.smartSlides1 = 'smartSlidesPresentation.pdf';
|
||||||
exports.responsePollQuestion = 'div[data-test="pollQuestion"]';
|
exports.responsePollQuestion = 'div[data-test="pollQuestion"]';
|
||||||
@ -278,7 +276,7 @@ exports.closePollingBtn = 'button[data-test="closePolling"]';
|
|||||||
exports.yesNoOption = 'button[data-test="yesNoQuickPoll"]';
|
exports.yesNoOption = 'button[data-test="yesNoQuickPoll"]';
|
||||||
exports.yesNoAbstentionOption = 'li[role="menuitem"]>>nth=1';
|
exports.yesNoAbstentionOption = 'li[role="menuitem"]>>nth=1';
|
||||||
exports.pollAnswerOptionE = 'button[data-test="pollAnswerOption"]>>nth=4';
|
exports.pollAnswerOptionE = 'button[data-test="pollAnswerOption"]>>nth=4';
|
||||||
exports.answerE = 'div[data-test="numberOfVotes"]>>nth=4';
|
|
||||||
// Presentation
|
// Presentation
|
||||||
exports.currentSlideImg = '[id="whiteboard-element"] [class="tl-image"]';
|
exports.currentSlideImg = '[id="whiteboard-element"] [class="tl-image"]';
|
||||||
exports.uploadPresentationFileName = 'uploadTest.png';
|
exports.uploadPresentationFileName = 'uploadTest.png';
|
||||||
|
@ -60,7 +60,7 @@ class LearningDashboard extends MultiUsers {
|
|||||||
await this.modPage.waitAndClick(e.startPoll);
|
await this.modPage.waitAndClick(e.startPoll);
|
||||||
|
|
||||||
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
||||||
await this.modPage.hasText(e.numberVotes, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, 'True');
|
||||||
await this.modPage.waitAndClick(e.cancelPollBtn);
|
await this.modPage.waitAndClick(e.cancelPollBtn);
|
||||||
|
|
||||||
//ABCD
|
//ABCD
|
||||||
@ -69,7 +69,7 @@ class LearningDashboard extends MultiUsers {
|
|||||||
await this.modPage.waitAndClick(e.pollLetterAlternatives);
|
await this.modPage.waitAndClick(e.pollLetterAlternatives);
|
||||||
await this.modPage.waitAndClick(e.startPoll);
|
await this.modPage.waitAndClick(e.startPoll);
|
||||||
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
||||||
await this.modPage.hasText(e.numberVotes, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, 'A');
|
||||||
await this.modPage.waitAndClick(e.cancelPollBtn);
|
await this.modPage.waitAndClick(e.cancelPollBtn);
|
||||||
|
|
||||||
//Yes/No/Abstention
|
//Yes/No/Abstention
|
||||||
@ -78,7 +78,7 @@ class LearningDashboard extends MultiUsers {
|
|||||||
await this.modPage.waitAndClick(e.pollYesNoAbstentionBtn);
|
await this.modPage.waitAndClick(e.pollYesNoAbstentionBtn);
|
||||||
await this.modPage.waitAndClick(e.startPoll);
|
await this.modPage.waitAndClick(e.startPoll);
|
||||||
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
||||||
await this.modPage.hasText(e.numberVotes, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, 'Yes');
|
||||||
await this.modPage.waitAndClick(e.cancelPollBtn);
|
await this.modPage.waitAndClick(e.cancelPollBtn);
|
||||||
|
|
||||||
//User Response
|
//User Response
|
||||||
@ -89,7 +89,7 @@ class LearningDashboard extends MultiUsers {
|
|||||||
await this.userPage.waitForSelector(e.pollingContainer);
|
await this.userPage.waitForSelector(e.pollingContainer);
|
||||||
await this.userPage.type(e.pollAnswerOptionInput, e.answerMessage);
|
await this.userPage.type(e.pollAnswerOptionInput, e.answerMessage);
|
||||||
await this.userPage.waitAndClick(e.pollSubmitAnswer);
|
await this.userPage.waitAndClick(e.pollSubmitAnswer);
|
||||||
await this.modPage.hasText(e.numberVotes, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, e.answerMessage);
|
||||||
await this.modPage.waitAndClick(e.cancelPollBtn);
|
await this.modPage.waitAndClick(e.cancelPollBtn);
|
||||||
|
|
||||||
//Checks
|
//Checks
|
||||||
|
@ -17,7 +17,9 @@ class Polling extends MultiUsers {
|
|||||||
async createPoll() {
|
async createPoll() {
|
||||||
await util.startPoll(this.modPage);
|
await util.startPoll(this.modPage);
|
||||||
await this.modPage.hasElement(e.pollMenuButton);
|
await this.modPage.hasElement(e.pollMenuButton);
|
||||||
|
await this.modPage.hasElement(e.publishPollingLabel);
|
||||||
|
await this.modPage.hasElement(e.cancelPollBtn);
|
||||||
|
await this.userPage.hasElement(e.pollingContainer);
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
await this.modPage.waitAndClick(e.closePollingBtn);
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
await this.modPage.wasRemoved(e.closePollingBtn);
|
||||||
}
|
}
|
||||||
@ -58,16 +60,13 @@ class Polling extends MultiUsers {
|
|||||||
await this.userPage.type(e.pollAnswerOptionInput, e.answerMessage);
|
await this.userPage.type(e.pollAnswerOptionInput, e.answerMessage);
|
||||||
await this.userPage.waitAndClick(e.pollSubmitAnswer);
|
await this.userPage.waitAndClick(e.pollSubmitAnswer);
|
||||||
|
|
||||||
await this.modPage.hasText(e.receivedAnswer, e.answerMessage);
|
await this.modPage.hasText(e.userVoteLiveResult, e.answerMessage);
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.publishPollingLabel);
|
await this.modPage.waitAndClick(e.publishPollingLabel);
|
||||||
await this.modPage.waitForSelector(e.restartPoll);
|
await this.modPage.wasRemoved(e.pollingContainer);
|
||||||
|
|
||||||
await this.modPage.hasElement(e.wbDrawnRectangle, ELEMENT_WAIT_LONGER_TIME);
|
await this.modPage.hasElement(e.wbDrawnRectangle, ELEMENT_WAIT_LONGER_TIME);
|
||||||
await this.userPage.hasElement(e.wbDrawnRectangle);
|
await this.userPage.hasElement(e.wbDrawnRectangle);
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async stopPoll() {
|
async stopPoll() {
|
||||||
@ -110,20 +109,20 @@ class Polling extends MultiUsers {
|
|||||||
await this.checkLastOptionText();
|
await this.checkLastOptionText();
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
await this.modPage.waitAndClick(e.closePollingBtn);
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.actions);
|
|
||||||
await this.modPage.waitAndClick(e.managePresentations);
|
|
||||||
await this.modPage.waitAndClick(e.removePresentation);
|
|
||||||
await this.modPage.waitAndClick(e.confirmManagePresentation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async notAbleStartNewPollWithoutPresentation() {
|
async notAbleStartNewPollWithoutPresentation() {
|
||||||
await this.modPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME);
|
await this.modPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME);
|
||||||
await this.modPage.waitAndClick(e.actions);
|
await this.modPage.waitAndClick(e.actions);
|
||||||
await this.modPage.waitAndClick(e.managePresentations);
|
await this.modPage.waitAndClick(e.managePresentations);
|
||||||
await this.modPage.waitAndClick(e.removePresentation);
|
|
||||||
await this.modPage.waitAndClick(e.confirmManagePresentation);
|
|
||||||
|
|
||||||
|
const allRemovePresentationBtn = await this.modPage.getLocator(e.removePresentation).all();
|
||||||
|
// reversing the order of clicking is needed to avoid failure as the tooltip shows in front of the below button
|
||||||
|
const reversedRemovePresentationButtons = allRemovePresentationBtn.reverse();
|
||||||
|
for (const removeBtn of reversedRemovePresentationButtons) {
|
||||||
|
await removeBtn.click({ timeout: ELEMENT_WAIT_TIME });
|
||||||
|
}
|
||||||
|
await this.modPage.waitAndClick(e.confirmManagePresentation);
|
||||||
await this.modPage.waitAndClick(e.actions);
|
await this.modPage.waitAndClick(e.actions);
|
||||||
await this.modPage.waitAndClick(e.polling);
|
await this.modPage.waitAndClick(e.polling);
|
||||||
await this.modPage.hasElement(e.noPresentation);
|
await this.modPage.hasElement(e.noPresentation);
|
||||||
@ -147,7 +146,7 @@ class Polling extends MultiUsers {
|
|||||||
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
||||||
|
|
||||||
await this.modPage.hasText(e.currentPollQuestion, /Test/);
|
await this.modPage.hasText(e.currentPollQuestion, /Test/);
|
||||||
await this.modPage.hasText(e.answer1, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, '1');
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
await this.modPage.waitAndClick(e.closePollingBtn);
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
await this.modPage.wasRemoved(e.closePollingBtn);
|
||||||
@ -175,8 +174,8 @@ class Polling extends MultiUsers {
|
|||||||
await this.userPage.waitAndClick(e.secondPollAnswerOptionBtn);
|
await this.userPage.waitAndClick(e.secondPollAnswerOptionBtn);
|
||||||
await this.userPage.waitAndClickElement(e.submitAnswersMultiple);
|
await this.userPage.waitAndClickElement(e.submitAnswersMultiple);
|
||||||
|
|
||||||
await this.modPage.hasText(e.answer1, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, '1');
|
||||||
await this.modPage.hasText(e.answer2, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, '2');
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
await this.modPage.waitAndClick(e.closePollingBtn);
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
await this.modPage.wasRemoved(e.closePollingBtn);
|
||||||
@ -193,11 +192,10 @@ class Polling extends MultiUsers {
|
|||||||
await this.userPage.type(e.pollAnswerOptionInput, 'test');
|
await this.userPage.type(e.pollAnswerOptionInput, 'test');
|
||||||
await this.userPage.waitAndClick(e.pollSubmitAnswer);
|
await this.userPage.waitAndClick(e.pollSubmitAnswer);
|
||||||
await this.userPage.wasRemoved(e.pollingContainer, ELEMENT_WAIT_LONGER_TIME);
|
await this.userPage.wasRemoved(e.pollingContainer, ELEMENT_WAIT_LONGER_TIME);
|
||||||
await this.modPage.hasText(e.receivedAnswer, 'test');
|
await this.modPage.hasText(e.userVoteLiveResult, 'test');
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.publishPollingLabel);
|
await this.modPage.waitAndClick(e.publishPollingLabel);
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
await this.modPage.wasRemoved(e.pollingContainer);
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
|
||||||
|
|
||||||
// Multiple Choices
|
// Multiple Choices
|
||||||
await sleep(500); // avoid error when the tooltip is in front of the button due to layout shift
|
await sleep(500); // avoid error when the tooltip is in front of the button due to layout shift
|
||||||
@ -206,23 +204,21 @@ class Polling extends MultiUsers {
|
|||||||
await this.userPage.waitAndClick(e.firstPollAnswerDescOption);
|
await this.userPage.waitAndClick(e.firstPollAnswerDescOption);
|
||||||
await this.userPage.waitAndClick(e.secondPollAnswerDescOption);
|
await this.userPage.waitAndClick(e.secondPollAnswerDescOption);
|
||||||
await this.userPage.waitAndClick(e.submitAnswersMultiple);
|
await this.userPage.waitAndClick(e.submitAnswersMultiple);
|
||||||
await this.modPage.hasText(e.answer1, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, 'A) 2222');
|
||||||
await this.modPage.hasText(e.answer2, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, 'B) 3333');
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.publishPollingLabel);
|
await this.modPage.waitAndClick(e.publishPollingLabel);
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
await this.modPage.wasRemoved(e.pollingContainer);
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
|
||||||
|
|
||||||
// One option answer
|
// One option answer
|
||||||
await sleep(500); // avoid error when the tooltip is in front of the button due to layout shift
|
await sleep(500); // avoid error when the tooltip is in front of the button due to layout shift
|
||||||
await skipSlide(this.modPage);
|
await skipSlide(this.modPage);
|
||||||
await this.modPage.waitAndClick(e.quickPoll);
|
await this.modPage.waitAndClick(e.quickPoll);
|
||||||
await this.userPage.waitAndClick(e.pollAnswerOptionE);
|
await this.userPage.waitAndClick(e.pollAnswerOptionE);
|
||||||
await this.modPage.hasText(e.answerE, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, 'E) 22222');
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.publishPollingLabel);
|
await this.modPage.waitAndClick(e.publishPollingLabel);
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
await this.modPage.wasRemoved(e.pollingContainer);
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
|
||||||
|
|
||||||
// Yes/No/Abstention
|
// Yes/No/Abstention
|
||||||
await sleep(500); // avoid error when the tooltip is in front of the button due to layout shift
|
await sleep(500); // avoid error when the tooltip is in front of the button due to layout shift
|
||||||
@ -230,24 +226,21 @@ class Polling extends MultiUsers {
|
|||||||
await this.modPage.waitAndClick(e.yesNoOption);
|
await this.modPage.waitAndClick(e.yesNoOption);
|
||||||
await this.modPage.waitAndClick(e.yesNoAbstentionOption)
|
await this.modPage.waitAndClick(e.yesNoAbstentionOption)
|
||||||
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
||||||
await this.modPage.hasText(e.answer1, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, 'Yes');
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.publishPollingLabel);
|
await this.modPage.waitAndClick(e.publishPollingLabel);
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
await this.modPage.wasRemoved(e.pollingContainer);
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
|
||||||
|
|
||||||
// True/False
|
// True/False
|
||||||
await sleep(500); // avoid error when the tooltip is in front of the button due to layout shift
|
await sleep(500); // avoid error when the tooltip is in front of the button due to layout shift
|
||||||
await skipSlide(this.modPage);
|
await skipSlide(this.modPage);
|
||||||
await this.modPage.waitAndClick(e.quickPoll);
|
await this.modPage.waitAndClick(e.quickPoll);
|
||||||
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
||||||
await this.modPage.hasText(e.answer1, '1');
|
await this.modPage.hasText(e.userVoteLiveResult, 'True');
|
||||||
await this.modPage.waitAndClick(e.publishPollingLabel);
|
await this.modPage.waitAndClick(e.publishPollingLabel);
|
||||||
|
|
||||||
await this.modPage.hasElementDisabled(e.nextSlide);
|
await this.modPage.hasElementDisabled(e.nextSlide);
|
||||||
|
await this.modPage.wasRemoved(e.pollingContainer);
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async pollResultsOnChat() {
|
async pollResultsOnChat() {
|
||||||
@ -273,24 +266,29 @@ class Polling extends MultiUsers {
|
|||||||
await this.modPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME);
|
await this.modPage.waitForSelector(e.whiteboard, ELEMENT_WAIT_LONGER_TIME);
|
||||||
await util.startPoll(this.modPage);
|
await util.startPoll(this.modPage);
|
||||||
|
|
||||||
|
const wbDrawnRectangleLocator = await this.modPage.getLocator(e.wbDrawnRectangle);
|
||||||
|
const initialWbDrawnRectangleCount = await wbDrawnRectangleLocator.count();
|
||||||
|
|
||||||
await this.modPage.hasElementDisabled(e.publishPollingLabel);
|
await this.modPage.hasElementDisabled(e.publishPollingLabel);
|
||||||
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
||||||
await this.modPage.hasElement(e.publishPollingLabel);
|
await this.modPage.hasElement(e.publishPollingLabel);
|
||||||
await this.modPage.waitAndClick(e.publishPollingLabel);
|
await this.modPage.waitAndClick(e.publishPollingLabel);
|
||||||
|
await expect(wbDrawnRectangleLocator).toHaveCount(initialWbDrawnRectangleCount + 1);
|
||||||
|
|
||||||
const wbDrawnRectangleLocator = await this.modPage.getLocator(e.wbDrawnRectangle).last();
|
const lastWbDrawnRectangleLocator = await wbDrawnRectangleLocator.last();
|
||||||
await expect(wbDrawnRectangleLocator).toBeVisible({ timeout: ELEMENT_WAIT_TIME});
|
await expect(lastWbDrawnRectangleLocator).toBeVisible({ timeout: ELEMENT_WAIT_TIME});
|
||||||
|
|
||||||
const modWbLocator = this.modPage.getLocator(e.whiteboard);
|
const modWbLocator = this.modPage.getLocator(e.whiteboard);
|
||||||
const wbBox = await modWbLocator.boundingBox();
|
const wbBox = await modWbLocator.boundingBox();
|
||||||
|
|
||||||
await wbDrawnRectangleLocator.dblclick();
|
// poll results should be editable by the presenter
|
||||||
|
await lastWbDrawnRectangleLocator.dblclick({ timeout: ELEMENT_WAIT_TIME });
|
||||||
await this.modPage.page.mouse.down();
|
await this.modPage.page.mouse.down();
|
||||||
await this.modPage.page.mouse.move(wbBox.x + 0.7 * wbBox.width, wbBox.y + 0.7 * wbBox.height);
|
await this.modPage.page.mouse.move(wbBox.x + 0.7 * wbBox.width, wbBox.y + 0.7 * wbBox.height);
|
||||||
await this.modPage.page.mouse.up();
|
await this.modPage.page.mouse.up();
|
||||||
await wbDrawnRectangleLocator.dblclick();
|
await lastWbDrawnRectangleLocator.dblclick({ timeout: ELEMENT_WAIT_TIME });
|
||||||
await this.modPage.page.keyboard.type('test');
|
await this.modPage.page.keyboard.type('test');
|
||||||
await expect(wbDrawnRectangleLocator).toContainText('test');
|
await expect(lastWbDrawnRectangleLocator).toContainText('test');
|
||||||
|
|
||||||
// user turns to presenter to edit the poll results
|
// user turns to presenter to edit the poll results
|
||||||
await this.modPage.waitAndClick(e.userListItem);
|
await this.modPage.waitAndClick(e.userListItem);
|
||||||
@ -300,7 +298,7 @@ class Polling extends MultiUsers {
|
|||||||
await this.userPage.waitAndClick(e.resetZoomButton);
|
await this.userPage.waitAndClick(e.resetZoomButton);
|
||||||
|
|
||||||
const wbDrawnRectangleUserLocator = await this.userPage.getLocator(e.wbDrawnRectangle).last();
|
const wbDrawnRectangleUserLocator = await this.userPage.getLocator(e.wbDrawnRectangle).last();
|
||||||
await wbDrawnRectangleUserLocator.dblclick();
|
await wbDrawnRectangleUserLocator.dblclick({ timeout: ELEMENT_WAIT_TIME });
|
||||||
await this.userPage.page.keyboard.type('testUser');
|
await this.userPage.page.keyboard.type('testUser');
|
||||||
await expect(wbDrawnRectangleUserLocator).toContainText('testUser');
|
await expect(wbDrawnRectangleUserLocator).toContainText('testUser');
|
||||||
|
|
||||||
@ -311,17 +309,12 @@ class Polling extends MultiUsers {
|
|||||||
|
|
||||||
async pollResultsInDifferentPresentation() {
|
async pollResultsInDifferentPresentation() {
|
||||||
await waitAndClearDefaultPresentationNotification(this.modPage);
|
await waitAndClearDefaultPresentationNotification(this.modPage);
|
||||||
|
|
||||||
|
|
||||||
await uploadSinglePresentation(this.modPage, e.questionSlideFileName);
|
|
||||||
await util.startPoll(this.modPage);
|
await util.startPoll(this.modPage);
|
||||||
|
await this.userPage.waitAndClick(e.pollAnswerOptionBtn);
|
||||||
|
await uploadSinglePresentation(this.modPage, e.questionSlideFileName);
|
||||||
await this.modPage.waitAndClick(e.publishPollingLabel);
|
await this.modPage.waitAndClick(e.publishPollingLabel);
|
||||||
|
|
||||||
// Check poll results
|
// Check poll results
|
||||||
await this.modPage.hasElement(e.wbDrawnRectangle);
|
await this.modPage.hasElement(e.wbDrawnRectangle);
|
||||||
|
|
||||||
await this.modPage.waitAndClick(e.closePollingBtn);
|
|
||||||
await this.modPage.wasRemoved(e.closePollingBtn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async startNewPoll() {
|
async startNewPoll() {
|
||||||
|
@ -24,7 +24,7 @@ test.describe('Polling', async () => {
|
|||||||
await polling.quickPoll();
|
await polling.quickPoll();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Create poll with user response @ci @flaky', async () => {
|
test('Create poll with user response @ci', async () => {
|
||||||
await polling.pollUserResponse();
|
await polling.pollUserResponse();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -61,7 +61,8 @@ test.describe('Polling', async () => {
|
|||||||
await polling.pollResultsOnWhiteboard();
|
await polling.pollResultsOnWhiteboard();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Poll results in a different presentation', async () => {
|
test('Poll results in a different presentation', async ({}, testInfo) => {
|
||||||
|
test.fixme(!testInfo.config.fullyParallel, 'Currently only works in parallel mode. Poll results not being displayed in the presentation');
|
||||||
await polling.pollResultsInDifferentPresentation();
|
await polling.pollResultsInDifferentPresentation();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -24,7 +24,7 @@ async function getCurrentPresentationHeight(locator) {
|
|||||||
|
|
||||||
async function uploadSinglePresentation(test, fileName, uploadTimeout = UPLOAD_PDF_WAIT_TIME) {
|
async function uploadSinglePresentation(test, fileName, uploadTimeout = UPLOAD_PDF_WAIT_TIME) {
|
||||||
const firstSlideSrc = await test.page.evaluate(selector => document.querySelector(selector)
|
const firstSlideSrc = await test.page.evaluate(selector => document.querySelector(selector)
|
||||||
.style
|
?.style
|
||||||
.backgroundImage
|
.backgroundImage
|
||||||
.split('"')[1],
|
.split('"')[1],
|
||||||
[e.currentSlideImg]);
|
[e.currentSlideImg]);
|
||||||
|
Loading…
Reference in New Issue
Block a user