From 045f7112e0271de8b40cddec076bd09c6ccd03be Mon Sep 17 00:00:00 2001 From: chris-mclennon Date: Sun, 1 Aug 2021 17:57:44 -0400 Subject: [PATCH] Add a timer --- CHANGELOG.md | 3 + src/App.vue | 4 + src/components/CountdownTimer.vue | 173 ++++++++++++++++++++++++++++++ src/components/Menu.vue | 7 ++ src/components/TownSquare.vue | 98 ++++++++++------- src/main.js | 5 + src/store/index.js | 15 +++ src/store/modules/session.js | 13 ++- src/store/socket.js | 50 ++++++++- 9 files changed, 330 insertions(+), 38 deletions(-) create mode 100644 src/components/CountdownTimer.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 99577c0..aca7e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Release Notes +### Version 2.15.4 +- add timer + ### Version 2.15.3 - add Huntsman/Damsel to list of available characters diff --git a/src/App.vue b/src/App.vue index 866d599..4337a69 100644 --- a/src/App.vue +++ b/src/App.vue @@ -121,6 +121,10 @@ export default { if (this.session.isSpectator) return; this.$refs.menu.toggleNight(); break; + case "t": + if (this.session.isSpectator) return; + this.$refs.menu.toggleTimer(); + break; case "escape": this.$store.commit("toggleModal"); } diff --git a/src/components/CountdownTimer.vue b/src/components/CountdownTimer.vue new file mode 100644 index 0000000..9904e33 --- /dev/null +++ b/src/components/CountdownTimer.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/src/components/Menu.vue b/src/components/Menu.vue index 46133ca..d144e3d 100644 --- a/src/components/Menu.vue +++ b/src/components/Menu.vue @@ -138,6 +138,10 @@ Send Characters +
  • + Toggle timer + [T] +
  • -
    -

    - Fabled - - -

    -
      -
    • -
      +
      +

      + Fabled + + +

      +
        +
      • - {{ nightOrder.get(role).first }}. - {{ - role.firstNightReminder - }} -
      -
      - {{ nightOrder.get(role).other }}. - {{ - role.otherNightReminder - }} -
      - -
    • -
    +
    + {{ nightOrder.get(role).first }}. + {{ + role.firstNightReminder + }} +
    +
    + {{ nightOrder.get(role).other }}. + {{ + role.otherNightReminder + }} +
    + +
  • + + + + @@ -90,6 +98,7 @@ import { mapGetters, mapState } from "vuex"; import Player from "./Player"; import Token from "./Token"; +import CountdownTimer from "./CountdownTimer"; import ReminderModal from "./modals/ReminderModal"; import RoleModal from "./modals/RoleModal"; @@ -97,6 +106,7 @@ export default { components: { Player, Token, + CountdownTimer, RoleModal, ReminderModal }, @@ -394,16 +404,32 @@ export default { } } -/***** Demon bluffs / Fabled *******/ -#townsquare > .bluffs, -#townsquare > .fabled { +#top-left { position: absolute; + top: 10px; + left: 10px; + + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; +} + +/***** Demon bluffs / Fabled / Countdown Timer *******/ +#townsquare > .bluffs, +#top-left > .fabled, +#top-left > .countdown-timer { &.bluffs { + position: absolute; bottom: 10px; } &.fabled { - top: 10px; + width: 100%; } + &.countdown-timer { + width: 100%; + } + margin: 5px 0 0 0; left: 10px; background: rgba(0, 0, 0, 0.5); border-radius: 10px; diff --git a/src/main.js b/src/main.js index 9d7af31..b7f1a8c 100644 --- a/src/main.js +++ b/src/main.js @@ -27,8 +27,12 @@ const faIcons = [ "Heartbeat", "Image", "Link", + "Minus", "MinusCircle", + "Pause", "PeopleArrows", + "Play", + "Plus", "PlusCircle", "Question", "Random", @@ -37,6 +41,7 @@ const faIcons = [ "SearchPlus", "Skull", "Square", + "Stop", "TheaterMasks", "Times", "TimesCircle", diff --git a/src/store/index.js b/src/store/index.js index feb528d..568d5bc 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -105,9 +105,15 @@ export default new Vuex.Store({ isStatic: false, isMuted: false, isImageOptIn: false, + isTimerEnabled: false, zoom: 0, background: "" }, + countdownTimer: { + totalSeconds: 300, + remainingSeconds: 300, + isTicking: false + }, modals: { edition: false, fabled: false, @@ -171,6 +177,7 @@ export default new Vuex.Store({ toggleNight: toggle("isNight"), toggleGrimoire: toggle("isPublic"), toggleImageOptIn: toggle("isImageOptIn"), + toggleTimer: toggle("isTimerEnabled"), toggleModal({ modals }, name) { if (name) { modals[name] = !modals[name]; @@ -260,6 +267,14 @@ export default new Vuex.Store({ state.edition = edition; } state.modals.edition = false; + }, + /** + * Set timer state + * @param state + * @param payload Object with keys: `remainingSeconds`, `totalSeconds`, and `isTicking` + */ + setTimerState(state, payload) { + state.countdownTimer = payload; } }, plugins: [persistence, socket] diff --git a/src/store/modules/session.js b/src/store/modules/session.js index 884117a..62f0179 100644 --- a/src/store/modules/session.js +++ b/src/store/modules/session.js @@ -27,7 +27,12 @@ const state = () => ({ voteHistory: [], markedPlayer: -1, isVoteHistoryAllowed: true, - isRolesDistributed: false + isRolesDistributed: false, + countdownTimer: { + totalSeconds: 300, + remainingSeconds: 300, + isTicking: false + } }); const getters = {}; @@ -94,6 +99,12 @@ const mutations = { clearVoteHistory(state) { state.voteHistory = []; }, + updateTimerState(state, payload) { + state.countdownTimer = payload; + }, + distributeTimerAction(state, payload) { + state.countdownTimer = payload; + }, /** * Store a vote with and without syncing it to the live session. * This is necessary in order to prevent infinite voting loops. diff --git a/src/store/socket.js b/src/store/socket.js index eea5821..4abee67 100644 --- a/src/store/socket.js +++ b/src/store/socket.js @@ -205,6 +205,13 @@ class LiveSession { case "pronouns": this._updatePlayerPronouns(params); break; + case "timer": + this._handleTimerAction(params); + break; + case "isTimerEnabled": + if (!this._isSpectator) return; + this._store.commit("toggleTimer", params); + break; } } @@ -284,6 +291,8 @@ class LiveSession { isVoteInProgress: session.isVoteInProgress, markedPlayer: session.markedPlayer, fabled: fabled.map(f => (f.isCustom ? f : { id: f.id })), + isTimerEnabled: grimoire.isTimerEnabled, + countdownTimer: session.countdownTimer, ...(session.nomination ? { votes: session.votes } : {}) }); } @@ -307,7 +316,9 @@ class LiveSession { lockedVote, isVoteInProgress, markedPlayer, - fabled + fabled, + isTimerEnabled, + countdownTimer } = data; const players = this._store.state.players.players; // adjust number of players @@ -365,6 +376,8 @@ class LiveSession { this._store.commit("players/setFabled", { fabled: fabled.map(f => this._store.state.fabled.get(f.id) || f) }); + this._store.commit("toggleTimer", isTimerEnabled); + this._store.commit("setTimerState", countdownTimer); } } @@ -668,6 +681,25 @@ class LiveSession { } } + /** + * Distribute new timer action to each player. + */ + distributeTimerAction(payload) { + if (this._isSpectator) { + return; + } + this._send("timer", payload); + } + + /** + * Handle a timer action. + * @param payload + * @private + */ + _handleTimerAction(payload) { + this._store.commit("setTimerState", payload); + } + /** * A player nomination. ST only * This also syncs the voting speed to the players. @@ -703,6 +735,14 @@ class LiveSession { this._send("isNight", this._store.state.grimoire.isNight); } + /** + * Send the isTimerEnabled status. ST only + */ + setIsTimerEnabled() { + if (this._isSpectator) return; + this._send("isTimerEnabled", this._store.state.grimoire.isTimerEnabled); + } + /** * Send the isVoteHistoryAllowed state. ST only */ @@ -859,6 +899,11 @@ export default store => { session.distributeRoles(); } break; + case "session/distributeTimerAction": + if (payload) { + session.distributeTimerAction(payload); + } + break; case "session/nomination": case "session/setNomination": session.nomination(payload); @@ -914,6 +959,9 @@ export default store => { session.sendPlayer(payload); } break; + case "toggleTimer": + session.setIsTimerEnabled(); + break; } });