diff --git a/src/components/Player.vue b/src/components/Player.vue
index b98ca20..f4c8628 100644
--- a/src/components/Player.vue
+++ b/src/components/Player.vue
@@ -285,9 +285,16 @@ export default {
this.isMenuOpen = false;
this.$emit("trigger", ["claimSeat"]);
},
+ /**
+ * Allow the ST to override a locked vote.
+ */
vote() {
- if (this.player.id !== this.session.playerId) return;
- this.$store.commit("session/vote", [this.index]);
+ if (this.session.isSpectator) return;
+ if (!this.voteLocked) return;
+ this.$store.commit("session/voteSync", [
+ this.index,
+ !this.session.votes[this.index]
+ ]);
}
}
};
@@ -503,11 +510,13 @@ export default {
}
}
+// other player voted yes, but is not locked yet
#townsquare.vote .player.vote-yes .overlay svg.vote.fa-skull {
opacity: 0.5;
transform: scale(1);
}
+// you voted yes | a locked vote yes | a locked vote no
#townsquare.vote .player.you.vote-yes .overlay svg.vote.fa-skull,
#townsquare.vote .player.vote-lock.vote-yes .overlay svg.vote.fa-skull,
#townsquare.vote .player.vote-lock:not(.vote-yes) .overlay svg.vote.fa-times {
@@ -515,6 +524,11 @@ export default {
transform: scale(1);
}
+// a locked vote can be clicked on by the ST
+#townsquare.vote:not(.spectator) .player.vote-lock .overlay svg.vote {
+ pointer-events: all;
+}
+
li.from:not(.nominate) .player .overlay svg.cancel {
opacity: 1;
transform: scale(1);
diff --git a/src/components/Vote.vue b/src/components/Vote.vue
index a869ecd..9f85c13 100644
--- a/src/components/Vote.vue
+++ b/src/components/Vote.vue
@@ -19,7 +19,8 @@
- {{ voters.join(", ") || "nobody" }}
+ {{ voters.join(", ") }}
+ nobody
voted YES
@@ -122,7 +123,7 @@ export default {
if (!this.canVote) return false;
const index = this.players.findIndex(p => p.id === this.session.playerId);
if (index >= 0 && !!this.session.votes[index] !== vote) {
- this.$store.commit("session/vote", [index, vote]);
+ this.$store.commit("session/voteSync", [index, vote]);
}
}
}
diff --git a/src/store/modules/session.js b/src/store/modules/session.js
index 69faaae..4ab53ae 100644
--- a/src/store/modules/session.js
+++ b/src/store/modules/session.js
@@ -1,3 +1,14 @@
+// helper functions
+const set = key => (state, val) => {
+ state[key] = val;
+};
+
+const handleVote = (state, [index, vote]) => {
+ if (!state.nomination) return;
+ state.votes = [...state.votes];
+ state.votes[index] = vote === undefined ? !state.votes[index] : vote;
+};
+
const state = () => ({
sessionId: "",
isSpectator: false,
@@ -14,31 +25,24 @@ const getters = {};
const actions = {};
const mutations = {
- setSessionId(state, sessionId) {
- state.sessionId = sessionId;
- },
- setPlayerId(state, playerId) {
- state.playerId = playerId;
- },
- setSpectator(state, spectator) {
- state.isSpectator = spectator;
- },
- setPlayerCount(state, playerCount) {
- state.playerCount = playerCount;
- },
- claimSeat(state, claimedSeat) {
- state.claimedSeat = claimedSeat;
- },
+ setSessionId: set("sessionId"),
+ setPlayerId: set("playerId"),
+ setSpectator: set("isSpectator"),
+ setPlayerCount: set("playerCount"),
+ claimSeat: set("claimedSeat"),
nomination(state, nomination) {
state.nomination = nomination;
state.votes = [];
state.lockedVote = 0;
},
- vote(state, [index, vote]) {
- if (!state.nomination) return;
- state.votes = [...state.votes];
- state.votes[index] = vote === undefined ? !state.votes[index] : vote;
- },
+ /**
+ * Store a vote with and without syncing it to the live session.
+ * This is necessary in order to prevent infinite voting loops.
+ * @param state
+ * @param vote
+ */
+ vote: handleVote,
+ voteSync: handleVote,
lockVote(state, lock) {
state.lockedVote = lock !== undefined ? lock : state.lockedVote + 1;
}
diff --git a/src/store/socket.js b/src/store/socket.js
index 3bf6b20..4bd1d82 100644
--- a/src/store/socket.js
+++ b/src/store/socket.js
@@ -386,27 +386,28 @@ class LiveSession {
}
/**
- * Send a vote. Player only
+ * Send a vote. Player or ST
* @param index Seat of the player
+ * @param sync Flag whether to sync this vote with others or not
*/
vote([index]) {
- if (!this._isSpectator) return;
const player = this._store.state.players.players[index];
- if (this._store.state.session.playerId === player.id) {
- // send vote only if it is your own vote
+ if (
+ this._store.state.session.playerId === player.id ||
+ !this._isSpectator
+ ) {
+ // send vote only if it is your own vote or you are the storyteller
this._send("vote", [index, this._store.state.session.votes[index]]);
}
}
/**
* Handle an incoming vote, but not if it is for your own seat.
+ * @param index
* @param vote
*/
- _handleVote(vote) {
- const player = this._store.state.players.players[vote[0]];
- if (this._store.state.session.playerId !== player.id) {
- this._store.commit("session/vote", vote);
- }
+ _handleVote([index, vote]) {
+ this._store.commit("session/vote", [index, vote]);
}
/**
@@ -460,7 +461,7 @@ module.exports = store => {
case "session/nomination":
session.nomination(payload);
break;
- case "session/vote":
+ case "session/voteSync":
session.vote(payload);
break;
case "session/lockVote":