Merge pull request #19989 from JoVictorNunes/remove-old-polls
cleanup: remove old polls
This commit is contained in:
commit
f2f255d37f
@ -4,7 +4,6 @@ import AbstractCollection from '/imports/ui/services/LocalCollectionSynchronizer
|
|||||||
import PresentationUploadToken from '/imports/api/presentation-upload-token';
|
import PresentationUploadToken from '/imports/api/presentation-upload-token';
|
||||||
import Screenshare from '/imports/api/screenshare';
|
import Screenshare from '/imports/api/screenshare';
|
||||||
import UserInfos from '/imports/api/users-infos';
|
import UserInfos from '/imports/api/users-infos';
|
||||||
import Polls, { CurrentPoll } from '/imports/api/polls';
|
|
||||||
import UserSettings from '/imports/api/users-settings';
|
import UserSettings from '/imports/api/users-settings';
|
||||||
import VideoStreams from '/imports/api/video-streams';
|
import VideoStreams from '/imports/api/video-streams';
|
||||||
import VoiceUsers from '/imports/api/voice-users';
|
import VoiceUsers from '/imports/api/voice-users';
|
||||||
@ -21,8 +20,6 @@ import Users from '/imports/api/users';
|
|||||||
|
|
||||||
// Custom Publishers
|
// Custom Publishers
|
||||||
export const localCollectionRegistry = {
|
export const localCollectionRegistry = {
|
||||||
localCurrentPollSync: new AbstractCollection(CurrentPoll, CurrentPoll),
|
|
||||||
localPollsSync: new AbstractCollection(Polls, Polls),
|
|
||||||
localPresentationUploadTokenSync: new AbstractCollection(
|
localPresentationUploadTokenSync: new AbstractCollection(
|
||||||
PresentationUploadToken,
|
PresentationUploadToken,
|
||||||
PresentationUploadToken,
|
PresentationUploadToken,
|
||||||
|
@ -6,7 +6,6 @@ import { removeExternalVideoStreamer } from '/imports/api/external-videos/server
|
|||||||
import clearUsers from '/imports/api/users/server/modifiers/clearUsers';
|
import clearUsers from '/imports/api/users/server/modifiers/clearUsers';
|
||||||
import clearUsersSettings from '/imports/api/users-settings/server/modifiers/clearUsersSettings';
|
import clearUsersSettings from '/imports/api/users-settings/server/modifiers/clearUsersSettings';
|
||||||
import clearBreakouts from '/imports/api/breakouts/server/modifiers/clearBreakouts';
|
import clearBreakouts from '/imports/api/breakouts/server/modifiers/clearBreakouts';
|
||||||
import clearPolls from '/imports/api/polls/server/modifiers/clearPolls';
|
|
||||||
import clearCaptions from '/imports/api/captions/server/modifiers/clearCaptions';
|
import clearCaptions from '/imports/api/captions/server/modifiers/clearCaptions';
|
||||||
import clearPads from '/imports/api/pads/server/modifiers/clearPads';
|
import clearPads from '/imports/api/pads/server/modifiers/clearPads';
|
||||||
import clearVoiceUsers from '/imports/api/voice-users/server/modifiers/clearVoiceUsers';
|
import clearVoiceUsers from '/imports/api/voice-users/server/modifiers/clearVoiceUsers';
|
||||||
@ -33,7 +32,6 @@ export default async function meetingHasEnded(meetingId) {
|
|||||||
clearCaptions(meetingId),
|
clearCaptions(meetingId),
|
||||||
clearPads(meetingId),
|
clearPads(meetingId),
|
||||||
clearBreakouts(meetingId),
|
clearBreakouts(meetingId),
|
||||||
clearPolls(meetingId),
|
|
||||||
clearUsers(meetingId),
|
clearUsers(meetingId),
|
||||||
clearUsersSettings(meetingId),
|
clearUsersSettings(meetingId),
|
||||||
clearVoiceUsers(meetingId),
|
clearVoiceUsers(meetingId),
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
|
|
||||||
const collectionOptions = Meteor.isClient ? {
|
|
||||||
connection: null,
|
|
||||||
} : {};
|
|
||||||
|
|
||||||
const Polls = new Mongo.Collection('polls', collectionOptions);
|
|
||||||
export const CurrentPoll = new Mongo.Collection('current-poll', { connection: null });
|
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
|
||||||
// We can have just one active poll per meeting
|
|
||||||
// makes no sense to index it by anything other than meetingId
|
|
||||||
|
|
||||||
Polls.createIndexAsync({ meetingId: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Polls;
|
|
@ -1,103 +0,0 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import Polls from '/imports/api/polls';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
// Modifiers
|
|
||||||
import clearPolls from './server/modifiers/clearPolls';
|
|
||||||
import removePoll from './server/modifiers/removePoll';
|
|
||||||
import addPoll from './server/modifiers/addPoll';
|
|
||||||
import updateVotes from './server/modifiers/updateVotes';
|
|
||||||
// Handlers
|
|
||||||
import pollStarted from './server/handlers/pollStarted';
|
|
||||||
import pollStopped from './server/handlers/pollStopped';
|
|
||||||
|
|
||||||
// mock test data
|
|
||||||
const _id = 'sJt6JaJMsTgy64TZG';
|
|
||||||
const meetingId = '183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1623285094106';
|
|
||||||
const requester = 'w_iotqesmfrtqj';
|
|
||||||
const pollType = 'TF';
|
|
||||||
const id = 'd2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1623285094108/1/1623285145173';
|
|
||||||
const answers = [{ id: 0, key: 'True' }, { id: 1, key: 'False' }];
|
|
||||||
const users = [];
|
|
||||||
const questionText = '';
|
|
||||||
const pollObj = {
|
|
||||||
_id,
|
|
||||||
meetingId,
|
|
||||||
requester,
|
|
||||||
pollType,
|
|
||||||
answers,
|
|
||||||
id,
|
|
||||||
users,
|
|
||||||
questionText,
|
|
||||||
};
|
|
||||||
|
|
||||||
Polls.insert(pollObj);
|
|
||||||
const poll = Polls.findOne(pollObj);
|
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
|
||||||
describe('Polls Collection', () => {
|
|
||||||
describe('Modifiers :', () => {
|
|
||||||
it('Validate (#_id, #meetingId, #requester, #pollType, #users, #answers)', () => {
|
|
||||||
expect(poll?._id).to.be.a('string').equal(_id);
|
|
||||||
expect(poll?.meetingId).to.be.a('string').equal(meetingId);
|
|
||||||
expect(poll?.requester).to.be.a('string').equal(requester);
|
|
||||||
expect(poll?.pollType).to.be.a('string').equal(pollType);
|
|
||||||
expect(poll?.users).to.be.an('array');
|
|
||||||
expect(poll?.answers).to.be.an('array');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('addPoll(): Should have added a poll', () => {
|
|
||||||
addPoll(meetingId, requester, { id, answers }, pollType, questionText);
|
|
||||||
expect(Polls.findOne({ id, meetingId })?.id).to.be.a('string').equal(pollObj.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updateVotes(): Should update vote for a poll', () => {
|
|
||||||
answers[0].numVotes = 1;
|
|
||||||
answers[1].numVotes = 0;
|
|
||||||
|
|
||||||
updateVotes({
|
|
||||||
id,
|
|
||||||
answers,
|
|
||||||
numResponders: 1,
|
|
||||||
numRespondents: 1,
|
|
||||||
}, meetingId);
|
|
||||||
|
|
||||||
expect(Polls.findOne({ meetingId, id })?.answers[0]?.numVotes).to.be.a('number').equal(1);
|
|
||||||
expect(Polls.findOne({ meetingId, id })?.answers[1]?.numVotes).to.be.a('number').equal(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('removePoll(): Should have removed specified poll', () => {
|
|
||||||
removePoll(meetingId, id);
|
|
||||||
expect(Polls.findOne({ meetingId, id })).to.be.an('undefined');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('clearPolls(): Should have cleared all polls', () => {
|
|
||||||
Polls.insert(pollObj);
|
|
||||||
clearPolls();
|
|
||||||
expect(Polls.findOne({ meetingId })).to.be.an('undefined');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Handlers :', () => {
|
|
||||||
it('pollStarted(): should add a poll and reset publishedPoll flag', () => {
|
|
||||||
delete answers[0].numVotes;
|
|
||||||
delete answers[1].numVotes;
|
|
||||||
|
|
||||||
pollStarted({
|
|
||||||
body: {
|
|
||||||
userId: requester,
|
|
||||||
poll: { id, answers },
|
|
||||||
pollType,
|
|
||||||
question: '',
|
|
||||||
},
|
|
||||||
}, meetingId);
|
|
||||||
|
|
||||||
expect(Polls.findOne({ meetingId, id })?.id).to.be.a('string').equal(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('pollStopped(): Should have removed poll', () => {
|
|
||||||
pollStopped({ body: { poll: { pollId: id } } }, meetingId);
|
|
||||||
expect(Polls.findOne({ meetingId, id })?.id).to.be.an('undefined');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
import RedisPubSub from '/imports/startup/server/redis';
|
|
||||||
import handlePollStarted from './handlers/pollStarted';
|
|
||||||
import handlePollStopped from './handlers/pollStopped';
|
|
||||||
import handlePollPublished from './handlers/pollPublished';
|
|
||||||
import handleUserVoted from './handlers/userVoted';
|
|
||||||
import handleUserResponded from './handlers/userResponded';
|
|
||||||
import handleUserTypedResponse from './handlers/userTypedResponse';
|
|
||||||
|
|
||||||
RedisPubSub.on('PollShowResultEvtMsg', handlePollPublished);
|
|
||||||
RedisPubSub.on('PollStartedEvtMsg', handlePollStarted);
|
|
||||||
RedisPubSub.on('PollStoppedEvtMsg', handlePollStopped);
|
|
||||||
RedisPubSub.on('PollUpdatedEvtMsg', handleUserVoted);
|
|
||||||
RedisPubSub.on('UserRespondedToPollRespMsg', handleUserResponded);
|
|
||||||
RedisPubSub.on('UserRespondedToTypedPollRespMsg', handleUserTypedResponse);
|
|
@ -1,11 +0,0 @@
|
|||||||
import { check } from 'meteor/check';
|
|
||||||
import setPublishedPoll from '../../../meetings/server/modifiers/setPublishedPoll';
|
|
||||||
|
|
||||||
export default function pollPublished({ body }, meetingId) {
|
|
||||||
const { pollId } = body;
|
|
||||||
|
|
||||||
check(meetingId, String);
|
|
||||||
check(pollId, String);
|
|
||||||
|
|
||||||
setPublishedPoll(meetingId, true);
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
import { check } from 'meteor/check';
|
|
||||||
import addPoll from '../modifiers/addPoll';
|
|
||||||
import setPublishedPoll from '../../../meetings/server/modifiers/setPublishedPoll';
|
|
||||||
|
|
||||||
export default async function pollStarted({ body }, meetingId) {
|
|
||||||
const {
|
|
||||||
userId, poll, pollType, secretPoll, question,
|
|
||||||
} = body;
|
|
||||||
|
|
||||||
check(meetingId, String);
|
|
||||||
check(userId, String);
|
|
||||||
check(poll, Object);
|
|
||||||
check(pollType, String);
|
|
||||||
check(secretPoll, Boolean);
|
|
||||||
check(question, String);
|
|
||||||
|
|
||||||
setPublishedPoll(meetingId, false);
|
|
||||||
|
|
||||||
const result = await addPoll(meetingId, userId, poll, pollType, secretPoll, question);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { check } from 'meteor/check';
|
|
||||||
import removePoll from '../modifiers/removePoll';
|
|
||||||
import clearPolls from '../modifiers/clearPolls';
|
|
||||||
|
|
||||||
export default async function pollStopped({ body }, meetingId) {
|
|
||||||
const { poll } = body;
|
|
||||||
|
|
||||||
check(meetingId, String);
|
|
||||||
|
|
||||||
if (poll) {
|
|
||||||
const { pollId } = poll;
|
|
||||||
|
|
||||||
check(pollId, String);
|
|
||||||
|
|
||||||
const result = await removePoll(meetingId, pollId);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await clearPolls(meetingId);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import { check } from 'meteor/check';
|
|
||||||
import Polls from '/imports/api/polls';
|
|
||||||
import Logger from '/imports/startup/server/logger';
|
|
||||||
|
|
||||||
export default async function userResponded({ body }) {
|
|
||||||
const { pollId, userId, answerIds } = body;
|
|
||||||
|
|
||||||
check(pollId, String);
|
|
||||||
check(userId, String);
|
|
||||||
check(answerIds, Array);
|
|
||||||
|
|
||||||
const selector = {
|
|
||||||
id: pollId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const modifier = {
|
|
||||||
$pull: {
|
|
||||||
users: userId,
|
|
||||||
},
|
|
||||||
$push: {
|
|
||||||
responses: { userId, answerIds },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const numberAffected = await Polls.updateAsync(selector, modifier);
|
|
||||||
|
|
||||||
if (numberAffected) {
|
|
||||||
Logger.info(`Updating Poll response (userId: ${userId}, response: ${JSON.stringify(answerIds)}, pollId: ${pollId})`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Logger.error(`Updating Poll responses: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
import { check } from 'meteor/check';
|
|
||||||
import Polls from '/imports/api/polls';
|
|
||||||
import RedisPubSub from '/imports/startup/server/redis';
|
|
||||||
|
|
||||||
export default async function userTypedResponse({ header, body }) {
|
|
||||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
|
||||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
|
||||||
const EVENT_NAME = 'RespondToPollReqMsg';
|
|
||||||
|
|
||||||
const { pollId, userId, answer } = body;
|
|
||||||
const { meetingId } = header;
|
|
||||||
|
|
||||||
check(pollId, String);
|
|
||||||
check(meetingId, String);
|
|
||||||
check(userId, String);
|
|
||||||
check(answer, String);
|
|
||||||
|
|
||||||
const poll = await Polls.findOneAsync({ meetingId, id: pollId });
|
|
||||||
|
|
||||||
let answerId = 0;
|
|
||||||
poll.answers.forEach((a) => {
|
|
||||||
const { id, key } = a;
|
|
||||||
if (key === answer) answerId = id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
requesterId: userId,
|
|
||||||
pollId,
|
|
||||||
questionId: 0,
|
|
||||||
answerIds: [answerId],
|
|
||||||
};
|
|
||||||
|
|
||||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload);
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
import { check } from 'meteor/check';
|
|
||||||
import updateVotes from '../modifiers/updateVotes';
|
|
||||||
|
|
||||||
export default async function userVoted({ body }, meetingId) {
|
|
||||||
const { poll } = body;
|
|
||||||
|
|
||||||
check(meetingId, String);
|
|
||||||
check(poll, {
|
|
||||||
id: String,
|
|
||||||
questionType: String,
|
|
||||||
questionText: String,
|
|
||||||
answers: [
|
|
||||||
{
|
|
||||||
id: Number,
|
|
||||||
key: String,
|
|
||||||
numVotes: Number,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
numRespondents: Number,
|
|
||||||
numResponders: Number,
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await updateVotes(poll, meetingId);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
import './eventHandlers';
|
|
||||||
import './publishers';
|
|
@ -1,56 +0,0 @@
|
|||||||
import Users from '/imports/api/users';
|
|
||||||
import Polls from '/imports/api/polls';
|
|
||||||
import Logger from '/imports/startup/server/logger';
|
|
||||||
import flat from 'flat';
|
|
||||||
import { check } from 'meteor/check';
|
|
||||||
|
|
||||||
export default async function addPoll(meetingId, requesterId, poll, pollType, secretPoll, question = '') {
|
|
||||||
check(requesterId, String);
|
|
||||||
check(meetingId, String);
|
|
||||||
check(poll, {
|
|
||||||
id: String,
|
|
||||||
answers: [
|
|
||||||
{
|
|
||||||
id: Number,
|
|
||||||
key: String,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
isMultipleResponse: Boolean,
|
|
||||||
});
|
|
||||||
|
|
||||||
const userSelector = {
|
|
||||||
meetingId,
|
|
||||||
userId: { $ne: requesterId },
|
|
||||||
clientType: { $ne: 'dial-in-user' },
|
|
||||||
};
|
|
||||||
|
|
||||||
const users = await Users.find(userSelector, { fields: { userId: 1 } })
|
|
||||||
.fetchAsync();
|
|
||||||
const userIds = users.map(user => user.userId);
|
|
||||||
|
|
||||||
const selector = {
|
|
||||||
meetingId,
|
|
||||||
requester: requesterId,
|
|
||||||
id: poll.id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const modifier = Object.assign(
|
|
||||||
{ meetingId },
|
|
||||||
{ requester: requesterId },
|
|
||||||
{ users: userIds },
|
|
||||||
{ question, pollType, secretPoll },
|
|
||||||
flat(poll, { safe: true }),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { insertedId } = await Polls.upsertAsync(selector, modifier);
|
|
||||||
|
|
||||||
if (insertedId) {
|
|
||||||
Logger.info(`Added Poll id=${poll.id}`);
|
|
||||||
} else {
|
|
||||||
Logger.info(`Upserted Poll id=${poll.id}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Logger.error(`Adding Poll to collection: ${poll.id}`);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
import Polls from '/imports/api/polls';
|
|
||||||
import Logger from '/imports/startup/server/logger';
|
|
||||||
|
|
||||||
export default async function clearPolls(meetingId) {
|
|
||||||
if (meetingId) {
|
|
||||||
try {
|
|
||||||
const numberAffected = await Polls.removeAsync({ meetingId });
|
|
||||||
|
|
||||||
if (numberAffected) {
|
|
||||||
Logger.info(`Cleared Polls (${meetingId})`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Logger.info(`Error on clearing Polls (${meetingId}). ${err}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const numberAffected = await Polls.removeAsync({});
|
|
||||||
|
|
||||||
if (numberAffected) {
|
|
||||||
Logger.info('Cleared Polls (all)');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Logger.info(`Error on clearing Polls (all). ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import Polls from '/imports/api/polls';
|
|
||||||
import { check } from 'meteor/check';
|
|
||||||
import Logger from '/imports/startup/server/logger';
|
|
||||||
|
|
||||||
export default async function removePoll(meetingId, id) {
|
|
||||||
check(meetingId, String);
|
|
||||||
check(id, String);
|
|
||||||
|
|
||||||
const selector = {
|
|
||||||
meetingId,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const numberAffected = await Polls.removeAsync(selector);
|
|
||||||
|
|
||||||
if (numberAffected) {
|
|
||||||
Logger.info(`Removed Poll id=${id}`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Logger.error(`Removing Poll from collection: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
import Polls from '/imports/api/polls';
|
|
||||||
import { check } from 'meteor/check';
|
|
||||||
import Logger from '/imports/startup/server/logger';
|
|
||||||
import flat from 'flat';
|
|
||||||
|
|
||||||
export default async function updateVotes(poll, meetingId) {
|
|
||||||
check(meetingId, String);
|
|
||||||
check(poll, Object);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
answers,
|
|
||||||
numResponders,
|
|
||||||
numRespondents,
|
|
||||||
} = poll;
|
|
||||||
|
|
||||||
check(id, String);
|
|
||||||
check(answers, Array);
|
|
||||||
|
|
||||||
check(numResponders, Number);
|
|
||||||
check(numRespondents, Number);
|
|
||||||
|
|
||||||
const selector = {
|
|
||||||
meetingId,
|
|
||||||
id,
|
|
||||||
};
|
|
||||||
|
|
||||||
const modifier = {
|
|
||||||
$set: flat(poll, { safe: true }),
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const numberAffected = await Polls.updateAsync(selector, modifier);
|
|
||||||
|
|
||||||
if (numberAffected) {
|
|
||||||
Logger.info(`Updating Polls collection vote (meetingId: ${meetingId}, pollId: ${id}!)`);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
Logger.error(`Updating Polls collection vote: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,146 +0,0 @@
|
|||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import { check } from 'meteor/check';
|
|
||||||
import Logger from '/imports/startup/server/logger';
|
|
||||||
import Users from '/imports/api/users';
|
|
||||||
import Polls from '/imports/api/polls';
|
|
||||||
import AuthTokenValidation, {
|
|
||||||
ValidationStates,
|
|
||||||
} from '/imports/api/auth-token-validation';
|
|
||||||
import { DDPServer } from 'meteor/ddp-server';
|
|
||||||
import { publicationSafeGuard } from '/imports/api/common/server/helpers';
|
|
||||||
|
|
||||||
Meteor.server.setPublicationStrategy('polls', DDPServer.publicationStrategies.NO_MERGE);
|
|
||||||
|
|
||||||
async function currentPoll(secretPoll) {
|
|
||||||
check(secretPoll, Boolean);
|
|
||||||
const tokenValidation = await AuthTokenValidation.findOneAsync({
|
|
||||||
connectionId: this.connection.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
!tokenValidation
|
|
||||||
|| tokenValidation.validationStatus !== ValidationStates.VALIDATED
|
|
||||||
) {
|
|
||||||
Logger.warn(
|
|
||||||
`Publishing Polls was requested by unauth connection ${this.connection.id}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Polls.find({ meetingId: '' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const { meetingId, userId } = tokenValidation;
|
|
||||||
|
|
||||||
const User = await Users.findOneAsync({ userId, meetingId },
|
|
||||||
{ fields: { role: 1, presenter: 1 } });
|
|
||||||
|
|
||||||
if (!!User && User.presenter) {
|
|
||||||
Logger.debug('Publishing Polls', { meetingId, userId });
|
|
||||||
|
|
||||||
const selector = {
|
|
||||||
meetingId,
|
|
||||||
requester: userId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const options = { fields: {} };
|
|
||||||
|
|
||||||
const hasPoll = await Polls.findOneAsync(selector);
|
|
||||||
|
|
||||||
if ((hasPoll && hasPoll.secretPoll) || secretPoll) {
|
|
||||||
options.fields.responses = 0;
|
|
||||||
selector.secretPoll = true;
|
|
||||||
} else {
|
|
||||||
selector.secretPoll = false;
|
|
||||||
}
|
|
||||||
Mongo.Collection._publishCursor(Polls.find(selector, options), this, 'current-poll');
|
|
||||||
return this.ready();
|
|
||||||
}
|
|
||||||
|
|
||||||
Logger.warn(
|
|
||||||
'Publishing current-poll was requested by non-presenter connection',
|
|
||||||
{ meetingId, userId, connectionId: this.connection.id },
|
|
||||||
);
|
|
||||||
Mongo.Collection._publishCursor(Polls.find({ meetingId: '' }), this, 'current-poll');
|
|
||||||
return this.ready();
|
|
||||||
}
|
|
||||||
|
|
||||||
function publishCurrentPoll(...args) {
|
|
||||||
const boundPolls = currentPoll.bind(this);
|
|
||||||
return boundPolls(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
Meteor.publish('current-poll', publishCurrentPoll);
|
|
||||||
|
|
||||||
async function polls() {
|
|
||||||
const tokenValidation = await AuthTokenValidation.findOneAsync({
|
|
||||||
connectionId: this.connection.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (
|
|
||||||
!tokenValidation
|
|
||||||
|| tokenValidation.validationStatus !== ValidationStates.VALIDATED
|
|
||||||
) {
|
|
||||||
Logger.warn(
|
|
||||||
`Publishing Polls was requested by unauth connection ${this.connection.id}`,
|
|
||||||
);
|
|
||||||
return Polls.find({ meetingId: '' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
fields: {
|
|
||||||
'answers.numVotes': 0,
|
|
||||||
responses: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const noKeyOptions = {
|
|
||||||
fields: {
|
|
||||||
'answers.numVotes': 0,
|
|
||||||
'answers.key': 0,
|
|
||||||
responses: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const { meetingId, userId } = tokenValidation;
|
|
||||||
const User = await Users.findOneAsync({ userId, meetingId },
|
|
||||||
{ fields: { role: 1, presenter: 1 } });
|
|
||||||
|
|
||||||
Logger.debug('Publishing polls', { meetingId, userId });
|
|
||||||
|
|
||||||
const selector = {
|
|
||||||
meetingId,
|
|
||||||
users: userId,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (User) {
|
|
||||||
const poll = await Polls.findOneAsync(selector, noKeyOptions);
|
|
||||||
if (User.presenter || poll?.pollType !== 'R-') {
|
|
||||||
// Monitor this publication and stop it when user is not a presenter anymore
|
|
||||||
// or poll type has changed
|
|
||||||
const comparisonFunc = async () => {
|
|
||||||
const user = await Users.findOneAsync({ userId, meetingId },
|
|
||||||
{ fields: { role: 1, userId: 1 } });
|
|
||||||
const currentPoll = await Polls.findOneAsync(selector, noKeyOptions);
|
|
||||||
|
|
||||||
const condition = user.presenter || currentPoll?.pollType !== 'R-';
|
|
||||||
|
|
||||||
if (!condition) {
|
|
||||||
Logger.info(`conditions aren't filled anymore in publication ${this._name}:
|
|
||||||
user.presenter || currentPoll?.pollType !== 'R-' :${condition}, user.presenter: ${user.presenter} pollType: ${currentPoll?.pollType}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return condition;
|
|
||||||
};
|
|
||||||
publicationSafeGuard(comparisonFunc, this);
|
|
||||||
return Polls.find(selector, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Polls.find(selector, noKeyOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
function publish(...args) {
|
|
||||||
const boundPolls = polls.bind(this);
|
|
||||||
return boundPolls(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
Meteor.publish('polls', publish);
|
|
File diff suppressed because it is too large
Load Diff
@ -5,17 +5,17 @@ import { Meteor } from 'meteor/meteor';
|
|||||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||||
import Header from '/imports/ui/components/common/control-header/component';
|
import Header from '/imports/ui/components/common/control-header/component';
|
||||||
import { useMutation, useSubscription } from '@apollo/client';
|
import { useMutation, useSubscription } from '@apollo/client';
|
||||||
import { Input } from '../../layout/layoutTypes';
|
import { Input } from '../layout/layoutTypes';
|
||||||
import { layoutDispatch, layoutSelectInput } from '../../layout/context';
|
import { layoutDispatch, layoutSelectInput } from '../layout/context';
|
||||||
import { addAlert } from '../../screenreader-alert/service';
|
import { addAlert } from '../screenreader-alert/service';
|
||||||
import { PANELS, ACTIONS } from '../../layout/enums';
|
import { PANELS, ACTIONS } from '../layout/enums';
|
||||||
import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
||||||
import { POLL_CANCEL } from './mutation';
|
import { POLL_CANCEL } from './mutations';
|
||||||
import { GetHasCurrentPresentationResponse, getHasCurrentPresentation } from './queries';
|
import { GetHasCurrentPresentationResponse, getHasCurrentPresentation } from './queries';
|
||||||
import EmptySlideArea from './components/EmptySlideArea';
|
import EmptySlideArea from './components/EmptySlideArea';
|
||||||
import { getSplittedQuestionAndOptions, pollTypes, validateInput } from './service';
|
import { getSplittedQuestionAndOptions, pollTypes, validateInput } from './service';
|
||||||
import Toggle from '/imports/ui/components/common/switch/component';
|
import Toggle from '/imports/ui/components/common/switch/component';
|
||||||
import Styled from '../styles';
|
import Styled from './styles';
|
||||||
import ResponseChoices from './components/ResponseChoices';
|
import ResponseChoices from './components/ResponseChoices';
|
||||||
import ResponseTypes from './components/ResponseTypes';
|
import ResponseTypes from './components/ResponseTypes';
|
||||||
import PollQuestionArea from './components/PollQuestionArea';
|
import PollQuestionArea from './components/PollQuestionArea';
|
@ -0,0 +1,107 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import Styled from '../styles';
|
||||||
|
|
||||||
|
interface DragAndDropPros {
|
||||||
|
MAX_INPUT_CHARS: number;
|
||||||
|
handlePollValuesText: (value: string) => void;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DragAndDrop: React.FC<DragAndDropPros> = (props) => {
|
||||||
|
const { MAX_INPUT_CHARS, handlePollValuesText } = props;
|
||||||
|
const [drag, setDrag] = useState(false);
|
||||||
|
const [pollValueText, setPollText] = useState('');
|
||||||
|
const dropRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
|
const dragCounter = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleDrag = (e: DragEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragIn = (e: DragEvent) => {
|
||||||
|
handleDrag(e);
|
||||||
|
dragCounter.current += 1;
|
||||||
|
if (e.dataTransfer?.items && e.dataTransfer.items.length > 0) {
|
||||||
|
setDrag(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragOut = (e: DragEvent) => {
|
||||||
|
handleDrag(e);
|
||||||
|
dragCounter.current -= 1;
|
||||||
|
if (dragCounter.current > 0) return;
|
||||||
|
setDrag(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = (e: DragEvent) => {
|
||||||
|
handleDrag(e);
|
||||||
|
setDrag(false);
|
||||||
|
if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {
|
||||||
|
setPollValuesFromFile(e.dataTransfer.files[0]);
|
||||||
|
dragCounter.current = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const div = dropRef.current;
|
||||||
|
if (!div) return undefined;
|
||||||
|
div.addEventListener('dragenter', handleDragIn);
|
||||||
|
div.addEventListener('dragleave', handleDragOut);
|
||||||
|
div.addEventListener('dragover', handleDrag);
|
||||||
|
div.addEventListener('drop', handleDrop);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
div.removeEventListener('dragenter', handleDragIn);
|
||||||
|
div.removeEventListener('dragleave', handleDragOut);
|
||||||
|
div.removeEventListener('dragover', handleDrag);
|
||||||
|
div.removeEventListener('drop', handleDrop);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setPollValues = () => {
|
||||||
|
if (pollValueText) {
|
||||||
|
handlePollValuesText(pollValueText);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPollValuesFromFile = (file: File) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async (e) => {
|
||||||
|
const result = e.target?.result;
|
||||||
|
if (!result) return;
|
||||||
|
const text = typeof result === 'string' ? result : String(result);
|
||||||
|
setPollValueText(text);
|
||||||
|
setPollValues();
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPollValueText = (pollText: string) => {
|
||||||
|
const arr = pollText.split('\n');
|
||||||
|
const text = arr.map((line) => (line.length > MAX_INPUT_CHARS ? line.substring(0, MAX_INPUT_CHARS) : line)).join('\n');
|
||||||
|
setPollText(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCleanProps = () => {
|
||||||
|
const cleanProps = { ...props };
|
||||||
|
const propsToDelete = ['MAX_INPUT_CHARS', 'handlePollValuesText'] as const;
|
||||||
|
|
||||||
|
propsToDelete.forEach((prop) => {
|
||||||
|
delete cleanProps[prop as keyof typeof cleanProps];
|
||||||
|
});
|
||||||
|
|
||||||
|
return props as Omit<DragAndDropPros, typeof propsToDelete[number]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.DndTextArea
|
||||||
|
ref={dropRef}
|
||||||
|
active={drag}
|
||||||
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
|
{...getCleanProps()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DragAndDrop;
|
@ -14,9 +14,9 @@ import {
|
|||||||
} from '../queries';
|
} from '../queries';
|
||||||
import logger from '/imports/startup/client/logger';
|
import logger from '/imports/startup/client/logger';
|
||||||
import Settings from '/imports/ui/services/settings';
|
import Settings from '/imports/ui/services/settings';
|
||||||
import { POLL_CANCEL, POLL_PUBLISH_RESULT } from '../mutation';
|
import { POLL_CANCEL, POLL_PUBLISH_RESULT } from '../mutations';
|
||||||
import { layoutDispatch } from '../../../layout/context';
|
import { layoutDispatch } from '../../layout/context';
|
||||||
import { ACTIONS, PANELS } from '../../../layout/enums';
|
import { ACTIONS, PANELS } from '../../layout/enums';
|
||||||
|
|
||||||
const intlMessages = defineMessages({
|
const intlMessages = defineMessages({
|
||||||
usersTitle: {
|
usersTitle: {
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { Meteor } from 'meteor/meteor';
|
import { Meteor } from 'meteor/meteor';
|
||||||
import DraggableTextArea from '/imports/ui/components/poll/dragAndDrop/component';
|
import DraggableTextArea from './DragAndDrop';
|
||||||
import { pollTypes } from '../service';
|
import { pollTypes } from '../service';
|
||||||
import Styled from '../styles';
|
import Styled from '../styles';
|
||||||
|
|
@ -4,7 +4,7 @@ import { Meteor } from 'meteor/meteor';
|
|||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import Styled from '../styles';
|
import Styled from '../styles';
|
||||||
import { pollTypes, checkPollType } from '../service';
|
import { pollTypes, checkPollType } from '../service';
|
||||||
import { POLL_CREATE } from '../mutation';
|
import { POLL_CREATE } from '../mutations';
|
||||||
|
|
||||||
const CHAT_CONFIG = Meteor.settings.public.chat;
|
const CHAT_CONFIG = Meteor.settings.public.chat;
|
||||||
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
|
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_id;
|
@ -1,111 +1,3 @@
|
|||||||
import React, { useContext } from 'react';
|
import PollCreationPanelContainer from './component';
|
||||||
import { withTracker } from 'meteor/react-meteor-data';
|
|
||||||
import Poll from '/imports/ui/components/poll/component';
|
|
||||||
import { Session } from 'meteor/session';
|
|
||||||
import { useMutation } from '@apollo/client';
|
|
||||||
import Service from './service';
|
|
||||||
import Auth from '/imports/ui/services/auth';
|
|
||||||
import { layoutDispatch, layoutSelectInput } from '../layout/context';
|
|
||||||
import { POLL_PUBLISH_RESULT, POLL_CANCEL, POLL_CREATE } from './mutations';
|
|
||||||
import PollCreationPanelContainer from './poll-graphql/component';
|
|
||||||
import { ACTIONS, PANELS } from '../layout/enums';
|
|
||||||
|
|
||||||
const CHAT_CONFIG = window.meetingClientSettings.public.chat;
|
|
||||||
const PUBLIC_CHAT_KEY = CHAT_CONFIG.public_group_id;
|
|
||||||
|
|
||||||
const PollContainer = (props) => {
|
|
||||||
const layoutContextDispatch = layoutDispatch();
|
|
||||||
const handleChatFormsOpen = () => {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const sidebarContent = layoutSelectInput((i) => i.sidebarContent);
|
|
||||||
const { sidebarContentPanel } = sidebarContent;
|
|
||||||
|
|
||||||
const users = [];
|
|
||||||
const usernames = {};
|
|
||||||
|
|
||||||
Object.values(users[Auth.meetingID]).forEach((user) => {
|
|
||||||
usernames[user.userId] = { userId: user.userId, name: user.name };
|
|
||||||
});
|
|
||||||
|
|
||||||
const [pollPublishResult] = useMutation(POLL_PUBLISH_RESULT);
|
|
||||||
const [stopPoll] = useMutation(POLL_CANCEL);
|
|
||||||
const [createPoll] = useMutation(POLL_CREATE);
|
|
||||||
|
|
||||||
const { currentSlideId } = props;
|
|
||||||
|
|
||||||
const startPoll = (pollType, secretPoll, question, isMultipleResponse, answers = []) => {
|
|
||||||
const pollId = currentSlideId || PUBLIC_CHAT_KEY;
|
|
||||||
|
|
||||||
createPoll({
|
|
||||||
variables: {
|
|
||||||
pollType,
|
|
||||||
pollId: `${pollId}/${new Date().getTime()}`,
|
|
||||||
secretPoll,
|
|
||||||
question,
|
|
||||||
isMultipleResponse,
|
|
||||||
answers,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const publishPoll = (pollId) => {
|
|
||||||
pollPublishResult({
|
|
||||||
variables: {
|
|
||||||
pollId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Poll
|
|
||||||
{...{
|
|
||||||
layoutContextDispatch,
|
|
||||||
sidebarContentPanel,
|
|
||||||
publishPoll,
|
|
||||||
stopPoll,
|
|
||||||
startPoll,
|
|
||||||
handleChatFormsOpen,
|
|
||||||
...props,
|
|
||||||
}}
|
|
||||||
usernames={usernames}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
withTracker(({ amIPresenter, currentSlideId }) => {
|
|
||||||
const isPollSecret = Session.get('secretPoll') || false;
|
|
||||||
|
|
||||||
Meteor.subscribe('current-poll', isPollSecret, amIPresenter);
|
|
||||||
|
|
||||||
const { pollTypes } = Service;
|
|
||||||
|
|
||||||
return {
|
|
||||||
isPollSecret,
|
|
||||||
currentSlideId,
|
|
||||||
pollTypes,
|
|
||||||
currentPoll: Service.currentPoll(),
|
|
||||||
isDefaultPoll: Service.isDefaultPoll,
|
|
||||||
checkPollType: Service.checkPollType,
|
|
||||||
resetPollPanel: Session.get('resetPollPanel') || false,
|
|
||||||
pollAnswerIds: Service.pollAnswerIds,
|
|
||||||
isMeteorConnected: Meteor.status().connected,
|
|
||||||
validateInput: Service.validateInput,
|
|
||||||
removeEmptyLineSpaces: Service.removeEmptyLineSpaces,
|
|
||||||
getSplittedQuestionAndOptions: Service.getSplittedQuestionAndOptions,
|
|
||||||
};
|
|
||||||
})(PollContainer);
|
|
||||||
|
|
||||||
export default PollCreationPanelContainer;
|
export default PollCreationPanelContainer;
|
||||||
|
@ -1,122 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Styled from './styles';
|
|
||||||
|
|
||||||
// src: https://medium.com/@650egor/simple-drag-and-drop-file-upload-in-react-2cb409d88929
|
|
||||||
|
|
||||||
class DragAndDrop extends Component {
|
|
||||||
static handleDrag(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
drag: false,
|
|
||||||
pollValueText: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
this.dropRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.dragCounter = 0;
|
|
||||||
const div = this.dropRef.current;
|
|
||||||
div.addEventListener('dragenter', e => this.handleDragIn(e));
|
|
||||||
div.addEventListener('dragleave', e => this.handleDragOut(e));
|
|
||||||
div.addEventListener('dragover', e => DragAndDrop.handleDrag(e));
|
|
||||||
div.addEventListener('drop', e => this.handleDrop(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
const div = this.dropRef.current;
|
|
||||||
div.removeEventListener('dragenter', e => this.handleDragIn(e));
|
|
||||||
div.removeEventListener('dragleave', e => this.handleDragOut(e));
|
|
||||||
div.removeEventListener('dragover', e => DragAndDrop.handleDrag(e));
|
|
||||||
div.removeEventListener('drop', e => this.handleDrop(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
setPollValues() {
|
|
||||||
const { pollValueText } = this.state;
|
|
||||||
const { handlePollValuesText } = this.props;
|
|
||||||
if (pollValueText) {
|
|
||||||
handlePollValuesText(pollValueText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPollValuesFromFile(file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = async (e) => {
|
|
||||||
const text = e.target.result;
|
|
||||||
this.setPollValueText(text);
|
|
||||||
this.setPollValues();
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPollValueText(pollText) {
|
|
||||||
const { MAX_INPUT_CHARS } = this.props;
|
|
||||||
const arr = pollText.split('\n');
|
|
||||||
const text = arr.map(line => (line.length > MAX_INPUT_CHARS ? line.substring(0, MAX_INPUT_CHARS) : line)).join('\n');
|
|
||||||
this.setState({ pollValueText: text });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleTextInput(e) {
|
|
||||||
this.setPollValueText(e.target.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDragIn(e) {
|
|
||||||
DragAndDrop.handleDrag(e);
|
|
||||||
this.dragCounter += 1;
|
|
||||||
if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {
|
|
||||||
this.setState({ drag: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDragOut(e) {
|
|
||||||
DragAndDrop.handleDrag(e);
|
|
||||||
this.dragCounter -= 1;
|
|
||||||
if (this.dragCounter > 0) return;
|
|
||||||
this.setState({ drag: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleDrop(e) {
|
|
||||||
DragAndDrop.handleDrag(e);
|
|
||||||
this.setState({ drag: false });
|
|
||||||
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
|
|
||||||
this.setPollValuesFromFile(e.dataTransfer.files[0]);
|
|
||||||
this.dragCounter = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getCleanProps() {
|
|
||||||
const props = Object.assign({}, this.props);
|
|
||||||
const propsToDelete = ['MAX_INPUT_CHARS', 'handlePollValuesText'];
|
|
||||||
|
|
||||||
propsToDelete.forEach((prop) => {
|
|
||||||
delete props[prop];
|
|
||||||
});
|
|
||||||
|
|
||||||
return props;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { drag } = this.state;
|
|
||||||
return (
|
|
||||||
<Styled.DndTextArea
|
|
||||||
ref={this.dropRef}
|
|
||||||
active={drag}
|
|
||||||
{...this.getCleanProps()}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DragAndDrop.propTypes = {
|
|
||||||
MAX_INPUT_CHARS: PropTypes.number.isRequired,
|
|
||||||
handlePollValuesText: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DragAndDrop;
|
|
@ -1,19 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
import {
|
|
||||||
colorGrayLighter,
|
|
||||||
colorWhite,
|
|
||||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
|
||||||
|
|
||||||
const DndTextArea = styled.textarea`
|
|
||||||
${({ active }) => active && `
|
|
||||||
background: ${colorGrayLighter};
|
|
||||||
`}
|
|
||||||
|
|
||||||
${({ active }) => !active && `
|
|
||||||
background: ${colorWhite};
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
DndTextArea,
|
|
||||||
};
|
|
@ -1,294 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
|
|
||||||
import { Session } from 'meteor/session';
|
|
||||||
import Styled from './styles';
|
|
||||||
import Service from './service';
|
|
||||||
import Settings from '/imports/ui/services/settings';
|
|
||||||
import { uniqueId } from '/imports/utils/string-utils';
|
|
||||||
|
|
||||||
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',
|
|
||||||
},
|
|
||||||
noAnswersLabel: {
|
|
||||||
id: 'app.poll.noAnswerWarning',
|
|
||||||
description: 'label shown when no answers have been received',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const getResponseString = (obj) => {
|
|
||||||
const { children } = obj.props;
|
|
||||||
if (typeof children !== 'string') {
|
|
||||||
return getResponseString(children[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return children;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LiveResult extends PureComponent {
|
|
||||||
static getDerivedStateFromProps(nextProps) {
|
|
||||||
const {
|
|
||||||
currentPoll, intl, pollAnswerIds, usernames, isDefaultPoll,
|
|
||||||
} = nextProps;
|
|
||||||
|
|
||||||
if (!currentPoll) return null;
|
|
||||||
|
|
||||||
const {
|
|
||||||
answers, responses, users, numResponders, pollType
|
|
||||||
} = currentPoll;
|
|
||||||
|
|
||||||
const defaultPoll = isDefaultPoll(pollType);
|
|
||||||
|
|
||||||
const currentPollQuestion = (currentPoll.question) ? currentPoll.question : '';
|
|
||||||
|
|
||||||
let userAnswers = responses
|
|
||||||
? [...users, ...responses.map(u => u.userId)]
|
|
||||||
: [...users];
|
|
||||||
|
|
||||||
userAnswers = userAnswers.map(id => usernames[id])
|
|
||||||
.map((user) => {
|
|
||||||
let answer = '';
|
|
||||||
|
|
||||||
if (responses) {
|
|
||||||
const response = responses.find(r => r.userId === user.userId);
|
|
||||||
if (response) {
|
|
||||||
const formattedAnswers = [];
|
|
||||||
response.answerIds.forEach((answerId) => {
|
|
||||||
const formattedMessageIndex = answers[answerId]?.key?.toLowerCase();
|
|
||||||
const formattedAnswer = defaultPoll && pollAnswerIds[formattedMessageIndex]
|
|
||||||
? intl.formatMessage(pollAnswerIds[formattedMessageIndex])
|
|
||||||
: answers[answerId].key;
|
|
||||||
|
|
||||||
formattedAnswers.push(formattedAnswer);
|
|
||||||
});
|
|
||||||
answer = formattedAnswers.join(', ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: user.name,
|
|
||||||
answer,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.sort(Service.sortUsers)
|
|
||||||
.reduce((acc, user) => {
|
|
||||||
return ([
|
|
||||||
...acc,
|
|
||||||
(
|
|
||||||
<tr key={uniqueId('stats-')}>
|
|
||||||
<Styled.ResultLeft>{user.name}</Styled.ResultLeft>
|
|
||||||
<Styled.ResultRight data-test="receivedAnswer">
|
|
||||||
{user.answer}
|
|
||||||
</Styled.ResultRight>
|
|
||||||
</tr>
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const pollStats = [];
|
|
||||||
|
|
||||||
answers.reduce(caseInsensitiveReducer, []).map((obj) => {
|
|
||||||
const formattedMessageIndex = obj?.key?.toLowerCase();
|
|
||||||
const pct = Math.round(obj.numVotes / numResponders * 100);
|
|
||||||
const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`;
|
|
||||||
|
|
||||||
const calculatedWidth = {
|
|
||||||
width: pctFotmatted,
|
|
||||||
};
|
|
||||||
|
|
||||||
return pollStats.push(
|
|
||||||
<Styled.Main key={uniqueId('stats-')}>
|
|
||||||
<Styled.Left>
|
|
||||||
{
|
|
||||||
defaultPoll && pollAnswerIds[formattedMessageIndex]
|
|
||||||
? intl.formatMessage(pollAnswerIds[formattedMessageIndex])
|
|
||||||
: obj.key
|
|
||||||
}
|
|
||||||
</Styled.Left>
|
|
||||||
<Styled.Center>
|
|
||||||
<Styled.BarShade style={calculatedWidth} />
|
|
||||||
<Styled.BarVal data-test="numberOfVotes">{obj.numVotes || 0}</Styled.BarVal>
|
|
||||||
</Styled.Center>
|
|
||||||
<Styled.Right>
|
|
||||||
{pctFotmatted}
|
|
||||||
</Styled.Right>
|
|
||||||
</Styled.Main>,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
userAnswers,
|
|
||||||
pollStats,
|
|
||||||
currentPollQuestion,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
userAnswers: null,
|
|
||||||
pollStats: null,
|
|
||||||
currentPollQuestion: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isMeteorConnected,
|
|
||||||
intl,
|
|
||||||
stopPoll,
|
|
||||||
handleBackClick,
|
|
||||||
currentPoll,
|
|
||||||
publishPoll,
|
|
||||||
handleChatFormsOpen,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const { userAnswers, pollStats, currentPollQuestion } = this.state;
|
|
||||||
const { animations } = Settings.application;
|
|
||||||
|
|
||||||
let waiting;
|
|
||||||
let userCount = 0;
|
|
||||||
let respondedCount = 0;
|
|
||||||
|
|
||||||
if (userAnswers) {
|
|
||||||
userCount = userAnswers.length;
|
|
||||||
userAnswers.map((user) => {
|
|
||||||
const response = getResponseString(user);
|
|
||||||
if (response === '') return user;
|
|
||||||
respondedCount += 1;
|
|
||||||
return user;
|
|
||||||
});
|
|
||||||
|
|
||||||
waiting = respondedCount !== userAnswers.length && currentPoll;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Styled.Stats>
|
|
||||||
{currentPollQuestion ? <Styled.Title data-test="currentPollQuestion">{currentPollQuestion}</Styled.Title> : null}
|
|
||||||
<Styled.Status>
|
|
||||||
{waiting
|
|
||||||
? (
|
|
||||||
<span>
|
|
||||||
{`${intl.formatMessage(intlMessages.waitingLabel, {
|
|
||||||
0: respondedCount,
|
|
||||||
1: userCount,
|
|
||||||
})} `}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
: <span>{intl.formatMessage(intlMessages.doneLabel)}</span>}
|
|
||||||
{waiting
|
|
||||||
? <Styled.ConnectingAnimation animations={animations}/> : null}
|
|
||||||
</Styled.Status>
|
|
||||||
{pollStats}
|
|
||||||
</Styled.Stats>
|
|
||||||
{currentPoll && currentPoll.answers.length >= 0
|
|
||||||
? (
|
|
||||||
<Styled.ButtonsActions>
|
|
||||||
<Styled.PublishButton
|
|
||||||
disabled={!isMeteorConnected || !currentPoll.numResponders}
|
|
||||||
onClick={() => {
|
|
||||||
Session.set('pollInitiated', false);
|
|
||||||
publishPoll(currentPoll?.id);
|
|
||||||
stopPoll();
|
|
||||||
handleChatFormsOpen();
|
|
||||||
}}
|
|
||||||
label={intl.formatMessage(intlMessages.publishLabel)}
|
|
||||||
data-test="publishPollingLabel"
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
<Styled.CancelButton
|
|
||||||
disabled={!isMeteorConnected}
|
|
||||||
onClick={() => {
|
|
||||||
Session.set('pollInitiated', false);
|
|
||||||
Session.set('resetPollPanel', true);
|
|
||||||
stopPoll();
|
|
||||||
}}
|
|
||||||
label={intl.formatMessage(intlMessages.cancelPollLabel)}
|
|
||||||
data-test="cancelPollLabel"
|
|
||||||
/>
|
|
||||||
</Styled.ButtonsActions>
|
|
||||||
) : (
|
|
||||||
<Styled.LiveResultButton
|
|
||||||
disabled={!isMeteorConnected}
|
|
||||||
onClick={() => {
|
|
||||||
handleBackClick();
|
|
||||||
}}
|
|
||||||
label={intl.formatMessage(intlMessages.backLabel)}
|
|
||||||
color="primary"
|
|
||||||
data-test="restartPoll"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{currentPoll && !currentPoll.numResponders
|
|
||||||
&& intl.formatMessage(intlMessages.noAnswersLabel)}
|
|
||||||
<Styled.Separator />
|
|
||||||
{ currentPoll && !currentPoll.secretPoll
|
|
||||||
? (
|
|
||||||
<table>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<Styled.THeading>{intl.formatMessage(intlMessages.usersTitle)}</Styled.THeading>
|
|
||||||
<Styled.THeading>{intl.formatMessage(intlMessages.responsesTitle)}</Styled.THeading>
|
|
||||||
</tr>
|
|
||||||
{userAnswers}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
) : (
|
|
||||||
currentPoll ? (<div>{intl.formatMessage(intlMessages.secretPollLabel)}</div>) : null
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(LiveResult);
|
|
||||||
|
|
||||||
LiveResult.defaultProps = { currentPoll: null };
|
|
||||||
|
|
||||||
LiveResult.propTypes = {
|
|
||||||
intl: PropTypes.shape({
|
|
||||||
formatMessage: PropTypes.func.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
currentPoll: PropTypes.oneOfType([
|
|
||||||
PropTypes.arrayOf(Object),
|
|
||||||
PropTypes.shape({
|
|
||||||
answers: PropTypes.arrayOf(PropTypes.object),
|
|
||||||
users: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
handleChatFormsOpen: PropTypes.func.isRequired,
|
|
||||||
stopPoll: PropTypes.func.isRequired,
|
|
||||||
handleBackClick: PropTypes.func.isRequired,
|
|
||||||
};
|
|
@ -1,35 +0,0 @@
|
|||||||
const sortUsers = (a, b) => {
|
|
||||||
const sortByResponse = (a, b) => {
|
|
||||||
const DEFAULT_CHAR = '-';
|
|
||||||
const _a = a.answer.toLowerCase();
|
|
||||||
const _b = b.answer.toLowerCase();
|
|
||||||
const isDefault = (_a === DEFAULT_CHAR || _b === DEFAULT_CHAR);
|
|
||||||
|
|
||||||
if (_a < _b || isDefault) {
|
|
||||||
return -1;
|
|
||||||
} if (_a > _b) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortByName = (a, b) => {
|
|
||||||
const _a = a.name.toLowerCase();
|
|
||||||
const _b = b.name.toLowerCase();
|
|
||||||
|
|
||||||
if (_a < _b) {
|
|
||||||
return -1;
|
|
||||||
} if (_a > _b) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
let sort = sortByResponse(a, b);
|
|
||||||
if (sort === 0) sort = sortByName(a, b);
|
|
||||||
return sort;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
sortUsers,
|
|
||||||
};
|
|
@ -1,229 +0,0 @@
|
|||||||
import styled, { css, keyframes } from 'styled-components';
|
|
||||||
import {
|
|
||||||
colorGrayLightest,
|
|
||||||
colorText,
|
|
||||||
colorGrayLighter,
|
|
||||||
pollStatsBorderColor,
|
|
||||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
|
||||||
import {
|
|
||||||
smPaddingX,
|
|
||||||
smPaddingY,
|
|
||||||
mdPaddingX,
|
|
||||||
pollStatsElementWidth,
|
|
||||||
pollSmMargin,
|
|
||||||
pollResultWidth,
|
|
||||||
borderSizeLarge,
|
|
||||||
} from '/imports/ui/stylesheets/styled-components/general';
|
|
||||||
import { fontSizeBase } from '/imports/ui/stylesheets/styled-components/typography';
|
|
||||||
import Button from '/imports/ui/components/common/button/component';
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const ConnectingAnimation = styled.span`
|
|
||||||
&: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;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PublishButton = styled(Button)`
|
|
||||||
width: 48%;
|
|
||||||
margin-bottom: ${smPaddingY};
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CancelButton = styled(PublishButton)``;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
ResultLeft,
|
|
||||||
ResultRight,
|
|
||||||
Main,
|
|
||||||
Left,
|
|
||||||
Center,
|
|
||||||
Right,
|
|
||||||
BarShade,
|
|
||||||
BarVal,
|
|
||||||
Stats,
|
|
||||||
Title,
|
|
||||||
Status,
|
|
||||||
ConnectingAnimation,
|
|
||||||
ButtonsActions,
|
|
||||||
PublishButton,
|
|
||||||
CancelButton,
|
|
||||||
LiveResultButton,
|
|
||||||
Separator,
|
|
||||||
THeading,
|
|
||||||
};
|
|
@ -1,54 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
@ -1,118 +0,0 @@
|
|||||||
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,
|
|
||||||
};
|
|
@ -1,21 +1,31 @@
|
|||||||
import Auth from '/imports/ui/services/auth';
|
|
||||||
import { CurrentPoll } from '/imports/api/polls';
|
|
||||||
import { escapeHtml } from '/imports/utils/string-utils';
|
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
|
import { escapeHtml } from '/imports/utils/string-utils';
|
||||||
|
|
||||||
const POLL_AVATAR_COLOR = '#3B48A9';
|
const POLL_AVATAR_COLOR = '#3B48A9';
|
||||||
const MAX_POLL_RESULT_BARS = 10;
|
const MAX_POLL_RESULT_BARS = 10;
|
||||||
const MAX_POLL_RESULT_KEY_LENGTH = 30;
|
const MAX_POLL_RESULT_KEY_LENGTH = 30;
|
||||||
const POLL_BAR_CHAR = '\u220E';
|
const POLL_BAR_CHAR = '\u220E';
|
||||||
|
|
||||||
// 'YN' = Yes,No
|
interface PollResultData {
|
||||||
// 'YNA' = Yes,No,Abstention
|
id: string;
|
||||||
// 'TF' = True,False
|
answers: {
|
||||||
// 'A-2' = A,B
|
id: number;
|
||||||
// 'A-3' = A,B,C
|
key: string;
|
||||||
// 'A-4' = A,B,C,D
|
numVotes: number;
|
||||||
// 'A-5' = A,B,C,D,E
|
}[];
|
||||||
const pollTypes = {
|
numRespondents: number;
|
||||||
|
numResponders: number;
|
||||||
|
questionText: string;
|
||||||
|
questionType: string;
|
||||||
|
type: string;
|
||||||
|
whiteboardId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Intl {
|
||||||
|
formatMessage: (descriptor: { id: string; description: string }) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const pollTypes = {
|
||||||
YesNo: 'YN',
|
YesNo: 'YN',
|
||||||
YesNoAbstention: 'YNA',
|
YesNoAbstention: 'YNA',
|
||||||
TrueFalse: 'TF',
|
TrueFalse: 'TF',
|
||||||
@ -82,7 +92,7 @@ const intlMessages = defineMessages({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const getUsedLabels = (listOfAnswers, possibleLabels) => listOfAnswers.map(
|
const getUsedLabels = (listOfAnswers: PollResultData['answers'], possibleLabels: string[]) => listOfAnswers.map(
|
||||||
(answer) => {
|
(answer) => {
|
||||||
if (answer.key.length >= 2) {
|
if (answer.key.length >= 2) {
|
||||||
const formattedLabel = answer.key.slice(0, 2).toUpperCase();
|
const formattedLabel = answer.key.slice(0, 2).toUpperCase();
|
||||||
@ -94,7 +104,7 @@ const getUsedLabels = (listOfAnswers, possibleLabels) => listOfAnswers.map(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const getFormattedAnswerValue = (answerText) => {
|
const getFormattedAnswerValue = (answerText: string) => {
|
||||||
// In generatePossibleLabels there is a check to see if the
|
// In generatePossibleLabels there is a check to see if the
|
||||||
// answer's length is greater than 2
|
// answer's length is greater than 2
|
||||||
const newText = answerText.slice(2).trim();
|
const newText = answerText.slice(2).trim();
|
||||||
@ -102,9 +112,9 @@ const getFormattedAnswerValue = (answerText) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generateAlphabetList = () => Array.from(Array(26))
|
const generateAlphabetList = () => Array.from(Array(26))
|
||||||
.map((e, i) => i + 65).map((x) => String.fromCharCode(x));
|
.map((_, i) => i + 65).map((x) => String.fromCharCode(x));
|
||||||
|
|
||||||
const generatePossibleLabels = (alphabetCharacters) => {
|
const generatePossibleLabels = (alphabetCharacters: string[]) => {
|
||||||
// Remove the Letter from the beginning and the following sign, if any, like so:
|
// Remove the Letter from the beginning and the following sign, if any, like so:
|
||||||
// "A- the answer is" -> Remove "A-" -> "the answer is"
|
// "A- the answer is" -> Remove "A-" -> "the answer is"
|
||||||
const listOfForbiddenSignsToStart = ['.', ':', '-'];
|
const listOfForbiddenSignsToStart = ['.', ':', '-'];
|
||||||
@ -118,7 +128,7 @@ const generatePossibleLabels = (alphabetCharacters) => {
|
|||||||
return possibleLabels;
|
return possibleLabels;
|
||||||
};
|
};
|
||||||
|
|
||||||
const truncate = (text, length) => {
|
const truncate = (text: string, length: number) => {
|
||||||
let resultText = text;
|
let resultText = text;
|
||||||
if (resultText.length < length) {
|
if (resultText.length < length) {
|
||||||
const diff = length - resultText.length;
|
const diff = length - resultText.length;
|
||||||
@ -130,7 +140,7 @@ const truncate = (text, length) => {
|
|||||||
return resultText;
|
return resultText;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => {
|
const getPollResultsText = (isDefaultPoll: boolean, answers: PollResultData['answers'], numRespondents: number, intl: Intl) => {
|
||||||
let responded = 0;
|
let responded = 0;
|
||||||
let resultString = '';
|
let resultString = '';
|
||||||
let optionsString = '';
|
let optionsString = '';
|
||||||
@ -160,16 +170,16 @@ const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => {
|
|||||||
const pctBars = POLL_BAR_CHAR.repeat((pct * MAX_POLL_RESULT_BARS) / 100);
|
const pctBars = POLL_BAR_CHAR.repeat((pct * MAX_POLL_RESULT_BARS) / 100);
|
||||||
const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`;
|
const pctFotmatted = `${Number.isNaN(pct) ? 0 : pct}%`;
|
||||||
if (isDefaultPoll) {
|
if (isDefaultPoll) {
|
||||||
let translatedKey = pollAnswerIds[item.key.toLowerCase()]
|
let translatedKey = pollAnswerIds[item.key.toLowerCase() as keyof typeof pollAnswerIds]
|
||||||
? intl.formatMessage(pollAnswerIds[item.key.toLowerCase()])
|
? intl.formatMessage(pollAnswerIds[item.key.toLowerCase() as keyof typeof pollAnswerIds])
|
||||||
: item.key;
|
: item.key;
|
||||||
translatedKey = truncate(translatedKey, longestKeyLength);
|
translatedKey = truncate(translatedKey, longestKeyLength);
|
||||||
resultString += `${translatedKey}: ${item.numVotes || 0} ${pctBars}${POLL_BAR_CHAR} ${pctFotmatted}\n`;
|
resultString += `${translatedKey}: ${item.numVotes || 0} ${pctBars}${POLL_BAR_CHAR} ${pctFotmatted}\n`;
|
||||||
} else {
|
} else {
|
||||||
if (isPollAnswerMatchFormat) {
|
if (isPollAnswerMatchFormat) {
|
||||||
resultString += `${pollAnswerMatchLabeledFormat[index][0]}`;
|
resultString += `${pollAnswerMatchLabeledFormat[index]?.[0]}`;
|
||||||
const formattedAnswerValue = getFormattedAnswerValue(item.key);
|
const formattedAnswerValue = getFormattedAnswerValue(item.key);
|
||||||
optionsString += `${pollAnswerMatchLabeledFormat[index][0]}: ${formattedAnswerValue}\n`;
|
optionsString += `${pollAnswerMatchLabeledFormat[index]?.[0]}: ${formattedAnswerValue}\n`;
|
||||||
} else {
|
} else {
|
||||||
let { key } = item;
|
let { key } = item;
|
||||||
key = truncate(key, longestKeyLength);
|
key = truncate(key, longestKeyLength);
|
||||||
@ -182,13 +192,10 @@ const getPollResultsText = (isDefaultPoll, answers, numRespondents, intl) => {
|
|||||||
return { resultString, optionsString };
|
return { resultString, optionsString };
|
||||||
};
|
};
|
||||||
|
|
||||||
const isDefaultPoll = (pollType) => pollType !== pollTypes.Custom
|
const getPollResultString = (pollResultData: PollResultData, intl: Intl) => {
|
||||||
&& pollType !== pollTypes.Response;
|
const formatBoldBlack = (s: string) => s.bold().fontcolor('black');
|
||||||
|
|
||||||
const getPollResultString = (pollResultData, intl) => {
|
const sanitize = (value: string) => escapeHtml(value);
|
||||||
const formatBoldBlack = (s) => s.bold().fontcolor('black');
|
|
||||||
|
|
||||||
const sanitize = (value) => escapeHtml(value);
|
|
||||||
|
|
||||||
const { answers, numRespondents, questionType } = pollResultData;
|
const { answers, numRespondents, questionType } = pollResultData;
|
||||||
const isDefault = isDefaultPoll(questionType);
|
const isDefault = isDefaultPoll(questionType);
|
||||||
@ -215,39 +222,72 @@ const getPollResultString = (pollResultData, intl) => {
|
|||||||
return pollText;
|
return pollText;
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchYesNoPoll = (yesValue, noValue, contentString) => {
|
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 ynPollString = `(${yesValue}\\s*\\/\\s*${noValue})|(${noValue}\\s*\\/\\s*${yesValue})`;
|
||||||
const ynOptionsRegex = new RegExp(ynPollString, 'gi');
|
const ynOptionsRegex = new RegExp(ynPollString, 'gi');
|
||||||
const ynPoll = contentString.replace(/\n/g, '').match(ynOptionsRegex) || [];
|
const ynPoll = contentString.replace(/\n/g, '').match(ynOptionsRegex) || [];
|
||||||
return ynPoll;
|
return ynPoll;
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchYesNoAbstentionPoll = (yesValue, noValue, abstentionValue, contentString) => {
|
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 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 ynaOptionsRegex = new RegExp(ynaPollString, 'gi');
|
||||||
const ynaPoll = contentString.replace(/\n/g, '').match(ynaOptionsRegex) || [];
|
const ynaPoll = contentString.replace(/\n/g, '').match(ynaOptionsRegex) || [];
|
||||||
return ynaPoll;
|
return ynaPoll;
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchTrueFalsePoll = (trueValue, falseValue, contentString) => {
|
const matchTrueFalsePoll = (trueValue:string, falseValue:string, contentString:string) => {
|
||||||
const tfPollString = `(${trueValue}\\s*\\/\\s*${falseValue})|(${falseValue}\\s*\\/\\s*${trueValue})`;
|
const tfPollString = `(${trueValue}\\s*\\/\\s*${falseValue})|(${falseValue}\\s*\\/\\s*${trueValue})`;
|
||||||
const tgOptionsRegex = new RegExp(tfPollString, 'gi');
|
const tgOptionsRegex = new RegExp(tfPollString, 'gi');
|
||||||
const tfPoll = contentString.match(tgOptionsRegex) || [];
|
const tfPoll = contentString.match(tgOptionsRegex) || [];
|
||||||
return tfPoll;
|
return tfPoll;
|
||||||
};
|
};
|
||||||
|
|
||||||
const checkPollType = (
|
export const checkPollType = (
|
||||||
type,
|
type: string | null,
|
||||||
optList,
|
optList: { val: string }[],
|
||||||
yesValue,
|
yesValue: string,
|
||||||
noValue,
|
noValue: string,
|
||||||
abstentionValue,
|
abstentionValue: string,
|
||||||
trueValue,
|
trueValue: string,
|
||||||
falseValue,
|
falseValue: string,
|
||||||
) => {
|
) => {
|
||||||
|
/* eslint no-underscore-dangle: "off" */
|
||||||
let _type = type;
|
let _type = type;
|
||||||
let pollString = '';
|
let pollString = '';
|
||||||
let defaultMatch = null;
|
let defaultMatch: RegExpMatchArray | [] | null = null;
|
||||||
let isDefault = null;
|
let isDefault = null;
|
||||||
|
|
||||||
switch (_type) {
|
switch (_type) {
|
||||||
@ -255,22 +295,22 @@ const checkPollType = (
|
|||||||
pollString = optList.map((x) => x.val.toUpperCase()).sort().join('');
|
pollString = optList.map((x) => x.val.toUpperCase()).sort().join('');
|
||||||
defaultMatch = pollString.match(/^(ABCDEF)|(ABCDE)|(ABCD)|(ABC)|(AB)$/gi);
|
defaultMatch = pollString.match(/^(ABCDEF)|(ABCDE)|(ABCD)|(ABC)|(AB)$/gi);
|
||||||
isDefault = defaultMatch && pollString.length === defaultMatch[0].length;
|
isDefault = defaultMatch && pollString.length === defaultMatch[0].length;
|
||||||
_type = isDefault ? `${_type}${defaultMatch[0].length}` : pollTypes.Custom;
|
_type = isDefault && Array.isArray(defaultMatch) ? `${_type}${defaultMatch[0].length}` : pollTypes.Custom;
|
||||||
break;
|
break;
|
||||||
case pollTypes.TrueFalse:
|
case pollTypes.TrueFalse:
|
||||||
pollString = optList.map((x) => x.val).join('/');
|
pollString = optList.map((x) => x.val).join('/');
|
||||||
defaultMatch = matchTrueFalsePoll(trueValue, falseValue, pollString);
|
defaultMatch = matchTrueFalsePoll(trueValue, falseValue, pollString);
|
||||||
isDefault = defaultMatch.length > 0 && pollString.length === defaultMatch[0].length;
|
isDefault = defaultMatch.length > 0 && pollString.length === (defaultMatch[0]?.length);
|
||||||
if (!isDefault) _type = pollTypes.Custom;
|
if (!isDefault) _type = pollTypes.Custom;
|
||||||
break;
|
break;
|
||||||
case pollTypes.YesNoAbstention:
|
case pollTypes.YesNoAbstention:
|
||||||
pollString = optList.map((x) => x.val).join('/');
|
pollString = optList.map((x) => x.val).join('/');
|
||||||
defaultMatch = matchYesNoAbstentionPoll(yesValue, noValue, abstentionValue, pollString);
|
defaultMatch = matchYesNoAbstentionPoll(yesValue, noValue, abstentionValue, pollString);
|
||||||
isDefault = defaultMatch.length > 0 && pollString.length === defaultMatch[0].length;
|
isDefault = Array.isArray(defaultMatch) && defaultMatch.length > 0 && pollString.length === defaultMatch[0]?.length;
|
||||||
if (!isDefault) {
|
if (!isDefault) {
|
||||||
// also try to match only yes/no
|
// also try to match only yes/no
|
||||||
defaultMatch = matchYesNoPoll(yesValue, noValue, pollString);
|
defaultMatch = matchYesNoPoll(yesValue, noValue, pollString);
|
||||||
isDefault = defaultMatch.length > 0 && pollString.length === defaultMatch[0].length;
|
isDefault = defaultMatch.length > 0 && pollString.length === defaultMatch[0]?.length;
|
||||||
_type = isDefault ? pollTypes.YesNo : _type = pollTypes.Custom;
|
_type = isDefault ? pollTypes.YesNo : _type = pollTypes.Custom;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -280,57 +320,18 @@ const checkPollType = (
|
|||||||
return _type;
|
return _type;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {String} input
|
|
||||||
*/
|
|
||||||
const validateInput = (input) => {
|
|
||||||
let _input = input;
|
|
||||||
while (/^\s/.test(_input)) _input = _input.substring(1);
|
|
||||||
return _input;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {String} input
|
|
||||||
*/
|
|
||||||
const removeEmptyLineSpaces = (input) => {
|
|
||||||
const filteredInput = input.split('\n').filter((val) => val.trim() !== '');
|
|
||||||
return filteredInput;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {String|Array} questionAndOptions
|
|
||||||
*/
|
|
||||||
const getSplittedQuestionAndOptions = (questionAndOptions) => {
|
|
||||||
const inputList = Array.isArray(questionAndOptions)
|
|
||||||
? questionAndOptions
|
|
||||||
: questionAndOptions.split('\n').filter((val) => val !== '');
|
|
||||||
const splittedQuestion = inputList.length > 0 ? inputList[0] : questionAndOptions;
|
|
||||||
const optionsList = inputList.slice(1);
|
|
||||||
|
|
||||||
optionsList.forEach((val, i) => { optionsList[i] = { val }; });
|
|
||||||
|
|
||||||
return {
|
|
||||||
splittedQuestion,
|
|
||||||
optionsList,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
pollTypes,
|
pollTypes,
|
||||||
currentPoll: () => CurrentPoll.findOne({ meetingId: Auth.meetingID }),
|
validateInput,
|
||||||
|
getSplittedQuestionAndOptions,
|
||||||
|
removeEmptyLineSpaces,
|
||||||
|
isDefaultPoll,
|
||||||
pollAnswerIds,
|
pollAnswerIds,
|
||||||
POLL_AVATAR_COLOR,
|
POLL_AVATAR_COLOR,
|
||||||
isDefaultPoll,
|
|
||||||
getPollResultString,
|
getPollResultString,
|
||||||
matchYesNoPoll,
|
matchYesNoPoll,
|
||||||
matchYesNoAbstentionPoll,
|
matchYesNoAbstentionPoll,
|
||||||
matchTrueFalsePoll,
|
matchTrueFalsePoll,
|
||||||
checkPollType,
|
checkPollType,
|
||||||
validateInput,
|
|
||||||
removeEmptyLineSpaces,
|
|
||||||
getSplittedQuestionAndOptions,
|
|
||||||
POLL_BAR_CHAR,
|
POLL_BAR_CHAR,
|
||||||
};
|
};
|
@ -1,340 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
import Button from '/imports/ui/components/common/button/component';
|
|
||||||
import {
|
|
||||||
jumboPaddingY,
|
|
||||||
smPaddingX,
|
|
||||||
smPaddingY,
|
|
||||||
lgPaddingX,
|
|
||||||
borderRadius,
|
|
||||||
borderSize,
|
|
||||||
pollInputHeight,
|
|
||||||
pollSmMargin,
|
|
||||||
pollMdMargin,
|
|
||||||
} from '/imports/ui/stylesheets/styled-components/general';
|
|
||||||
import {
|
|
||||||
colorText,
|
|
||||||
colorBlueLight,
|
|
||||||
colorGrayLight,
|
|
||||||
colorGrayLighter,
|
|
||||||
colorGrayLightest,
|
|
||||||
colorDanger,
|
|
||||||
colorWarning,
|
|
||||||
colorHeading,
|
|
||||||
colorPrimary,
|
|
||||||
colorGrayDark,
|
|
||||||
} 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};
|
|
||||||
`;
|
|
||||||
|
|
||||||
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};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PollQuestionArea = styled.textarea`
|
|
||||||
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%;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
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};
|
|
||||||
`;
|
|
||||||
|
|
||||||
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;
|
|
||||||
`;
|
|
||||||
|
|
||||||
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;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PollButton = styled(Button)``;
|
|
||||||
|
|
||||||
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%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
@ -580,6 +580,16 @@ const THeading = styled.th`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const DndTextArea = styled.textarea<{ active: boolean }>`
|
||||||
|
${({ active }) => active && `
|
||||||
|
background: ${colorGrayLighter};
|
||||||
|
`}
|
||||||
|
|
||||||
|
${({ active }) => !active && `
|
||||||
|
background: ${colorWhite};
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
ToggleLabel,
|
ToggleLabel,
|
||||||
PollOptionInput,
|
PollOptionInput,
|
||||||
@ -631,4 +641,5 @@ export default {
|
|||||||
LiveResultButton,
|
LiveResultButton,
|
||||||
Separator,
|
Separator,
|
||||||
THeading,
|
THeading,
|
||||||
|
DndTextArea,
|
||||||
};
|
};
|
@ -1,343 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import { Meteor } from 'meteor/meteor';
|
|
||||||
import Styled from './styles';
|
|
||||||
import AudioService from '/imports/ui/components/audio/service';
|
|
||||||
import Checkbox from '/imports/ui/components/common/checkbox/component';
|
|
||||||
|
|
||||||
const MAX_INPUT_CHARS = window.meetingClientSettings.public.poll.maxTypedAnswerLength;
|
|
||||||
|
|
||||||
const intlMessages = defineMessages({
|
|
||||||
pollingTitleLabel: {
|
|
||||||
id: 'app.polling.pollingTitle',
|
|
||||||
},
|
|
||||||
pollAnswerLabel: {
|
|
||||||
id: 'app.polling.pollAnswerLabel',
|
|
||||||
},
|
|
||||||
pollAnswerDesc: {
|
|
||||||
id: 'app.polling.pollAnswerDesc',
|
|
||||||
},
|
|
||||||
pollQuestionTitle: {
|
|
||||||
id: 'app.polling.pollQuestionTitle',
|
|
||||||
},
|
|
||||||
responseIsSecret: {
|
|
||||||
id: 'app.polling.responseSecret',
|
|
||||||
},
|
|
||||||
responseNotSecret: {
|
|
||||||
id: 'app.polling.responseNotSecret',
|
|
||||||
},
|
|
||||||
submitLabel: {
|
|
||||||
id: 'app.polling.submitLabel',
|
|
||||||
},
|
|
||||||
submitAriaLabel: {
|
|
||||||
id: 'app.polling.submitAriaLabel',
|
|
||||||
},
|
|
||||||
responsePlaceholder: {
|
|
||||||
id: 'app.polling.responsePlaceholder',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const validateInput = (i) => {
|
|
||||||
let _input = i;
|
|
||||||
if (/^\s/.test(_input)) _input = '';
|
|
||||||
return _input;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Polling extends Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
typedAns: '',
|
|
||||||
checkedAnswers: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
this.pollingContainer = null;
|
|
||||||
this.play = this.play.bind(this);
|
|
||||||
this.handleUpdateResponseInput = this.handleUpdateResponseInput.bind(this);
|
|
||||||
this.renderButtonAnswers = this.renderButtonAnswers.bind(this);
|
|
||||||
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
|
|
||||||
this.handleSubmit = this.handleSubmit.bind(this);
|
|
||||||
this.renderCheckboxAnswers = this.renderCheckboxAnswers.bind(this);
|
|
||||||
this.handleMessageKeyDown = this.handleMessageKeyDown.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.play();
|
|
||||||
this.pollingContainer && this.pollingContainer?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
play() {
|
|
||||||
AudioService.playAlertSound(`${window.meetingClientSettings.public.app.cdn
|
|
||||||
+ window.meetingClientSettings.public.app.basename
|
|
||||||
+ window.meetingClientSettings.public.app.instanceId}`
|
|
||||||
+ '/resources/sounds/Poll.mp3');
|
|
||||||
}
|
|
||||||
|
|
||||||
handleUpdateResponseInput(e) {
|
|
||||||
this.responseInput.value = validateInput(e.target.value);
|
|
||||||
this.setState({ typedAns: this.responseInput.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSubmit(pollId) {
|
|
||||||
const { handleVote } = this.props;
|
|
||||||
const { checkedAnswers } = this.state;
|
|
||||||
handleVote(pollId, checkedAnswers);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCheckboxChange(pollId, answerId) {
|
|
||||||
const { checkedAnswers } = this.state;
|
|
||||||
if (checkedAnswers.includes(answerId)) {
|
|
||||||
checkedAnswers.splice(checkedAnswers.indexOf(answerId), 1);
|
|
||||||
} else {
|
|
||||||
checkedAnswers.push(answerId);
|
|
||||||
}
|
|
||||||
checkedAnswers.sort();
|
|
||||||
this.setState({ checkedAnswers });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMessageKeyDown(e) {
|
|
||||||
const {
|
|
||||||
poll,
|
|
||||||
handleTypedVote,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
typedAns,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
if (e.keyCode === 13 && typedAns.length > 0) {
|
|
||||||
handleTypedVote(poll.pollId, typedAns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderButtonAnswers() {
|
|
||||||
const {
|
|
||||||
isMeteorConnected,
|
|
||||||
intl,
|
|
||||||
poll,
|
|
||||||
handleVote,
|
|
||||||
handleTypedVote,
|
|
||||||
pollAnswerIds,
|
|
||||||
pollTypes,
|
|
||||||
isDefaultPoll,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
typedAns,
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
if (!poll) return null;
|
|
||||||
|
|
||||||
const { stackOptions, answers, question, pollType } = poll;
|
|
||||||
const defaultPoll = isDefaultPoll(pollType);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{
|
|
||||||
poll.pollType !== pollTypes.Response && (
|
|
||||||
<span>
|
|
||||||
{
|
|
||||||
question.length === 0 && (
|
|
||||||
<Styled.PollingTitle>
|
|
||||||
{intl.formatMessage(intlMessages.pollingTitleLabel)}
|
|
||||||
</Styled.PollingTitle>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<Styled.PollingAnswers removeColumns={answers.length === 1} stacked={stackOptions}>
|
|
||||||
{answers.map((pollAnswer) => {
|
|
||||||
const formattedMessageIndex = pollAnswer?.key?.toLowerCase();
|
|
||||||
let label = pollAnswer.key;
|
|
||||||
if ((defaultPoll || pollType.includes('CUSTOM')) && pollAnswerIds[formattedMessageIndex]) {
|
|
||||||
label = intl.formatMessage(pollAnswerIds[formattedMessageIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Styled.PollButtonWrapper key={pollAnswer.id} >
|
|
||||||
<Styled.PollingButton
|
|
||||||
disabled={!isMeteorConnected}
|
|
||||||
color="primary"
|
|
||||||
size="md"
|
|
||||||
label={label}
|
|
||||||
key={pollAnswer.key}
|
|
||||||
onClick={() => handleVote(poll.pollId, [pollAnswer.id])}
|
|
||||||
aria-labelledby={`pollAnswerLabel${pollAnswer.key}`}
|
|
||||||
aria-describedby={`pollAnswerDesc${pollAnswer.key}`}
|
|
||||||
data-test="pollAnswerOption"
|
|
||||||
/>
|
|
||||||
<Styled.Hidden id={`pollAnswerLabel${pollAnswer.key}`}>
|
|
||||||
{intl.formatMessage(intlMessages.pollAnswerLabel, { 0: label })}
|
|
||||||
</Styled.Hidden>
|
|
||||||
<Styled.Hidden id={`pollAnswerDesc${pollAnswer.key}`}>
|
|
||||||
{intl.formatMessage(intlMessages.pollAnswerDesc, { 0: label })}
|
|
||||||
</Styled.Hidden>
|
|
||||||
</Styled.PollButtonWrapper>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Styled.PollingAnswers>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{
|
|
||||||
poll.pollType === pollTypes.Response
|
|
||||||
&& (
|
|
||||||
<Styled.TypedResponseWrapper>
|
|
||||||
<Styled.TypedResponseInput
|
|
||||||
data-test="pollAnswerOption"
|
|
||||||
onChange={(e) => {
|
|
||||||
this.handleUpdateResponseInput(e);
|
|
||||||
}}
|
|
||||||
onKeyDown={(e) => {
|
|
||||||
this.handleMessageKeyDown(e);
|
|
||||||
}}
|
|
||||||
type="text"
|
|
||||||
placeholder={intl.formatMessage(intlMessages.responsePlaceholder)}
|
|
||||||
maxLength={MAX_INPUT_CHARS}
|
|
||||||
ref={(r) => { this.responseInput = r; }}
|
|
||||||
onPaste={(e) => { e.stopPropagation(); }}
|
|
||||||
onCut={(e) => { e.stopPropagation(); }}
|
|
||||||
onCopy={(e) => { e.stopPropagation(); }}
|
|
||||||
/>
|
|
||||||
<Styled.SubmitVoteButton
|
|
||||||
data-test="submitAnswer"
|
|
||||||
disabled={typedAns.length === 0}
|
|
||||||
color="primary"
|
|
||||||
size="sm"
|
|
||||||
label={intl.formatMessage(intlMessages.submitLabel)}
|
|
||||||
aria-label={intl.formatMessage(intlMessages.submitAriaLabel)}
|
|
||||||
onClick={() => {
|
|
||||||
handleTypedVote(poll.pollId, typedAns);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Styled.TypedResponseWrapper>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<Styled.PollingSecret>
|
|
||||||
{intl.formatMessage(poll.secretPoll ? intlMessages.responseIsSecret : intlMessages.responseNotSecret)}
|
|
||||||
</Styled.PollingSecret>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCheckboxAnswers() {
|
|
||||||
const {
|
|
||||||
isMeteorConnected,
|
|
||||||
intl,
|
|
||||||
poll,
|
|
||||||
pollAnswerIds,
|
|
||||||
} = this.props;
|
|
||||||
const { checkedAnswers } = this.state;
|
|
||||||
const { question } = poll;
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{question.length === 0
|
|
||||||
&& (
|
|
||||||
<Styled.PollingTitle>
|
|
||||||
{intl.formatMessage(intlMessages.pollingTitleLabel)}
|
|
||||||
</Styled.PollingTitle>
|
|
||||||
)}
|
|
||||||
<Styled.MultipleResponseAnswersTable>
|
|
||||||
{poll.answers.map((pollAnswer) => {
|
|
||||||
const formattedMessageIndex = pollAnswer?.key?.toLowerCase();
|
|
||||||
let label = pollAnswer?.key;
|
|
||||||
if (pollAnswerIds[formattedMessageIndex]) {
|
|
||||||
label = intl.formatMessage(pollAnswerIds[formattedMessageIndex]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Styled.CheckboxContainer
|
|
||||||
key={pollAnswer.id}
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
<Styled.PollingCheckbox data-test="optionsAnswers">
|
|
||||||
<Checkbox
|
|
||||||
disabled={!isMeteorConnected}
|
|
||||||
id={`answerInput${pollAnswer.key}`}
|
|
||||||
onChange={() => this.handleCheckboxChange(poll.pollId, pollAnswer.id)}
|
|
||||||
checked={checkedAnswers.includes(pollAnswer.id)}
|
|
||||||
ariaLabelledBy={`pollAnswerLabel${pollAnswer.key}`}
|
|
||||||
ariaDescribedBy={`pollAnswerDesc${pollAnswer.key}`}
|
|
||||||
/>
|
|
||||||
</Styled.PollingCheckbox>
|
|
||||||
</td>
|
|
||||||
<Styled.MultipleResponseAnswersTableAnswerText>
|
|
||||||
<label id={`pollAnswerLabel${pollAnswer.key}`}>
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
<Styled.Hidden id={`pollAnswerDesc${pollAnswer.key}`} >
|
|
||||||
{intl.formatMessage(intlMessages.pollAnswerDesc, { 0: label })}
|
|
||||||
</Styled.Hidden>
|
|
||||||
</Styled.MultipleResponseAnswersTableAnswerText>
|
|
||||||
</Styled.CheckboxContainer>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Styled.MultipleResponseAnswersTable>
|
|
||||||
<div>
|
|
||||||
<Styled.SubmitVoteButton
|
|
||||||
disabled={!isMeteorConnected || checkedAnswers.length === 0}
|
|
||||||
color="primary"
|
|
||||||
size="sm"
|
|
||||||
label={intl.formatMessage(intlMessages.submitLabel)}
|
|
||||||
aria-label={intl.formatMessage(intlMessages.submitAriaLabel)}
|
|
||||||
onClick={() => this.handleSubmit(poll.pollId)}
|
|
||||||
data-test="submitAnswersMultiple"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
intl,
|
|
||||||
poll,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (!poll) return null;
|
|
||||||
|
|
||||||
const { stackOptions, question } = poll;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Styled.Overlay>
|
|
||||||
<Styled.PollingContainer
|
|
||||||
autoWidth={stackOptions}
|
|
||||||
data-test="pollingContainer"
|
|
||||||
role="complementary"
|
|
||||||
ref={el => this.pollingContainer = el}
|
|
||||||
tabIndex={-1}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
question.length > 0 && (
|
|
||||||
<Styled.QHeader>
|
|
||||||
<Styled.QTitle>
|
|
||||||
{intl.formatMessage(intlMessages.pollQuestionTitle)}
|
|
||||||
</Styled.QTitle>
|
|
||||||
<Styled.QText data-test="pollQuestion">{question}</Styled.QText>
|
|
||||||
</Styled.QHeader>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
{poll.isMultipleResponse ? this.renderCheckboxAnswers() : this.renderButtonAnswers()}
|
|
||||||
</Styled.PollingContainer>
|
|
||||||
</Styled.Overlay>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default injectIntl(injectWbResizeEvent(Polling));
|
|
||||||
|
|
||||||
Polling.propTypes = {
|
|
||||||
intl: PropTypes.shape({
|
|
||||||
formatMessage: PropTypes.func.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
handleVote: PropTypes.func.isRequired,
|
|
||||||
handleTypedVote: PropTypes.func.isRequired,
|
|
||||||
poll: PropTypes.shape({
|
|
||||||
pollId: PropTypes.string.isRequired,
|
|
||||||
answers: PropTypes.arrayOf(PropTypes.shape({
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
key: PropTypes.string,
|
|
||||||
}).isRequired).isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
};
|
|
@ -1,75 +1,3 @@
|
|||||||
import React from 'react';
|
import PollingGraphqlContainer from './component';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { withTracker } from 'meteor/react-meteor-data';
|
|
||||||
import { useMutation } from '@apollo/client';
|
|
||||||
import PollingService from './service';
|
|
||||||
import PollService from '/imports/ui/components/poll/service';
|
|
||||||
import PollingComponent from './component';
|
|
||||||
import { isPollingEnabled } from '/imports/ui/services/features';
|
|
||||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
|
||||||
import { POLL_SUBMIT_TYPED_VOTE, POLL_SUBMIT_VOTE } from '/imports/ui/components/poll/mutations';
|
|
||||||
import PollingGraphqlContainer from './polling-graphql/component';
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
pollExists: PropTypes.bool.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PollingContainer = ({ pollExists, ...props }) => {
|
|
||||||
const { data: currentUserData } = useCurrentUser((user) => ({
|
|
||||||
presenter: user.presenter,
|
|
||||||
}));
|
|
||||||
const showPolling = pollExists && !currentUserData?.presenter && isPollingEnabled();
|
|
||||||
|
|
||||||
const [pollSubmitUserTypedVote] = useMutation(POLL_SUBMIT_TYPED_VOTE);
|
|
||||||
const [pollSubmitUserVote] = useMutation(POLL_SUBMIT_VOTE);
|
|
||||||
|
|
||||||
const handleTypedVote = (pollId, answer) => {
|
|
||||||
pollSubmitUserTypedVote({
|
|
||||||
variables: {
|
|
||||||
pollId,
|
|
||||||
answer,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleVote = (pollId, answerIds) => {
|
|
||||||
pollSubmitUserVote({
|
|
||||||
variables: {
|
|
||||||
pollId,
|
|
||||||
answerIds,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (showPolling) {
|
|
||||||
return (
|
|
||||||
<PollingComponent handleTypedVote={handleTypedVote} handleVote={handleVote} {...props} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
PollingContainer.propTypes = propTypes;
|
|
||||||
|
|
||||||
withTracker(() => {
|
|
||||||
const {
|
|
||||||
pollExists, poll,
|
|
||||||
} = PollingService.mapPolls();
|
|
||||||
const { pollTypes } = PollService;
|
|
||||||
|
|
||||||
if (poll && poll?.pollType) {
|
|
||||||
const isResponse = poll.pollType === pollTypes.Response;
|
|
||||||
Meteor.subscribe('polls', isResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ({
|
|
||||||
pollExists,
|
|
||||||
poll,
|
|
||||||
pollAnswerIds: PollService.pollAnswerIds,
|
|
||||||
pollTypes,
|
|
||||||
isDefaultPoll: PollService.isDefaultPoll,
|
|
||||||
isMeteorConnected: Meteor.status().connected,
|
|
||||||
});
|
|
||||||
})(PollingContainer);
|
|
||||||
|
|
||||||
export default PollingGraphqlContainer;
|
export default PollingGraphqlContainer;
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import Polls from '/imports/api/polls';
|
|
||||||
|
|
||||||
const MAX_CHAR_LENGTH = 5;
|
|
||||||
|
|
||||||
const mapPolls = () => {
|
|
||||||
const poll = Polls.findOne({});
|
|
||||||
if (!poll) {
|
|
||||||
return { pollExists: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const { answers } = poll;
|
|
||||||
let stackOptions = false;
|
|
||||||
|
|
||||||
answers.map((obj) => {
|
|
||||||
if (stackOptions) return obj;
|
|
||||||
if (obj.key && obj.key.length > MAX_CHAR_LENGTH) {
|
|
||||||
stackOptions = true;
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
});
|
|
||||||
|
|
||||||
const amIRequester = poll.requester !== 'userId';
|
|
||||||
|
|
||||||
return {
|
|
||||||
poll: {
|
|
||||||
answers: poll.answers,
|
|
||||||
pollId: poll.id,
|
|
||||||
isMultipleResponse: poll.isMultipleResponse,
|
|
||||||
pollType: poll.pollType,
|
|
||||||
stackOptions,
|
|
||||||
question: poll.question,
|
|
||||||
secretPoll: poll.secretPoll,
|
|
||||||
},
|
|
||||||
pollExists: true,
|
|
||||||
amIRequester,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default { mapPolls };
|
|
@ -1,239 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
import {
|
|
||||||
mdPaddingY,
|
|
||||||
smPaddingY,
|
|
||||||
jumboPaddingY,
|
|
||||||
smPaddingX,
|
|
||||||
borderRadius,
|
|
||||||
pollWidth,
|
|
||||||
pollSmMargin,
|
|
||||||
overlayIndex,
|
|
||||||
overlayOpacity,
|
|
||||||
pollIndex,
|
|
||||||
lgPaddingY,
|
|
||||||
pollBottomOffset,
|
|
||||||
jumboPaddingX,
|
|
||||||
pollColAmount,
|
|
||||||
borderSize,
|
|
||||||
} from '/imports/ui/stylesheets/styled-components/general';
|
|
||||||
import {
|
|
||||||
fontSizeSmall,
|
|
||||||
fontSizeBase,
|
|
||||||
fontSizeLarge,
|
|
||||||
} from '/imports/ui/stylesheets/styled-components/typography';
|
|
||||||
import {
|
|
||||||
colorText,
|
|
||||||
colorBlueLight,
|
|
||||||
colorGrayLighter,
|
|
||||||
colorOffWhite,
|
|
||||||
colorGrayDark,
|
|
||||||
colorWhite,
|
|
||||||
colorPrimary,
|
|
||||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
|
||||||
import { hasPhoneDimentions } from '/imports/ui/stylesheets/styled-components/breakpoints';
|
|
||||||
import Button from '/imports/ui/components/common/button/component';
|
|
||||||
|
|
||||||
const PollingTitle = styled.div`
|
|
||||||
white-space: nowrap;
|
|
||||||
padding-bottom: ${mdPaddingY};
|
|
||||||
padding-top: ${mdPaddingY};
|
|
||||||
font-size: ${fontSizeSmall};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PollButtonWrapper = styled.div`
|
|
||||||
text-align: center;
|
|
||||||
padding: ${smPaddingY};
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PollingButton = styled(Button)`
|
|
||||||
width: 100%;
|
|
||||||
max-width: 9em;
|
|
||||||
|
|
||||||
@media ${hasPhoneDimentions} {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Hidden = styled.div`
|
|
||||||
display: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TypedResponseWrapper = styled.div`
|
|
||||||
margin: ${jumboPaddingY} .5rem .5rem .5rem;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const TypedResponseInput = styled.input`
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-radius: ${borderSize};
|
|
||||||
box-shadow: 0 0 0 ${borderSize} ${colorBlueLight}, inset 0 0 0 1px ${colorPrimary};
|
|
||||||
}
|
|
||||||
|
|
||||||
color: ${colorText};
|
|
||||||
-webkit-appearance: none;
|
|
||||||
padding: calc(${smPaddingY} * 2.5) calc(${smPaddingX} * 1.25);
|
|
||||||
border-radius: ${borderRadius};
|
|
||||||
font-size: ${fontSizeBase};
|
|
||||||
border: 1px solid ${colorGrayLighter};
|
|
||||||
box-shadow: 0 0 0 1px ${colorGrayLighter};
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const SubmitVoteButton = styled(Button)`
|
|
||||||
font-size: ${fontSizeBase};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PollingSecret = styled.div`
|
|
||||||
font-size: ${fontSizeSmall};
|
|
||||||
max-width: ${pollWidth};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MultipleResponseAnswersTable = styled.table`
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PollingCheckbox = styled.div`
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: ${pollSmMargin};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CheckboxContainer = styled.tr`
|
|
||||||
margin-bottom: ${pollSmMargin};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const MultipleResponseAnswersTableAnswerText = styled.td`
|
|
||||||
text-align: left;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Overlay = styled.div`
|
|
||||||
position: absolute;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
z-index: ${overlayIndex};
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
@media ${hasPhoneDimentions} {
|
|
||||||
pointer-events: auto;
|
|
||||||
background-color: rgba(0, 0, 0, ${overlayOpacity});
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QHeader = styled.span`
|
|
||||||
text-align: left;
|
|
||||||
position: relative;
|
|
||||||
left: ${smPaddingY};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QTitle = styled.div`
|
|
||||||
font-size: ${fontSizeSmall};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QText = styled.div`
|
|
||||||
color: ${colorText};
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
font-size: ${fontSizeLarge};
|
|
||||||
max-width: ${pollWidth};
|
|
||||||
padding-right: ${smPaddingX};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PollingContainer = styled.aside`
|
|
||||||
pointer-events:auto;
|
|
||||||
min-width: ${pollWidth};
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
z-index: ${pollIndex};
|
|
||||||
border: 1px solid ${colorOffWhite};
|
|
||||||
border-radius: ${borderRadius};
|
|
||||||
box-shadow: ${colorGrayDark} 0px 0px ${lgPaddingY};
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: ${mdPaddingY};
|
|
||||||
background-color: ${colorWhite};
|
|
||||||
bottom: ${pollBottomOffset};
|
|
||||||
right: ${jumboPaddingX};
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border: 1px solid ${colorPrimary};
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] & {
|
|
||||||
left: ${jumboPaddingX};
|
|
||||||
right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media ${hasPhoneDimentions} {
|
|
||||||
bottom: auto;
|
|
||||||
right: auto;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
max-height: 95%;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
[dir="rtl"] & {
|
|
||||||
left: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
${({ autoWidth }) => autoWidth && `
|
|
||||||
width: auto;
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const PollingAnswers = styled.div`
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(${pollColAmount}, 1fr);
|
|
||||||
|
|
||||||
@media ${hasPhoneDimentions} {
|
|
||||||
grid-template-columns: repeat(1, 1fr);
|
|
||||||
|
|
||||||
& div button {
|
|
||||||
grid-column: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
${({ removeColumns }) => removeColumns && `
|
|
||||||
grid-template-columns: auto;
|
|
||||||
`}
|
|
||||||
|
|
||||||
${({ stacked }) => stacked && `
|
|
||||||
grid-template-columns: repeat(1, 1fr);
|
|
||||||
|
|
||||||
& div button {
|
|
||||||
max-width: none !important;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
PollingTitle,
|
|
||||||
PollButtonWrapper,
|
|
||||||
PollingButton,
|
|
||||||
Hidden,
|
|
||||||
TypedResponseWrapper,
|
|
||||||
TypedResponseInput,
|
|
||||||
SubmitVoteButton,
|
|
||||||
PollingSecret,
|
|
||||||
MultipleResponseAnswersTable,
|
|
||||||
PollingCheckbox,
|
|
||||||
CheckboxContainer,
|
|
||||||
MultipleResponseAnswersTableAnswerText,
|
|
||||||
Overlay,
|
|
||||||
QHeader,
|
|
||||||
QTitle,
|
|
||||||
QText,
|
|
||||||
PollingContainer,
|
|
||||||
PollingAnswers,
|
|
||||||
};
|
|
@ -14,7 +14,6 @@ const TYPING_INDICATOR_ENABLED = CHAT_CONFIG.typingIndicator.enabled;
|
|||||||
const SUBSCRIPTIONS = [
|
const SUBSCRIPTIONS = [
|
||||||
// 'users',
|
// 'users',
|
||||||
// 'meetings',
|
// 'meetings',
|
||||||
'polls',
|
|
||||||
'captions',
|
'captions',
|
||||||
// 'voiceUsers',
|
// 'voiceUsers',
|
||||||
'screenshare',
|
'screenshare',
|
||||||
|
@ -3,7 +3,6 @@ import '/imports/startup/server';
|
|||||||
// 2x
|
// 2x
|
||||||
import '/imports/api/meetings/server';
|
import '/imports/api/meetings/server';
|
||||||
import '/imports/api/users/server';
|
import '/imports/api/users/server';
|
||||||
import '/imports/api/polls/server';
|
|
||||||
import '/imports/api/captions/server';
|
import '/imports/api/captions/server';
|
||||||
import '/imports/api/presentation-upload-token/server';
|
import '/imports/api/presentation-upload-token/server';
|
||||||
import '/imports/api/breakouts/server';
|
import '/imports/api/breakouts/server';
|
||||||
|
Loading…
Reference in New Issue
Block a user