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":