mirror of https://github.com/bra1n/townsquare.git
commit
ef4241308e
|
@ -18,6 +18,9 @@ on:
|
|||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ main ]
|
||||
|
||||
###############
|
||||
# Set the Job #
|
||||
|
|
47
CHANGELOG.md
47
CHANGELOG.md
|
@ -1,10 +1,57 @@
|
|||
# Release Notes
|
||||
|
||||
## Version 2.6.0
|
||||
- night mode can be toggeled with [S] now (thanks @davotronic5000)
|
||||
- night order shows which players are dead
|
||||
|
||||
---
|
||||
|
||||
## Version 2.5.0
|
||||
- all travelers from the base editions are now optionally available (thanks @davotronic5000)
|
||||
- night order shows player names near roles now
|
||||
|
||||
---
|
||||
|
||||
## Version 2.4.0
|
||||
- added spoiler role (Pixie!)
|
||||
- fixed bug with ST sending out roles that are not part of the current edition / script (ie. travelers or base set roles)
|
||||
- better Lycanthrope icon (thanks @AWConant)
|
||||
|
||||
---
|
||||
|
||||
## Version 2.3.1
|
||||
- better vote history design and added timestamps
|
||||
- adjusted player menu styling on smaller screens
|
||||
- improved CONTRIBUTING.md description of hosting your own copy
|
||||
|
||||
---
|
||||
|
||||
## Version 2.3.0
|
||||
- added spoiler role (Lycanthrope!)
|
||||
- fixed copy to clipboard in Firefox
|
||||
- fixed non-countdown votes still playing countdown sound for a split second
|
||||
|
||||
---
|
||||
|
||||
## Version 2.2.1
|
||||
- clearing players / roles now also clears Fabled
|
||||
- fix list of locked votes showing unlocked votes sometimes
|
||||
|
||||
---
|
||||
|
||||
## Version 2.2.0
|
||||
- added [V] hotkey to open nomination history (thanks @lilserf)
|
||||
- updated roles according to official Wiki changes
|
||||
- adjusted roles night order
|
||||
|
||||
---
|
||||
|
||||
## Version 2.1.1
|
||||
- show vote results at the end of a vote
|
||||
- fixed global reminders not showing up anymore when the associated role is assigned to a player
|
||||
- adjusted backend metrics
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Version 2.1.0
|
||||
|
|
|
@ -43,6 +43,20 @@ $ npm install
|
|||
|
||||
The development server can be started with `npm run serve`.
|
||||
|
||||
### Deploying to GitHub pages or with a non-root path
|
||||
|
||||
Deploying a forked version to GitHub Pages or running your local
|
||||
development copy in a sub-path (instead of the web root) will require you to modify
|
||||
the `vue.config.js` and configure the path at which the website will be served.
|
||||
|
||||
For example, deploying your forked `townsquare` project to GitHub pages would need the following
|
||||
`vue.config.js` changes:
|
||||
```js
|
||||
module.exports = {
|
||||
publicPath: process.env.NODE_ENV === "production" ? "/townsquare/" : "/"
|
||||
};
|
||||
```
|
||||
|
||||
### Committing Changes
|
||||
|
||||
Commit messages should be verbose enough to allow someone else to follow your changes and should include references to issues that are being worked on.
|
||||
|
@ -64,6 +78,10 @@ $ npm run lint
|
|||
|
||||
- **`dist`**: contains built files for distribution.
|
||||
|
||||
- **`public`**: static assets and templates that don't need to be built dynamically.
|
||||
|
||||
- **`server`**: NodeJS code for the live session backend server.
|
||||
|
||||
- **`src`**: contains the source code. The codebase is written in ES2015.
|
||||
|
||||
- **`assets`**: contains all graphical assets like images, fonts, icons, etc.
|
||||
|
@ -73,9 +91,13 @@ $ npm run lint
|
|||
- **`fonts`**: webfonts used on the page
|
||||
|
||||
- **`icons`**: character token icons
|
||||
|
||||
- **`sounds`**: sound effects used on the page
|
||||
|
||||
- **`components`**: the internal components used in the project
|
||||
|
||||
- **`modals`**: the modals have a separate subfolder
|
||||
|
||||
- **`store`**: the VueX data store and modules
|
||||
|
||||
- **`modules`**: VueX modules that live in their own namespace
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "townsquare",
|
||||
"version": "2.1.1",
|
||||
"version": "2.6.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "townsquare",
|
||||
"version": "2.1.1",
|
||||
"version": "2.6.0",
|
||||
"description": "Blood on the Clocktower Town Square",
|
||||
"author": "Steffen Baumgart",
|
||||
"scripts": {
|
||||
|
|
|
@ -102,6 +102,15 @@ export default {
|
|||
if (this.session.isSpectator) return;
|
||||
this.$store.commit("toggleModal", "roles");
|
||||
break;
|
||||
case "v":
|
||||
if (this.session.voteHistory.length) {
|
||||
this.$store.commit("toggleModal", "voteHistory");
|
||||
}
|
||||
break;
|
||||
case "s":
|
||||
if (this.session.isSpectator) return;
|
||||
this.$store.commit("toggleNight");
|
||||
break;
|
||||
case "escape":
|
||||
this.$store.commit("toggleModal");
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 95 KiB |
Binary file not shown.
After Width: | Height: | Size: 86 KiB |
Binary file not shown.
After Width: | Height: | Size: 151 KiB |
|
@ -43,10 +43,7 @@
|
|||
<li @click="toggleNight" v-if="!session.isSpectator">
|
||||
<template v-if="!grimoire.isNight">Switch to Night</template>
|
||||
<template v-if="grimoire.isNight">Switch to Day</template>
|
||||
<em
|
||||
><font-awesome-icon
|
||||
:icon="['fas', grimoire.isNight ? 'sun' : 'cloud-moon']"
|
||||
/></em>
|
||||
<em>[S]</em>
|
||||
</li>
|
||||
<li @click="toggleNightOrder" v-if="players.length">
|
||||
Night order
|
||||
|
@ -114,8 +111,7 @@
|
|||
v-if="session.voteHistory.length"
|
||||
@click="toggleModal('voteHistory')"
|
||||
>
|
||||
Nomination history
|
||||
<em><font-awesome-icon icon="hand-paper"/></em>
|
||||
Nomination history<em>[V]</em>
|
||||
</li>
|
||||
<li @click="leaveSession" v-if="session.sessionId">
|
||||
Leave Session
|
||||
|
@ -239,16 +235,9 @@ export default {
|
|||
}
|
||||
},
|
||||
copySessionUrl() {
|
||||
// check for clipboard permissions
|
||||
navigator.permissions
|
||||
.query({ name: "clipboard-write" })
|
||||
.then(({ state }) => {
|
||||
if (state === "granted" || state === "prompt") {
|
||||
const url = window.location.href.split("#")[0];
|
||||
const link = url + "#" + this.session.sessionId;
|
||||
navigator.clipboard.writeText(link);
|
||||
}
|
||||
});
|
||||
const url = window.location.href.split("#")[0];
|
||||
const link = url + "#" + this.session.sessionId;
|
||||
navigator.clipboard.writeText(link);
|
||||
},
|
||||
distributeRoles() {
|
||||
if (this.session.isSpectator) return;
|
||||
|
|
|
@ -675,7 +675,7 @@ li.move:not(.from) .player .overlay svg.move {
|
|||
border: 10px solid transparent;
|
||||
border-right-color: black;
|
||||
right: 100%;
|
||||
bottom: 7px;
|
||||
bottom: 5px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
|
|
|
@ -161,11 +161,13 @@ export default {
|
|||
},
|
||||
voters: function() {
|
||||
const nomination = this.session.nomination[1];
|
||||
const voters = this.session.votes.map((vote, index) =>
|
||||
vote ? this.players[index].name : ""
|
||||
);
|
||||
const voters = Array(this.players.length)
|
||||
.fill("")
|
||||
.map((x, index) =>
|
||||
this.session.votes[index] ? this.players[index].name : ""
|
||||
);
|
||||
const reorder = [
|
||||
...voters.slice(nomination + 1, this.players.length),
|
||||
...voters.slice(nomination + 1),
|
||||
...voters.slice(0, nomination + 1)
|
||||
];
|
||||
return reorder.slice(0, this.session.lockedVote - 1).filter(n => !!n);
|
||||
|
@ -178,15 +180,15 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
countdown() {
|
||||
this.$store.commit("session/setVoteInProgress", true);
|
||||
this.$store.commit("session/lockVote", 0);
|
||||
this.$store.commit("session/setVoteInProgress", true);
|
||||
this.voteTimer = setInterval(() => {
|
||||
this.start();
|
||||
}, 4000);
|
||||
},
|
||||
start() {
|
||||
this.$store.commit("session/setVoteInProgress", true);
|
||||
this.$store.commit("session/lockVote", 1);
|
||||
this.$store.commit("session/setVoteInProgress", true);
|
||||
clearInterval(this.voteTimer);
|
||||
this.voteTimer = setInterval(() => {
|
||||
this.$store.commit("session/lockVote");
|
||||
|
|
|
@ -54,14 +54,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
copy: function() {
|
||||
// check for clipboard permissions
|
||||
navigator.permissions
|
||||
.query({ name: "clipboard-write" })
|
||||
.then(({ state }) => {
|
||||
if (state === "granted" || state === "prompt") {
|
||||
navigator.clipboard.writeText(this.input || this.gamestate);
|
||||
}
|
||||
});
|
||||
navigator.clipboard.writeText(this.input || this.gamestate);
|
||||
},
|
||||
load: function() {
|
||||
if (this.session.isSpectator) return;
|
||||
|
|
|
@ -25,6 +25,17 @@
|
|||
>
|
||||
<span class="name">
|
||||
{{ role.name }}
|
||||
<template v-if="role.players.length">
|
||||
<br />
|
||||
<small
|
||||
v-for="(player, index) in role.players"
|
||||
:class="{ dead: player.isDead }"
|
||||
:key="index"
|
||||
>{{
|
||||
player.name + (role.players.length > index + 1 ? "," : "")
|
||||
}}</small
|
||||
>
|
||||
</template>
|
||||
</span>
|
||||
<span
|
||||
class="icon"
|
||||
|
@ -53,6 +64,17 @@
|
|||
></span>
|
||||
<span class="name">
|
||||
{{ role.name }}
|
||||
<template v-if="role.players.length">
|
||||
<br />
|
||||
<small
|
||||
v-for="(player, index) in role.players"
|
||||
:class="{ dead: player.isDead }"
|
||||
:key="index"
|
||||
>{{
|
||||
player.name + (role.players.length > index + 1 ? "," : "")
|
||||
}}</small
|
||||
>
|
||||
</template>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -68,11 +90,6 @@ export default {
|
|||
components: {
|
||||
Modal
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
roleSelection: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
rolesFirstNight: function() {
|
||||
const rolesFirstNight = [];
|
||||
|
@ -83,23 +100,22 @@ export default {
|
|||
id: "evil",
|
||||
name: "Minion info",
|
||||
firstNight: 2,
|
||||
team: "minion"
|
||||
team: "minion",
|
||||
players: this.players.filter(p => p.role.team === "minion")
|
||||
},
|
||||
{
|
||||
id: "evil",
|
||||
name: "Demon info & bluffs",
|
||||
firstNight: 4,
|
||||
team: "demon"
|
||||
team: "demon",
|
||||
players: this.players.filter(p => p.role.team === "demon")
|
||||
}
|
||||
);
|
||||
}
|
||||
this.roles.forEach(role => {
|
||||
if (
|
||||
role.firstNight &&
|
||||
(role.team !== "traveler" ||
|
||||
this.players.some(p => p.role.id === role.id))
|
||||
) {
|
||||
rolesFirstNight.push(role);
|
||||
const players = this.players.filter(p => p.role.id === role.id);
|
||||
if (role.firstNight && (role.team !== "traveler" || players.length)) {
|
||||
rolesFirstNight.push(Object.assign({ players }, role));
|
||||
}
|
||||
});
|
||||
this.fabled
|
||||
|
@ -113,12 +129,9 @@ export default {
|
|||
rolesOtherNight: function() {
|
||||
const rolesOtherNight = [];
|
||||
this.roles.forEach(role => {
|
||||
if (
|
||||
role.otherNight &&
|
||||
(role.team !== "traveler" ||
|
||||
this.players.some(p => p.role.id === role.id))
|
||||
) {
|
||||
rolesOtherNight.push(role);
|
||||
const players = this.players.filter(p => p.role.id === role.id);
|
||||
if (role.otherNight && (role.team !== "traveler" || players.length)) {
|
||||
rolesOtherNight.push(Object.assign({ players }, role));
|
||||
}
|
||||
});
|
||||
this.fabled
|
||||
|
@ -179,57 +192,42 @@ h4 {
|
|||
}
|
||||
|
||||
.fabled {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $fabled;
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: $fabled;
|
||||
.name {
|
||||
background: linear-gradient(90deg, $fabled, transparent 35%);
|
||||
.night .other & {
|
||||
background: linear-gradient(-90deg, $fabled, transparent 35%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.townsfolk {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $townsfolk;
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: $townsfolk;
|
||||
.name {
|
||||
background: linear-gradient(90deg, $townsfolk, transparent 35%);
|
||||
.night .other & {
|
||||
background: linear-gradient(-90deg, $townsfolk, transparent 35%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.outsider {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $outsider;
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: $outsider;
|
||||
.name {
|
||||
background: linear-gradient(90deg, $outsider, transparent 35%);
|
||||
.night .other & {
|
||||
background: linear-gradient(-90deg, $outsider, transparent 35%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.minion {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $minion;
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: $minion;
|
||||
.name {
|
||||
background: linear-gradient(90deg, $minion, transparent 35%);
|
||||
.night .other & {
|
||||
background: linear-gradient(-90deg, $minion, transparent 35%);
|
||||
}
|
||||
}
|
||||
}
|
||||
.demon {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $demon;
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: $demon;
|
||||
.name {
|
||||
background: linear-gradient(90deg, $demon, transparent 35%);
|
||||
.night .other & {
|
||||
background: linear-gradient(-90deg, $demon, transparent 35%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,20 +235,15 @@ ul {
|
|||
li {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
align-content: center;
|
||||
/*background: linear-gradient(0deg, #ffffff0f, transparent);*/
|
||||
border-radius: 10px;
|
||||
margin-bottom: 3px;
|
||||
.icon {
|
||||
width: 6vh;
|
||||
background-size: cover;
|
||||
background-position: 0 -5px;
|
||||
background-position: 0 0;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
margin: 0 10px;
|
||||
text-align: center;
|
||||
border-left: 1px solid #ffffff1f;
|
||||
border-right: 1px solid #ffffff1f;
|
||||
margin: 0 2px;
|
||||
&:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
|
@ -261,19 +254,18 @@ ul {
|
|||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: 15%;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
font-family: "Papyrus", sans-serif;
|
||||
font-size: 110%;
|
||||
}
|
||||
.player {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 1;
|
||||
text-align: right;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.ability {
|
||||
flex-grow: 1;
|
||||
padding: 5px;
|
||||
border-left: 1px solid rgba(255, 255, 255, 0.4);
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.4);
|
||||
small {
|
||||
color: #888;
|
||||
margin-right: 5px;
|
||||
&.dead {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&.legend {
|
||||
|
@ -307,28 +299,23 @@ ul {
|
|||
.headline {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid white;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.4);
|
||||
padding: 5px 10px;
|
||||
border-radius: 0;
|
||||
text-align: center;
|
||||
}
|
||||
.icon {
|
||||
border-color: white;
|
||||
}
|
||||
.name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.first {
|
||||
.icon {
|
||||
border-right: 0;
|
||||
.name {
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
.other {
|
||||
li .name {
|
||||
text-align: left;
|
||||
}
|
||||
.icon {
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,11 +56,6 @@ export default {
|
|||
components: {
|
||||
Modal
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
roleSelection: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
rolesGrouped: function() {
|
||||
const rolesGrouped = {};
|
||||
|
@ -136,7 +131,6 @@ h4 {
|
|||
|
||||
.townsfolk {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $townsfolk;
|
||||
&:before,
|
||||
|
@ -147,7 +141,6 @@ h4 {
|
|||
}
|
||||
.outsider {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $outsider;
|
||||
&:before,
|
||||
|
@ -158,7 +151,6 @@ h4 {
|
|||
}
|
||||
.minion {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $minion;
|
||||
&:before,
|
||||
|
@ -169,7 +161,6 @@ h4 {
|
|||
}
|
||||
.demon {
|
||||
.name,
|
||||
.player,
|
||||
h4 {
|
||||
color: $demon;
|
||||
&:before,
|
||||
|
@ -208,7 +199,6 @@ ul {
|
|||
width: 15%;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
font-family: "Papyrus", sans-serif;
|
||||
font-size: 110%;
|
||||
}
|
||||
.player {
|
||||
|
@ -216,6 +206,8 @@ ul {
|
|||
flex-shrink: 1;
|
||||
text-align: right;
|
||||
margin: 0 10px;
|
||||
color: #888;
|
||||
font-size: smaller;
|
||||
}
|
||||
.ability {
|
||||
flex-grow: 1;
|
||||
|
@ -230,6 +222,7 @@ ul {
|
|||
height: auto;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
color: #fff;
|
||||
}
|
||||
.icon:after {
|
||||
padding-top: 0;
|
||||
|
|
|
@ -82,6 +82,21 @@ export default {
|
|||
}))
|
||||
];
|
||||
});
|
||||
|
||||
// add out of script traveler reminders
|
||||
this.$store.state.otherTravelers.forEach(role => {
|
||||
if (players.some(p => p.role.id === role.id)) {
|
||||
reminders = [
|
||||
...reminders,
|
||||
...role.reminders.map(name => ({
|
||||
role: role.id,
|
||||
image: role.image,
|
||||
name
|
||||
}))
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
reminders.push({ role: "good", name: "Good" });
|
||||
reminders.push({ role: "evil", name: "Evil" });
|
||||
reminders.push({ role: "custom", name: "Custom note" });
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<template>
|
||||
<Modal
|
||||
v-if="modals.role && availableRoles.length"
|
||||
@close="toggleModal('role')"
|
||||
>
|
||||
<Modal v-if="modals.role && availableRoles.length" @close="close">
|
||||
<h3>
|
||||
Choose a new character for
|
||||
{{
|
||||
|
@ -11,7 +8,7 @@
|
|||
: "bluffing"
|
||||
}}
|
||||
</h3>
|
||||
<ul class="tokens">
|
||||
<ul class="tokens" v-if="tab === 'editionRoles' || !otherTravelers.size">
|
||||
<li
|
||||
v-for="role in availableRoles"
|
||||
:class="[role.team]"
|
||||
|
@ -21,6 +18,33 @@
|
|||
<Token :role="role" />
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="tokens" v-if="tab === 'otherTravelers' && otherTravelers.size">
|
||||
<li
|
||||
v-for="role in otherTravelers.values()"
|
||||
:class="[role.team]"
|
||||
:key="role.id"
|
||||
@click="setRole(role)"
|
||||
>
|
||||
<Token :role="role" />
|
||||
</li>
|
||||
</ul>
|
||||
<div
|
||||
class="button-group"
|
||||
v-if="playerIndex >= 0 && otherTravelers.size && !session.isSpectator"
|
||||
>
|
||||
<span
|
||||
class="button"
|
||||
:class="{ townsfolk: tab === 'editionRoles' }"
|
||||
@click="tab = 'editionRoles'"
|
||||
>Edtition Roles</span
|
||||
>
|
||||
<span
|
||||
class="button"
|
||||
:class="{ townsfolk: tab === 'otherTravelers' }"
|
||||
@click="tab = 'otherTravelers'"
|
||||
>Other Travelers</span
|
||||
>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
|
@ -50,7 +74,13 @@ export default {
|
|||
return availableRoles;
|
||||
},
|
||||
...mapState(["modals", "roles", "session"]),
|
||||
...mapState("players", ["players"])
|
||||
...mapState("players", ["players"]),
|
||||
...mapState(["otherTravelers"])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tab: "editionRoles"
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
setRole(role) {
|
||||
|
@ -72,6 +102,10 @@ export default {
|
|||
}
|
||||
this.$store.commit("toggleModal", "role");
|
||||
},
|
||||
close() {
|
||||
this.tab = "editionRoles";
|
||||
this.toggleModal("role");
|
||||
},
|
||||
...mapMutations(["toggleModal"])
|
||||
}
|
||||
};
|
||||
|
|
|
@ -15,22 +15,50 @@
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Time</td>
|
||||
<td>Nominator</td>
|
||||
<td>Nominee</td>
|
||||
<td>Type</td>
|
||||
<td>Votes</td>
|
||||
<td>Majority</td>
|
||||
<td><font-awesome-icon icon="hand-paper" /> Hand up</td>
|
||||
<td>
|
||||
<font-awesome-icon icon="user-friends" />
|
||||
Voters
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(vote, index) in session.voteHistory" :key="index">
|
||||
<td>
|
||||
{{
|
||||
vote.timestamp
|
||||
.getHours()
|
||||
.toString()
|
||||
.padStart(2, "0")
|
||||
}}:{{
|
||||
vote.timestamp
|
||||
.getMinutes()
|
||||
.toString()
|
||||
.padStart(2, "0")
|
||||
}}
|
||||
</td>
|
||||
<td>{{ vote.nominator }}</td>
|
||||
<td>{{ vote.nominee }}</td>
|
||||
<td>{{ vote.type }}</td>
|
||||
<td>{{ vote.majority }}</td>
|
||||
<td>
|
||||
{{ vote.votes.length }}
|
||||
<font-awesome-icon icon="user-friends" />
|
||||
<font-awesome-icon icon="hand-paper" />
|
||||
</td>
|
||||
<td>
|
||||
{{ vote.majority }}
|
||||
<font-awesome-icon
|
||||
:icon="[
|
||||
'fas',
|
||||
vote.votes.length >= vote.majority ? 'check-square' : 'square'
|
||||
]"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{{ vote.votes.join(", ") }}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -89,13 +117,16 @@ thead td {
|
|||
}
|
||||
|
||||
tbody {
|
||||
td:nth-child(1) {
|
||||
td:nth-child(2) {
|
||||
color: $townsfolk;
|
||||
}
|
||||
td:nth-child(2) {
|
||||
td:nth-child(3) {
|
||||
color: $demon;
|
||||
}
|
||||
td:nth-child(4) {
|
||||
td:nth-child(5) {
|
||||
text-align: center;
|
||||
}
|
||||
td:nth-child(6) {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ const faIcons = [
|
|||
"SearchMinus",
|
||||
"SearchPlus",
|
||||
"Square",
|
||||
"Sun",
|
||||
"TheaterMasks",
|
||||
"Times",
|
||||
"TimesCircle",
|
||||
|
|
|
@ -44,6 +44,12 @@
|
|||
.player > .name {
|
||||
top: 0;
|
||||
}
|
||||
.player > .menu {
|
||||
bottom: 0;
|
||||
&:before {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Old phones
|
||||
|
|
1578
src/roles.json
1578
src/roles.json
File diff suppressed because it is too large
Load Diff
|
@ -25,6 +25,19 @@ const getRolesByEdition = (edition = editionJSON[0]) => {
|
|||
);
|
||||
};
|
||||
|
||||
const getTravelersNotInEdition = (edition = editionJSON[0]) => {
|
||||
return new Map(
|
||||
rolesJSON
|
||||
.filter(
|
||||
r =>
|
||||
r.team === "traveler" &&
|
||||
r.edition !== edition.id &&
|
||||
!edition.roles.includes(r.id)
|
||||
)
|
||||
.map(role => [role.id, role])
|
||||
);
|
||||
};
|
||||
|
||||
// base definition for custom roles
|
||||
const imageBase =
|
||||
"https://raw.githubusercontent.com/bra1n/townsquare/main/src/assets/icons/";
|
||||
|
@ -70,6 +83,7 @@ export default new Vuex.Store({
|
|||
},
|
||||
edition: editionJSONbyId.get("tb"),
|
||||
roles: getRolesByEdition(),
|
||||
otherTravelers: getTravelersNotInEdition(),
|
||||
fabled
|
||||
},
|
||||
getters: {
|
||||
|
@ -180,11 +194,18 @@ export default new Vuex.Store({
|
|||
// convert to Map
|
||||
.map(role => [role.id, role])
|
||||
);
|
||||
// update extraTravelers map to only show travelers not in this script
|
||||
state.otherTravelers = new Map(
|
||||
rolesJSON
|
||||
.filter(r => r.team === "traveler" && !roles.some(i => i.id === r.id))
|
||||
.map(role => [role.id, role])
|
||||
);
|
||||
},
|
||||
setEdition(state, edition) {
|
||||
if (editionJSONbyId.has(edition.id)) {
|
||||
state.edition = editionJSONbyId.get(edition.id);
|
||||
state.roles = getRolesByEdition(state.edition);
|
||||
state.otherTravelers = getTravelersNotInEdition(state.edition);
|
||||
} else {
|
||||
state.edition = edition;
|
||||
}
|
||||
|
|
|
@ -84,6 +84,7 @@ const actions = {
|
|||
name,
|
||||
id
|
||||
}));
|
||||
commit("setFabled", { fabled: [] });
|
||||
}
|
||||
commit("set", players);
|
||||
commit("setBluff");
|
||||
|
@ -94,6 +95,7 @@ const mutations = {
|
|||
clear(state) {
|
||||
state.players = [];
|
||||
state.bluffs = [];
|
||||
state.fabled = [];
|
||||
},
|
||||
set(state, players = []) {
|
||||
state.players = players;
|
||||
|
|
|
@ -72,8 +72,8 @@ const mutations = {
|
|||
addHistory(state, players) {
|
||||
if (!state.nomination || state.lockedVote <= players.length) return;
|
||||
const isBanishment = players[state.nomination[1]].role.team === "traveler";
|
||||
console.log(isBanishment);
|
||||
state.voteHistory.push({
|
||||
timestamp: new Date(),
|
||||
nominator: players[state.nomination[0]].name,
|
||||
nominee: players[state.nomination[1]].name,
|
||||
type: isBanishment ? "Banishment" : "Execution",
|
||||
|
|
|
@ -443,8 +443,11 @@ class LiveSession {
|
|||
value: {}
|
||||
});
|
||||
} else {
|
||||
// load role
|
||||
const role = this._store.state.roles.get(value);
|
||||
// load role, first from session, the global, then fail gracefully
|
||||
const role =
|
||||
this._store.state.roles.get(value) ||
|
||||
this._store.getters.rolesJSONbyId.get(value) ||
|
||||
{};
|
||||
this._store.commit("players/update", {
|
||||
player,
|
||||
property: "role",
|
||||
|
|
Loading…
Reference in New Issue