243 lines
8.2 KiB
JavaScript
243 lines
8.2 KiB
JavaScript
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,
|
|
},
|
|
},
|
|
};
|