import util from "../../plugins/utils"; export default { data() { return { pollQuestion: "", pollAnswers: [], pollTotalVotes: 0, pollResponseRelations: null, pollEndRelations: null, pollEndTs: null, pollIsDisclosed: true, pollTentativeAnswer: null, pollViewResults: false, }; }, mounted() { this.$matrix.on("Room.timeline", this.pollMixinOnEvent); this.pollQuestion = (this.event && this.event.getContent()["m.poll.start"] && this.event.getContent()["m.poll.start"]["question"]["body"]) || (this.event && this.event.getContent()["org.matrix.msc3381.poll.start"] && this.event.getContent()["org.matrix.msc3381.poll.start"]["question"]["body"]) || ""; this.updateAnswers(); }, destroyed() { this.$matrix.off("Room.timeline", this.pollMixinOnEvent); }, beforeDestroy() { if (this.pollResponseRelations) { this.pollResponseRelations.off("Relations.add", this.onAddRelation); this.pollResponseRelations = null; } if (this.pollEndRelations) { this.pollEndRelations.off("Relations.add", this.onAddRelation); this.pollEndRelations = null; } }, methods: { updateAnswers() { let answers = (this.event && this.event.getContent()["m.poll.start"] && this.event.getContent()["m.poll.start"]["answers"]) || (this.event && this.event.getContent()["org.matrix.msc3381.poll.start"] && this.event.getContent()["org.matrix.msc3381.poll.start"]["answers"]) || []; var answerMap = {}; var answerArray = []; answers.forEach((a) => { let text = a["org.matrix.msc1767.text"]; let answer = { id: a.id, text: text, numVotes: 0, percentage: 0 }; answerMap[a.id] = answer; answerArray.push(answer); }); // Kind of poll this.pollIsDisclosed = false; if (this.event) { if (this.event.getContent()["m.poll.start"]) { this.pollIssDisclosed = this.event.getContent()["m.poll.start"]["kind"] != "m.poll.undisclosed" || false; } else if (this.event.getContent()["org.matrix.msc3381.poll.start"]) { this.pollIssDisclosed = this.event.getContent()["org.matrix.msc3381.poll.start"]["kind"] != "org.matrix.msc3381.poll.undisclosed" || false; } } // Look for poll end this.pollEndRelations = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.reference", "m.poll.end") || this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.reference", "org.matrix.msc3381.poll.end"); if (this.pollEndRelations) { const endMessages = this.pollEndRelations.getRelations() || []; if (endMessages.length > 0) { this.pollEndTs = endMessages[endMessages.length - 1].getTs(); } } // Process votes this.pollResponseRelations = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.reference", "m.poll.response") || this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.reference", "org.matrix.msc3381.poll.response"); var userVotes = {}; if (this.pollResponseRelations) { const votes = this.pollResponseRelations.getRelations() || []; for (const vote of votes) { //const emoji = r.getRelation().key; if (this.pollEndTs && vote.getTs() > this.pollEndTs) { continue; // Invalid vote, after poll was closed. } const sender = vote.getSender(); const answersFromThisUser = (vote.getContent()["m.poll.response"] && vote.getContent()["m.poll.response"]["answers"]) || (vote.getContent()["org.matrix.msc3381.poll.response"] && vote.getContent()["org.matrix.msc3381.poll.response"]["answers"]) || []; if (answersFromThisUser.length == 0) { delete userVotes[sender]; } else { userVotes[sender] = answersFromThisUser; } } } var totalVotes = 0; for (const [user, answersFromThisUser] of Object.entries(userVotes)) { for (const a of answersFromThisUser) { if (answerMap[a]) { answerMap[a].numVotes += 1; totalVotes += 1; if (user == this.$matrix.currentUserId) { answerMap[a].hasMyVote = true; } } } } // Update percentages. For algorithm, see here: https://revs.runtime-revolution.com/getting-100-with-rounded-percentages-273ffa70252b // (need to add up to 100%) if (totalVotes > 0) { let a = answerArray.map((a) => { let votes = (100 * a.numVotes) / totalVotes; return { answer: a, unrounded: votes, floor: Math.floor(votes), }; }); a.sort((item1, item2) => { Math.abs(item2.floor) - Math.abs(item1.floor); }); var diff = 100 - a.reduce((previousValue, currentValue) => { return previousValue + currentValue.floor; }, 0); const maxVotes = Math.max(...a.map((item) => item.unrounded)); a.map((answer, index) => { answer.answer.percentage = index < diff ? answer.floor + 1 : answer.floor; answer.answer.winner = answer.unrounded == maxVotes; }); } this.pollAnswers = answerArray; this.pollTotalVotes = totalVotes; }, pollAnswer(id) { // Only if not voted and not currently viewing poll results. if (!this.userHasVoted && !this.pollViewResults) { if (id == this.pollTentativeAnswer) { this.pollTentativeAnswer = null; } else { this.pollTentativeAnswer = id; } } }, pollAnswerSubmit() { if (!this.pollTentativeAnswer || this.pollIsClosed) { return; } util .sendPollAnswer(this.$matrix.matrixClient, this.room.roomId, [this.pollTentativeAnswer], this.event) .catch((err) => { console.log("Failed to send:", err); }) .finally(() => { this.pollTentativeAnswer = null; }); }, pollClose() { util.closePoll(this.$matrix.matrixClient, this.room.roomId, this.event).then(() => { this.$emit("poll-closed", this); }).catch((err) => { console.log("Failed to send:", err); }); }, onAddRelation(ignoredevent) { this.updateAnswers(); }, pollMixinOnEvent(event) { if (event.getRoomId() !== this.room.roomId) { return; // Not for this room } this.$matrix.matrixClient.decryptEventIfNeeded(event).then(() => { if (event.getType().startsWith("m.poll.") || event.getType().startsWith("org.matrix.msc3381.poll.")) { this.updateAnswers(); } }); }, }, computed: { pollIsClosed() { return this.pollEndTs != null && this.pollEndTs !== undefined; }, userCanClosePoll() { return ( this.room && this.room.currentState && this.room.currentState.maySendRedactionForEvent(this.event, this.$matrix.currentUserId) ); }, userHasVoted() { return this.pollAnswers.some((a) => a.hasMyVote); }, pollNumAnswers() { return this.pollTotalVotes; }, pollIsAdmin() { // Admins can view results of not-yet-closed undisclosed polls. const me = this.room && this.room.getMember(this.$matrix.currentUserId); let isAdmin = me && this.room.currentState && this.room.currentState.hasSufficientPowerLevelFor("redact", me.powerLevel); return isAdmin; }, }, watch: { pollResponseRelations: { handler(newValue, oldValue) { if (oldValue) { oldValue.off("Relations.add", this.onAddRelation); } if (newValue) { newValue.on("Relations.add", this.onAddRelation); } this.updateAnswers(); }, immediate: true, }, pollEndRelations: { handler(newValue, oldValue) { if (oldValue) { oldValue.off("Relations.add", this.onAddRelation); } if (newValue) { newValue.on("Relations.add", this.onAddRelation); } this.updateAnswers(); }, immediate: true, }, }, };