mirror of
https://github.com/bra1n/townsquare.git
synced 2025-04-04 22:24:36 +00:00
Merge pull request #7 from davotronic5000/develop
upgraded vue to latest version and fixed bugs resulting from it. Sti…
This commit is contained in:
commit
b01b61ad47
17 changed files with 4842 additions and 17938 deletions
21951
package-lock.json
generated
21951
package-lock.json
generated
File diff suppressed because it is too large
Load diff
30
package.json
30
package.json
|
@ -11,26 +11,26 @@
|
||||||
},
|
},
|
||||||
"main": "App.vue",
|
"main": "App.vue",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
"@fortawesome/fontawesome-svg-core": "^6.4.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.1",
|
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/vue-fontawesome": "^0.1.10",
|
"@fortawesome/vue-fontawesome": "^3.0.3",
|
||||||
"@vue/cli-service": "^4.5.9",
|
"@vue/cli-service": "^5.0.8",
|
||||||
"prom-client": "^13.0.0",
|
"prom-client": "^13.0.0",
|
||||||
"sass": "^1.30.0",
|
"sass": "^1.30.0",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"vue": "^2.6.12",
|
"vue": "^3.3.4",
|
||||||
"vue-template-compiler": "^2.6.12",
|
"@vue/compat": "^3.3.4",
|
||||||
"vuex": "^3.6.0",
|
"vuex": "^4.1.0",
|
||||||
"ws": "^7.4.6"
|
"ws": "^7.4.6",
|
||||||
|
"vue-loader": "^17.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-eslint": "^4.5.9",
|
"@vue/compiler-sfc": "^3.3.4",
|
||||||
"@vue/eslint-config-prettier": "^6.0.0",
|
"eslint": "^8.41.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-prettier": "^3.2.0",
|
"eslint-plugin-vue": "^9.14.0",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"prettier": "^2.8.8"
|
||||||
"prettier": "^1.19.1"
|
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"botc",
|
"botc",
|
||||||
|
|
|
@ -7,7 +7,7 @@ const client = require("prom-client");
|
||||||
const register = new client.Registry();
|
const register = new client.Registry();
|
||||||
// Add a default label which is added to all metrics
|
// Add a default label which is added to all metrics
|
||||||
register.setDefaultLabels({
|
register.setDefaultLabels({
|
||||||
app: "clocktower-online"
|
app: "clocktower-online",
|
||||||
});
|
});
|
||||||
|
|
||||||
const PING_INTERVAL = 30000; // 30 seconds
|
const PING_INTERVAL = 30000; // 30 seconds
|
||||||
|
@ -22,11 +22,9 @@ if (process.env.NODE_ENV !== "development") {
|
||||||
const server = https.createServer(options);
|
const server = https.createServer(options);
|
||||||
const wss = new WebSocket.Server({
|
const wss = new WebSocket.Server({
|
||||||
...(process.env.NODE_ENV === "development" ? { port: 8081 } : { server }),
|
...(process.env.NODE_ENV === "development" ? { port: 8081 } : { server }),
|
||||||
verifyClient: info =>
|
verifyClient: (info) =>
|
||||||
info.origin &&
|
info.origin &&
|
||||||
!!info.origin.match(
|
!!info.origin.match(/^https?:\/\/(townsquare.clocktower.guru|localhost)/i),
|
||||||
/^https?:\/\/([^.]+\.github\.io|localhost|clocktower\.online|eddbra1nprivatetownsquare\.xyz)/i
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
@ -48,14 +46,14 @@ const metrics = {
|
||||||
help: "Concurrent Players",
|
help: "Concurrent Players",
|
||||||
collect() {
|
collect() {
|
||||||
this.set(wss.clients.size);
|
this.set(wss.clients.size);
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
channels_concurrent: new client.Gauge({
|
channels_concurrent: new client.Gauge({
|
||||||
name: "channels_concurrent",
|
name: "channels_concurrent",
|
||||||
help: "Concurrent Channels",
|
help: "Concurrent Channels",
|
||||||
collect() {
|
collect() {
|
||||||
this.set(Object.keys(channels).length);
|
this.set(Object.keys(channels).length);
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
channels_list: new client.Gauge({
|
channels_list: new client.Gauge({
|
||||||
name: "channel_players",
|
name: "channel_players",
|
||||||
|
@ -66,35 +64,35 @@ const metrics = {
|
||||||
this.set(
|
this.set(
|
||||||
{ name: channel },
|
{ name: channel },
|
||||||
channels[channel].filter(
|
channels[channel].filter(
|
||||||
ws =>
|
(ws) =>
|
||||||
ws &&
|
ws &&
|
||||||
(ws.readyState === WebSocket.OPEN ||
|
(ws.readyState === WebSocket.OPEN ||
|
||||||
ws.readyState === WebSocket.CONNECTING)
|
ws.readyState === WebSocket.CONNECTING)
|
||||||
).length
|
).length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
messages_incoming: new client.Counter({
|
messages_incoming: new client.Counter({
|
||||||
name: "messages_incoming",
|
name: "messages_incoming",
|
||||||
help: "Incoming messages"
|
help: "Incoming messages",
|
||||||
}),
|
}),
|
||||||
messages_outgoing: new client.Counter({
|
messages_outgoing: new client.Counter({
|
||||||
name: "messages_outgoing",
|
name: "messages_outgoing",
|
||||||
help: "Outgoing messages"
|
help: "Outgoing messages",
|
||||||
}),
|
}),
|
||||||
connection_terminated_host: new client.Counter({
|
connection_terminated_host: new client.Counter({
|
||||||
name: "connection_terminated_host",
|
name: "connection_terminated_host",
|
||||||
help: "Terminated connection due to host already present"
|
help: "Terminated connection due to host already present",
|
||||||
}),
|
}),
|
||||||
connection_terminated_spam: new client.Counter({
|
connection_terminated_spam: new client.Counter({
|
||||||
name: "connection_terminated_spam",
|
name: "connection_terminated_spam",
|
||||||
help: "Terminated connection due to message spam"
|
help: "Terminated connection due to message spam",
|
||||||
}),
|
}),
|
||||||
connection_terminated_timeout: new client.Counter({
|
connection_terminated_timeout: new client.Counter({
|
||||||
name: "connection_terminated_timeout",
|
name: "connection_terminated_timeout",
|
||||||
help: "Terminated connection due to timeout"
|
help: "Terminated connection due to timeout",
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// register metrics
|
// register metrics
|
||||||
|
@ -113,7 +111,7 @@ wss.on("connection", function connection(ws, req) {
|
||||||
ws.playerId === "host" &&
|
ws.playerId === "host" &&
|
||||||
channels[ws.channel] &&
|
channels[ws.channel] &&
|
||||||
channels[ws.channel].some(
|
channels[ws.channel].some(
|
||||||
client =>
|
(client) =>
|
||||||
client !== ws &&
|
client !== ws &&
|
||||||
client.readyState === WebSocket.OPEN &&
|
client.readyState === WebSocket.OPEN &&
|
||||||
client.playerId === "host"
|
client.playerId === "host"
|
||||||
|
@ -149,11 +147,7 @@ wss.on("connection", function connection(ws, req) {
|
||||||
metrics.connection_terminated_spam.inc();
|
metrics.connection_terminated_spam.inc();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const messageType = data
|
const messageType = data.toLocaleLowerCase().substr(1).split(",", 1).pop();
|
||||||
.toLocaleLowerCase()
|
|
||||||
.substr(1)
|
|
||||||
.split(",", 1)
|
|
||||||
.pop();
|
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
case '"ping"':
|
case '"ping"':
|
||||||
// ping messages will only be sent host -> all or all -> host
|
// ping messages will only be sent host -> all or all -> host
|
||||||
|
@ -232,7 +226,7 @@ const interval = setInterval(function ping() {
|
||||||
if (
|
if (
|
||||||
!channels[channel].length ||
|
!channels[channel].length ||
|
||||||
!channels[channel].some(
|
!channels[channel].some(
|
||||||
ws =>
|
(ws) =>
|
||||||
ws &&
|
ws &&
|
||||||
(ws.readyState === WebSocket.OPEN ||
|
(ws.readyState === WebSocket.OPEN ||
|
||||||
ws.readyState === WebSocket.CONNECTING)
|
ws.readyState === WebSocket.CONNECTING)
|
||||||
|
@ -255,6 +249,6 @@ if (process.env.NODE_ENV !== "development") {
|
||||||
server.listen(8080);
|
server.listen(8080);
|
||||||
server.on("request", (req, res) => {
|
server.on("request", (req, res) => {
|
||||||
res.setHeader("Content-Type", register.contentType);
|
res.setHeader("Content-Type", register.contentType);
|
||||||
register.metrics().then(out => res.end(out));
|
register.metrics().then((out) => res.end(out));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
43
src/App.vue
43
src/App.vue
|
@ -1,16 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
id="app"
|
id="townsquare-app"
|
||||||
@keyup="keyup"
|
@keyup="keyup"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
:class="{
|
:class="{
|
||||||
night: grimoire.isNight,
|
night: grimoire.isNight,
|
||||||
static: grimoire.isStatic
|
static: grimoire.isStatic,
|
||||||
}"
|
}"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: grimoire.background
|
backgroundImage: grimoire.background
|
||||||
? `url('${grimoire.background}')`
|
? `url('${grimoire.background}')`
|
||||||
: ''
|
: '',
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
|
@ -20,29 +20,42 @@
|
||||||
autoplay
|
autoplay
|
||||||
loop
|
loop
|
||||||
></video>
|
></video>
|
||||||
|
|
||||||
<div class="backdrop"></div>
|
<div class="backdrop"></div>
|
||||||
<transition name="blur">
|
|
||||||
<Intro v-if="!players.length"></Intro>
|
<Intro v-if="!players.length"></Intro>
|
||||||
|
|
||||||
<TownInfo v-if="players.length && !session.nomination"></TownInfo>
|
<TownInfo v-if="players.length && !session.nomination"></TownInfo>
|
||||||
|
|
||||||
<Vote v-if="session.nomination"></Vote>
|
<Vote v-if="session.nomination"></Vote>
|
||||||
</transition>
|
|
||||||
<TownSquare></TownSquare>
|
<TownSquare></TownSquare>
|
||||||
|
|
||||||
<Menu ref="menu"></Menu>
|
<Menu ref="menu"></Menu>
|
||||||
|
|
||||||
<EditionModal />
|
<EditionModal />
|
||||||
|
|
||||||
<FabledModal />
|
<FabledModal />
|
||||||
|
|
||||||
<RolesModal />
|
<RolesModal />
|
||||||
|
|
||||||
<ReferenceModal />
|
<ReferenceModal />
|
||||||
|
|
||||||
<NightOrderModal />
|
<NightOrderModal />
|
||||||
|
|
||||||
<VoteHistoryModal />
|
<VoteHistoryModal />
|
||||||
|
|
||||||
<GameStateModal />
|
<GameStateModal />
|
||||||
|
|
||||||
<Gradients />
|
<Gradients />
|
||||||
|
|
||||||
<span id="version">v{{ version }}</span>
|
<span id="version">v{{ version }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
import { version } from "../package.json";
|
import Package from "../package.json";
|
||||||
import TownSquare from "./components/TownSquare";
|
import TownSquare from "./components/TownSquare";
|
||||||
import TownInfo from "./components/TownInfo";
|
import TownInfo from "./components/TownInfo";
|
||||||
import Menu from "./components/Menu";
|
import Menu from "./components/Menu";
|
||||||
|
@ -71,15 +84,15 @@ export default {
|
||||||
Menu,
|
Menu,
|
||||||
EditionModal,
|
EditionModal,
|
||||||
RolesModal,
|
RolesModal,
|
||||||
Gradients
|
Gradients,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["grimoire", "session"]),
|
...mapState(["grimoire", "session"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
version
|
version: Package.version,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -124,8 +137,8 @@ export default {
|
||||||
case "escape":
|
case "escape":
|
||||||
this.$store.commit("toggleModal");
|
this.$store.commit("toggleModal");
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -198,6 +211,10 @@ ul {
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#townsquare-app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
@ -326,7 +343,7 @@ video#background {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Night phase backdrop */
|
/* Night phase backdrop */
|
||||||
#app > .backdrop {
|
#townsquare-app > .backdrop {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -364,7 +381,7 @@ video#background {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#app.night > .backdrop {
|
#townsquare-app.night > .backdrop {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -2,13 +2,11 @@
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<span
|
<span
|
||||||
class="nomlog-summary"
|
class="nomlog-summary"
|
||||||
v-show="session.voteHistory.length && session.sessionId"
|
v-show="session.sessionId"
|
||||||
@click="toggleModal('voteHistory')"
|
@click="toggleModal('voteHistory')"
|
||||||
:title="
|
:title="`${session.voteHistory.length} recent ${
|
||||||
`${session.voteHistory.length} recent ${
|
|
||||||
session.voteHistory.length == 1 ? 'nomination' : 'nominations'
|
session.voteHistory.length == 1 ? 'nomination' : 'nominations'
|
||||||
}`
|
}`"
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="book-dead" />
|
<font-awesome-icon icon="book-dead" />
|
||||||
{{ session.voteHistory.length }}
|
{{ session.voteHistory.length }}
|
||||||
|
@ -17,32 +15,39 @@
|
||||||
class="session"
|
class="session"
|
||||||
:class="{
|
:class="{
|
||||||
spectator: session.isSpectator,
|
spectator: session.isSpectator,
|
||||||
reconnecting: session.isReconnecting
|
reconnecting: session.isReconnecting,
|
||||||
}"
|
}"
|
||||||
v-if="session.sessionId"
|
v-if="session.sessionId"
|
||||||
@click="leaveSession"
|
@click="leaveSession"
|
||||||
:title="
|
:title="`${session.playerCount} other players in this session${
|
||||||
`${session.playerCount} other players in this session${
|
|
||||||
session.ping ? ' (' + session.ping + 'ms latency)' : ''
|
session.ping ? ' (' + session.ping + 'ms latency)' : ''
|
||||||
}`
|
}`"
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="broadcast-tower" />
|
<font-awesome-icon icon="broadcast-tower" />
|
||||||
{{ session.playerCount }}
|
{{ session.playerCount }}
|
||||||
</span>
|
</span>
|
||||||
<div class="menu" :class="{ open: grimoire.isMenuOpen }">
|
<div class="menu" :class="{ open: grimoire.isMenuOpen }">
|
||||||
<font-awesome-icon icon="cog" @click="toggleMenu" />
|
<span @click="toggleMenu">
|
||||||
|
<font-awesome-icon icon="cog" class="menuCog" />
|
||||||
|
</span>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li class="tabs" :class="tab">
|
<li class="tabs" :class="tab">
|
||||||
<font-awesome-icon icon="book-open" @click="tab = 'grimoire'" />
|
<span @click="tab = 'grimoire'">
|
||||||
<font-awesome-icon icon="broadcast-tower" @click="tab = 'session'" />
|
<font-awesome-icon icon="book-open" />
|
||||||
<font-awesome-icon
|
</span>
|
||||||
icon="users"
|
<span @click="tab = 'session'">
|
||||||
v-if="!session.isSpectator"
|
<font-awesome-icon icon="broadcast-tower" />
|
||||||
@click="tab = 'players'"
|
</span>
|
||||||
/>
|
<span @click="tab = 'players'">
|
||||||
<font-awesome-icon icon="theater-masks" @click="tab = 'characters'" />
|
<font-awesome-icon icon="users" v-if="!session.isSpectator" />
|
||||||
<font-awesome-icon icon="question" @click="tab = 'help'" />
|
</span>
|
||||||
|
<span @click="tab = 'characters'">
|
||||||
|
<font-awesome-icon icon="theater-masks" />
|
||||||
|
</span>
|
||||||
|
<span @click="tab = 'help'">
|
||||||
|
<font-awesome-icon icon="question" />
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<template v-if="tab === 'grimoire'">
|
<template v-if="tab === 'grimoire'">
|
||||||
|
@ -64,7 +69,7 @@
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
:icon="[
|
:icon="[
|
||||||
'fas',
|
'fas',
|
||||||
grimoire.isNightOrder ? 'check-square' : 'square'
|
grimoire.isNightOrder ? 'check-square' : 'square',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</em>
|
</em>
|
||||||
|
@ -72,15 +77,13 @@
|
||||||
<li v-if="players.length">
|
<li v-if="players.length">
|
||||||
Zoom
|
Zoom
|
||||||
<em>
|
<em>
|
||||||
<font-awesome-icon
|
<span @click="setZoom(grimoire.zoom - 1)" class="zoom">
|
||||||
@click="setZoom(grimoire.zoom - 1)"
|
<font-awesome-icon icon="search-minus" />
|
||||||
icon="search-minus"
|
</span>
|
||||||
/>
|
|
||||||
{{ Math.round(100 + grimoire.zoom * 10) }}%
|
{{ Math.round(100 + grimoire.zoom * 10) }}%
|
||||||
<font-awesome-icon
|
<span @click="setZoom(grimoire.zoom + 1)" class="zoom">
|
||||||
@click="setZoom(grimoire.zoom + 1)"
|
<font-awesome-icon icon="search-plus" />
|
||||||
icon="search-plus"
|
</span>
|
||||||
/>
|
|
||||||
</em>
|
</em>
|
||||||
</li>
|
</li>
|
||||||
<li @click="setBackground">
|
<li @click="setBackground">
|
||||||
|
@ -93,7 +96,7 @@
|
||||||
><font-awesome-icon
|
><font-awesome-icon
|
||||||
:icon="[
|
:icon="[
|
||||||
'fas',
|
'fas',
|
||||||
grimoire.isImageOptIn ? 'check-square' : 'square'
|
grimoire.isImageOptIn ? 'check-square' : 'square',
|
||||||
]"
|
]"
|
||||||
/></em>
|
/></em>
|
||||||
</li>
|
</li>
|
||||||
|
@ -118,9 +121,7 @@
|
||||||
<li class="headline" v-if="session.sessionId">
|
<li class="headline" v-if="session.sessionId">
|
||||||
{{ session.isSpectator ? "Playing" : "Hosting" }}
|
{{ session.isSpectator ? "Playing" : "Hosting" }}
|
||||||
</li>
|
</li>
|
||||||
<li class="headline" v-else>
|
<li class="headline" v-else>Live Session</li>
|
||||||
Live Session
|
|
||||||
</li>
|
|
||||||
<template v-if="!session.sessionId">
|
<template v-if="!session.sessionId">
|
||||||
<li @click="hostSession">Host (Storyteller)<em>[H]</em></li>
|
<li @click="hostSession">Host (Storyteller)<em>[H]</em></li>
|
||||||
<li @click="joinSession">Join (Player)<em>[J]</em></li>
|
<li @click="joinSession">Join (Player)<em>[J]</em></li>
|
||||||
|
@ -236,11 +237,11 @@ import { mapMutations, mapState } from "vuex";
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(["grimoire", "session", "edition"]),
|
...mapState(["grimoire", "session", "edition"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tab: "grimoire"
|
tab: "grimoire",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -353,9 +354,9 @@ export default {
|
||||||
"toggleNightOrder",
|
"toggleNightOrder",
|
||||||
"toggleStatic",
|
"toggleStatic",
|
||||||
"setZoom",
|
"setZoom",
|
||||||
"toggleModal"
|
"toggleModal",
|
||||||
])
|
]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -389,7 +390,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> span {
|
span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
@ -432,7 +433,7 @@ export default {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
> svg {
|
span .menuCog {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
border: 3px solid black;
|
border: 3px solid black;
|
||||||
|
@ -442,6 +443,7 @@ export default {
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
border-radius: 10px 10px 0 0;
|
border-radius: 10px 10px 0 0;
|
||||||
padding: 5px 5px 15px;
|
padding: 5px 5px 15px;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@ -473,10 +475,10 @@ export default {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
|
|
||||||
&.tabs {
|
tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
svg {
|
span svg {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
|
@ -492,11 +494,11 @@ export default {
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.grimoire .fa-book-open,
|
&.grimoire span .fa-book-open,
|
||||||
&.players .fa-users,
|
&.players span .fa-users,
|
||||||
&.characters .fa-theater-masks,
|
&.characters span .fa-theater-masks,
|
||||||
&.session .fa-broadcast-tower,
|
&.session span .fa-broadcast-tower,
|
||||||
&.help .fa-question {
|
&.help span .fa-question {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
to bottom,
|
to bottom,
|
||||||
$townsfolk 0%,
|
$townsfolk 0%,
|
||||||
|
|
|
@ -47,41 +47,33 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Overlay icons -->
|
<!-- Overlay icons -->
|
||||||
<div class="overlay">
|
<div class="overlay" @click="vote()">
|
||||||
<font-awesome-icon
|
<font-awesome-icon icon="hand-paper" class="vote" title="Hand UP" />
|
||||||
icon="hand-paper"
|
</div>
|
||||||
class="vote"
|
<div class="overlay" @click="vote()">
|
||||||
title="Hand UP"
|
<font-awesome-icon icon="times" class="vote" title="Hand DOWN" />
|
||||||
@click="vote()"
|
</div>
|
||||||
/>
|
<div class="overlay" @click="cancel()">
|
||||||
<font-awesome-icon
|
<font-awesome-icon icon="times-circle" class="cancel" title="Cancel" />
|
||||||
icon="times"
|
</div>
|
||||||
class="vote"
|
<div class="overlay" @click="swapPlayer(player)">
|
||||||
title="Hand DOWN"
|
|
||||||
@click="vote()"
|
|
||||||
/>
|
|
||||||
<font-awesome-icon
|
|
||||||
icon="times-circle"
|
|
||||||
class="cancel"
|
|
||||||
title="Cancel"
|
|
||||||
@click="cancel()"
|
|
||||||
/>
|
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
icon="exchange-alt"
|
icon="exchange-alt"
|
||||||
class="swap"
|
class="swap"
|
||||||
@click="swapPlayer(player)"
|
|
||||||
title="Swap seats with this player"
|
title="Swap seats with this player"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="overlay" @click="movePlayer(player)">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
icon="redo-alt"
|
icon="redo-alt"
|
||||||
class="move"
|
class="move"
|
||||||
@click="movePlayer(player)"
|
|
||||||
title="Move player to this seat"
|
title="Move player to this seat"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="overlay" @click="nominatePlayer(player)">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
icon="hand-point-right"
|
icon="hand-point-right"
|
||||||
class="nominate"
|
class="nominate"
|
||||||
@click="nominatePlayer(player)"
|
|
||||||
title="Nominate this player"
|
title="Nominate this player"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -95,13 +87,14 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Ghost vote icon -->
|
<!-- Ghost vote icon -->
|
||||||
|
<div @click="updatePlayer('isVoteless', true)">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
icon="vote-yea"
|
icon="vote-yea"
|
||||||
class="has-vote"
|
class="has-vote"
|
||||||
v-if="player.isDead && !player.isVoteless"
|
v-if="player.isDead && !player.isVoteless"
|
||||||
@click="updatePlayer('isVoteless', true)"
|
|
||||||
title="Ghost vote"
|
title="Ghost vote"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- On block icon -->
|
<!-- On block icon -->
|
||||||
<div class="marked">
|
<div class="marked">
|
||||||
|
@ -166,9 +159,7 @@
|
||||||
:class="{ disabled: player.id && player.id !== session.playerId }"
|
:class="{ disabled: player.id && player.id !== session.playerId }"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="chair" />
|
<font-awesome-icon icon="chair" />
|
||||||
<template v-if="!player.id">
|
<template v-if="!player.id"> Claim seat </template>
|
||||||
Claim seat
|
|
||||||
</template>
|
|
||||||
<template v-else-if="player.id === session.playerId">
|
<template v-else-if="player.id === session.playerId">
|
||||||
Vacate seat
|
Vacate seat
|
||||||
</template>
|
</template>
|
||||||
|
@ -554,7 +545,7 @@ export default {
|
||||||
fill: url(#default);
|
fill: url(#default);
|
||||||
}
|
}
|
||||||
&:hover *,
|
&:hover *,
|
||||||
&.fa-hand-paper * {
|
&.fa-hand * {
|
||||||
fill: url(#demon);
|
fill: url(#demon);
|
||||||
}
|
}
|
||||||
&.fa-times * {
|
&.fa-times * {
|
||||||
|
@ -564,14 +555,14 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// other player voted yes, but is not locked yet
|
// other player voted yes, but is not locked yet
|
||||||
#townsquare.vote .player.vote-yes .overlay svg.vote.fa-hand-paper {
|
#townsquare.vote .player.vote-yes .overlay svg.vote.fa-hand {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// you voted yes | a locked vote yes | a locked vote no
|
// you voted yes | a locked vote yes | a locked vote no
|
||||||
#townsquare.vote .player.you.vote-yes .overlay svg.vote.fa-hand-paper,
|
#townsquare.vote .player.you.vote-yes .overlay svg.vote.fa-hand,
|
||||||
#townsquare.vote .player.vote-lock.vote-yes .overlay svg.vote.fa-hand-paper,
|
#townsquare.vote .player.vote-lock.vote-yes .overlay svg.vote.fa-hand,
|
||||||
#townsquare.vote .player.vote-lock:not(.vote-yes) .overlay svg.vote.fa-times {
|
#townsquare.vote .player.vote-lock:not(.vote-yes) .overlay svg.vote.fa-times {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div class="token" @click="setRole" :class="[role.id]">
|
<div class="token" @click="setRole" :class="[role.id]">
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="icon"
|
class="icon"
|
||||||
v-if="role.id"
|
v-if="role.id"
|
||||||
|
@ -8,42 +10,52 @@
|
||||||
role.image && grimoire.isImageOptIn
|
role.image && grimoire.isImageOptIn
|
||||||
? role.image
|
? role.image
|
||||||
: require('../assets/icons/' + (role.imageAlt || role.id) + '.png')
|
: require('../assets/icons/' + (role.imageAlt || role.id) + '.png')
|
||||||
})`
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="leaf-left"
|
class="leaf-left"
|
||||||
v-if="role.firstNight || role.firstNightReminder"
|
v-if="role.firstNight || role.firstNightReminder"
|
||||||
></span>
|
></span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="leaf-right"
|
class="leaf-right"
|
||||||
v-if="role.otherNight || role.otherNightReminder"
|
v-if="role.otherNight || role.otherNightReminder"
|
||||||
></span>
|
></span>
|
||||||
|
|
||||||
<span v-if="reminderLeaves" :class="['leaf-top' + reminderLeaves]"></span>
|
<span v-if="reminderLeaves" :class="['leaf-top' + reminderLeaves]"></span>
|
||||||
|
|
||||||
<span class="leaf-orange" v-if="role.setup"></span>
|
<span class="leaf-orange" v-if="role.setup"></span>
|
||||||
|
|
||||||
<svg viewBox="0 0 150 150" class="name">
|
<svg viewBox="0 0 150 150" class="name">
|
||||||
|
|
||||||
<path
|
<path
|
||||||
d="M 13 75 C 13 160, 138 160, 138 75"
|
d="M 13 75 C 13 160, 138 160, 138 75"
|
||||||
id="curve"
|
id="curve"
|
||||||
fill="transparent"
|
fill="transparent"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<text
|
<text
|
||||||
width="150"
|
width="150"
|
||||||
x="66.6%"
|
x="66.6%"
|
||||||
text-anchor="middle"
|
text-anchor="middle"
|
||||||
class="label mozilla"
|
class="label mozilla"
|
||||||
:font-size="role.name | nameToFontSize"
|
:font-size="nameToFontSize(role.name)"
|
||||||
>
|
>
|
||||||
<textPath xlink:href="#curve">
|
|
||||||
{{ role.name }}
|
<textPath xlink:href="#curve"> {{ role.name }} </textPath>
|
||||||
</textPath>
|
|
||||||
</text>
|
</text>
|
||||||
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<div class="edition" :class="[`edition-${role.edition}`, role.team]"></div>
|
<div class="edition" :class="[`edition-${role.edition}`, role.team]"></div>
|
||||||
<div class="ability" v-if="role.ability">
|
|
||||||
{{ role.ability }}
|
<div class="ability" v-if="role.ability"> {{ role.ability }} </div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -54,8 +66,8 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
role: {
|
role: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => ({})
|
default: () => ({}),
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
reminderLeaves: function() {
|
reminderLeaves: function() {
|
||||||
|
@ -64,19 +76,19 @@ export default {
|
||||||
(this.role.remindersGlobal || []).length
|
(this.role.remindersGlobal || []).length
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
...mapState(["grimoire"])
|
...mapState(["grimoire"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
filters: {
|
|
||||||
nameToFontSize: name => (name && name.length > 10 ? "90%" : "110%")
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
|
nameToFontSize(name) {
|
||||||
|
name && name.length > 10 ? "90%" : "110%";
|
||||||
|
},
|
||||||
setRole() {
|
setRole() {
|
||||||
this.$emit("set-role");
|
this.$emit("set-role");
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -233,3 +245,4 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
|
|
||||||
<ul class="info">
|
<ul class="info">
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="edition"
|
class="edition"
|
||||||
:class="['edition-' + edition.id]"
|
:class="['edition-' + edition.id]"
|
||||||
|
@ -8,67 +10,94 @@
|
||||||
edition.logo && grimoire.isImageOptIn
|
edition.logo && grimoire.isImageOptIn
|
||||||
? edition.logo
|
? edition.logo
|
||||||
: require('../assets/editions/' + edition.id + '.png')
|
: require('../assets/editions/' + edition.id + '.png')
|
||||||
})`
|
})`,
|
||||||
}"
|
}"
|
||||||
></li>
|
></li>
|
||||||
|
|
||||||
<li v-if="players.length - teams.traveler < 5">
|
<li v-if="players.length - teams.traveler < 5">
|
||||||
Please add more players!
|
Please add more players!
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
|
|
||||||
<span class="meta" v-if="!edition.isOfficial">
|
<span class="meta" v-if="!edition.isOfficial">
|
||||||
{{ edition.name }}
|
{{ edition.name }} {{ edition.author ? "by " + edition.author : "" }}
|
||||||
{{ edition.author ? "by " + edition.author : "" }}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ players.length }} <font-awesome-icon class="players" icon="users" />
|
{{ players.length }}
|
||||||
|
<font-awesome-icon class="players" icon="users" />
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ teams.alive }}
|
{{ teams.alive }}
|
||||||
<font-awesome-icon class="alive" icon="heartbeat" />
|
<font-awesome-icon class="alive" icon="heartbeat" />
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ teams.votes }} <font-awesome-icon class="votes" icon="vote-yea" />
|
{{ teams.votes }}
|
||||||
|
<font-awesome-icon class="votes" icon="vote-yea" />
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li v-if="players.length - teams.traveler >= 5">
|
<li v-if="players.length - teams.traveler >= 5">
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ teams.townsfolk }}
|
{{ teams.townsfolk }}
|
||||||
<font-awesome-icon class="townsfolk" icon="user-friends" />
|
<font-awesome-icon class="townsfolk" icon="user-friends" />
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ teams.outsider }}
|
{{ teams.outsider }}
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
class="outsider"
|
class="outsider"
|
||||||
:icon="teams.outsider > 1 ? 'user-friends' : 'user'"
|
:icon="teams.outsider > 1 ? 'user-friends' : 'user'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ teams.minion }}
|
{{ teams.minion }}
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
class="minion"
|
class="minion"
|
||||||
:icon="teams.minion > 1 ? 'user-friends' : 'user'"
|
:icon="teams.minion > 1 ? 'user-friends' : 'user'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
{{ teams.demon }}
|
{{ teams.demon }}
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
class="demon"
|
class="demon"
|
||||||
:icon="teams.demon > 1 ? 'user-friends' : 'user'"
|
:icon="teams.demon > 1 ? 'user-friends' : 'user'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="teams.traveler">
|
<span v-if="teams.traveler">
|
||||||
{{ teams.traveler }}
|
{{ teams.traveler }}
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
class="traveler"
|
class="traveler"
|
||||||
:icon="teams.traveler > 1 ? 'user-friends' : 'user'"
|
:icon="teams.traveler > 1 ? 'user-friends' : 'user'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span v-if="grimoire.isNight">
|
<span v-if="grimoire.isNight">
|
||||||
Night phase
|
Night phase
|
||||||
<font-awesome-icon :icon="['fas', 'cloud-moon']" />
|
<font-awesome-icon :icon="['fas', 'cloud-moon']" />
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -80,7 +109,7 @@ export default {
|
||||||
teams: function() {
|
teams: function() {
|
||||||
const { players } = this.$store.state.players;
|
const { players } = this.$store.state.players;
|
||||||
const nonTravelers = this.$store.getters["players/nonTravelers"];
|
const nonTravelers = this.$store.getters["players/nonTravelers"];
|
||||||
const alive = players.filter(player => player.isDead !== true).length;
|
const alive = players.filter((player) => player.isDead !== true).length;
|
||||||
return {
|
return {
|
||||||
...gameJSON[nonTravelers - 5],
|
...gameJSON[nonTravelers - 5],
|
||||||
traveler: players.length - nonTravelers,
|
traveler: players.length - nonTravelers,
|
||||||
|
@ -88,13 +117,13 @@ export default {
|
||||||
votes:
|
votes:
|
||||||
alive +
|
alive +
|
||||||
players.filter(
|
players.filter(
|
||||||
player => player.isDead === true && player.isVoteless !== true
|
(player) => player.isDead === true && player.isVoteless !== true
|
||||||
).length
|
).length,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
...mapState(["edition", "grimoire"]),
|
...mapState(["edition", "grimoire"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -178,3 +207,4 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
:class="{
|
:class="{
|
||||||
public: grimoire.isPublic,
|
public: grimoire.isPublic,
|
||||||
spectator: session.isSpectator,
|
spectator: session.isSpectator,
|
||||||
vote: session.nomination
|
vote: session.nomination,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<ul class="circle" :class="['size-' + players.length]">
|
<ul class="circle" :class="['size-' + players.length]">
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
from: Math.max(swap, move, nominate) === index,
|
from: Math.max(swap, move, nominate) === index,
|
||||||
swap: swap > -1,
|
swap: swap > -1,
|
||||||
move: move > -1,
|
move: move > -1,
|
||||||
nominate: nominate > -1
|
nominate: nominate > -1,
|
||||||
}"
|
}"
|
||||||
></Player>
|
></Player>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -31,10 +31,16 @@
|
||||||
>
|
>
|
||||||
<h3>
|
<h3>
|
||||||
<span v-if="session.isSpectator">Other characters</span>
|
<span v-if="session.isSpectator">Other characters</span>
|
||||||
|
|
||||||
<span v-else>Demon bluffs</span>
|
<span v-else>Demon bluffs</span>
|
||||||
<font-awesome-icon icon="times-circle" @click.stop="toggleBluffs" />
|
|
||||||
<font-awesome-icon icon="plus-circle" @click.stop="toggleBluffs" />
|
<span @click.stop="toggleBluffs">
|
||||||
|
<font-awesome-icon
|
||||||
|
:icon="['fas', isBluffsOpen ? 'minus-circle' : 'plus-circle']"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
v-for="index in bluffSize"
|
v-for="index in bluffSize"
|
||||||
|
@ -49,9 +55,13 @@
|
||||||
<div class="fabled" :class="{ closed: !isFabledOpen }" v-if="fabled.length">
|
<div class="fabled" :class="{ closed: !isFabledOpen }" v-if="fabled.length">
|
||||||
<h3>
|
<h3>
|
||||||
<span>Fabled</span>
|
<span>Fabled</span>
|
||||||
<font-awesome-icon icon="times-circle" @click.stop="toggleFabled" />
|
<span @click.stop="toggleFabled">
|
||||||
<font-awesome-icon icon="plus-circle" @click.stop="toggleFabled" />
|
<font-awesome-icon
|
||||||
|
:icon="['fas', isFabledOpen ? 'minus-circle' : 'plus-circle']"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li
|
<li
|
||||||
v-for="(role, index) in fabled"
|
v-for="(role, index) in fabled"
|
||||||
|
@ -63,25 +73,30 @@
|
||||||
v-if="nightOrder.get(role).first && grimoire.isNightOrder"
|
v-if="nightOrder.get(role).first && grimoire.isNightOrder"
|
||||||
>
|
>
|
||||||
<em>{{ nightOrder.get(role).first }}.</em>
|
<em>{{ nightOrder.get(role).first }}.</em>
|
||||||
<span v-if="role.firstNightReminder">{{
|
|
||||||
role.firstNightReminder
|
<span v-if="role.firstNightReminder">
|
||||||
}}</span>
|
{{ role.firstNightReminder }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="night-order other"
|
class="night-order other"
|
||||||
v-if="nightOrder.get(role).other && grimoire.isNightOrder"
|
v-if="nightOrder.get(role).other && grimoire.isNightOrder"
|
||||||
>
|
>
|
||||||
<em>{{ nightOrder.get(role).other }}.</em>
|
<em>{{ nightOrder.get(role).other }}.</em>
|
||||||
<span v-if="role.otherNightReminder">{{
|
|
||||||
role.otherNightReminder
|
<span v-if="role.otherNightReminder">
|
||||||
}}</span>
|
{{ role.otherNightReminder }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Token :role="role"></Token>
|
<Token :role="role"></Token>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ReminderModal :player-index="selectedPlayer"></ReminderModal>
|
<ReminderModal :player-index="selectedPlayer"></ReminderModal>
|
||||||
|
|
||||||
<RoleModal :player-index="selectedPlayer"></RoleModal>
|
<RoleModal :player-index="selectedPlayer"></RoleModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -98,12 +113,12 @@ export default {
|
||||||
Player,
|
Player,
|
||||||
Token,
|
Token,
|
||||||
RoleModal,
|
RoleModal,
|
||||||
ReminderModal
|
ReminderModal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({ nightOrder: "players/nightOrder" }),
|
...mapGetters({ nightOrder: "players/nightOrder" }),
|
||||||
...mapState(["grimoire", "roles", "session"]),
|
...mapState(["grimoire", "roles", "session"]),
|
||||||
...mapState("players", ["players", "bluffs", "fabled"])
|
...mapState("players", ["players", "bluffs", "fabled"]),
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -113,7 +128,7 @@ export default {
|
||||||
move: -1,
|
move: -1,
|
||||||
nominate: -1,
|
nominate: -1,
|
||||||
isBluffsOpen: true,
|
isBluffsOpen: true,
|
||||||
isFabledOpen: true
|
isFabledOpen: true,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -170,7 +185,7 @@ export default {
|
||||||
// update nomination array if removed player has lower index
|
// update nomination array if removed player has lower index
|
||||||
this.$store.commit("session/setNomination", [
|
this.$store.commit("session/setNomination", [
|
||||||
nomination[0] > playerIndex ? nomination[0] - 1 : nomination[0],
|
nomination[0] > playerIndex ? nomination[0] - 1 : nomination[0],
|
||||||
nomination[1] > playerIndex ? nomination[1] - 1 : nomination[1]
|
nomination[1] > playerIndex ? nomination[1] - 1 : nomination[1],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,7 +201,7 @@ export default {
|
||||||
if (this.session.nomination) {
|
if (this.session.nomination) {
|
||||||
// update nomination if one of the involved players is swapped
|
// update nomination if one of the involved players is swapped
|
||||||
const swapTo = this.players.indexOf(to);
|
const swapTo = this.players.indexOf(to);
|
||||||
const updatedNomination = this.session.nomination.map(nom => {
|
const updatedNomination = this.session.nomination.map((nom) => {
|
||||||
if (nom === this.swap) return swapTo;
|
if (nom === this.swap) return swapTo;
|
||||||
if (nom === swapTo) return this.swap;
|
if (nom === swapTo) return this.swap;
|
||||||
return nom;
|
return nom;
|
||||||
|
@ -200,7 +215,7 @@ export default {
|
||||||
}
|
}
|
||||||
this.$store.commit("players/swap", [
|
this.$store.commit("players/swap", [
|
||||||
this.swap,
|
this.swap,
|
||||||
this.players.indexOf(to)
|
this.players.indexOf(to),
|
||||||
]);
|
]);
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
|
@ -214,7 +229,7 @@ export default {
|
||||||
if (this.session.nomination) {
|
if (this.session.nomination) {
|
||||||
// update nomination if it is affected by the move
|
// update nomination if it is affected by the move
|
||||||
const moveTo = this.players.indexOf(to);
|
const moveTo = this.players.indexOf(to);
|
||||||
const updatedNomination = this.session.nomination.map(nom => {
|
const updatedNomination = this.session.nomination.map((nom) => {
|
||||||
if (nom === this.move) return moveTo;
|
if (nom === this.move) return moveTo;
|
||||||
if (nom > this.move && nom <= moveTo) return nom - 1;
|
if (nom > this.move && nom <= moveTo) return nom - 1;
|
||||||
if (nom < this.move && nom >= moveTo) return nom + 1;
|
if (nom < this.move && nom >= moveTo) return nom + 1;
|
||||||
|
@ -229,7 +244,7 @@ export default {
|
||||||
}
|
}
|
||||||
this.$store.commit("players/move", [
|
this.$store.commit("players/move", [
|
||||||
this.move,
|
this.move,
|
||||||
this.players.indexOf(to)
|
this.players.indexOf(to),
|
||||||
]);
|
]);
|
||||||
this.cancel();
|
this.cancel();
|
||||||
}
|
}
|
||||||
|
@ -251,8 +266,8 @@ export default {
|
||||||
this.move = -1;
|
this.move = -1;
|
||||||
this.swap = -1;
|
this.swap = -1;
|
||||||
this.nominate = -1;
|
this.nominate = -1;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -301,14 +316,14 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin on-circle($item-count) {
|
@mixin on-circle($item-count) {
|
||||||
$angle: (360 / $item-count);
|
$angle: calc(360 / $item-count);
|
||||||
$rot: 0;
|
$rot: 0;
|
||||||
|
|
||||||
// rotation and tooltip placement
|
// rotation and tooltip placement
|
||||||
@for $i from 1 through $item-count {
|
@for $i from 1 through $item-count {
|
||||||
&:nth-child(#{$i}) {
|
&:nth-child(#{$i}) {
|
||||||
transform: rotate($rot * 1deg);
|
transform: rotate($rot * 1deg);
|
||||||
@if $i - 1 <= $item-count / 2 {
|
@if $i - 1 <= calc($item-count / 2) {
|
||||||
// first half of players
|
// first half of players
|
||||||
z-index: $item-count - $i + 1;
|
z-index: $item-count - $i + 1;
|
||||||
// open menu on the left
|
// open menu on the left
|
||||||
|
@ -372,15 +387,15 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// move reminders closer to the sides of the circle
|
// move reminders closer to the sides of the circle
|
||||||
$q: $item-count / 4;
|
$q: calc($item-count / 4);
|
||||||
$x: $i - 1;
|
$x: $i - 1;
|
||||||
@if $x < $q or ($x >= $item-count / 2 and $x < $q * 3) {
|
@if $x < $q or ($x >= calc($item-count / 2) and $x < $q * 3) {
|
||||||
.player {
|
.player {
|
||||||
margin-bottom: -10% + 20% * (1 - ($x % $q / $q));
|
margin-bottom: -10% + 20% * (1 - ($x % $q / $q));
|
||||||
}
|
}
|
||||||
} @else {
|
} @else {
|
||||||
.player {
|
.player {
|
||||||
margin-bottom: -10% + 20% * ($x % $q / $q);
|
margin-bottom: -10% + 20% * ($x % calc($q / $q));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,15 +28,14 @@
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
Time per player:
|
Time per player:
|
||||||
<font-awesome-icon
|
<span @mousedown.prevent="setVotingSpeed(-500)">
|
||||||
@mousedown.prevent="setVotingSpeed(-500)"
|
<font-awesome-icon icon="minus-circle"
|
||||||
icon="minus-circle"
|
/></span>
|
||||||
/>
|
|
||||||
{{ session.votingSpeed / 1000 }}s
|
{{ session.votingSpeed / 1000 }}s
|
||||||
<font-awesome-icon
|
<span @mousedown.prevent="setVotingSpeed(500)">
|
||||||
@mousedown.prevent="setVotingSpeed(500)"
|
<font-awesome-icon icon="plus-circle" />
|
||||||
icon="plus-circle"
|
</span>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<div
|
<div
|
||||||
|
@ -71,9 +70,7 @@
|
||||||
>
|
>
|
||||||
Mark for execution
|
Mark for execution
|
||||||
</div>
|
</div>
|
||||||
<div class="button" @click="removeMarked">
|
<div class="button" @click="removeMarked">Clear mark</div>
|
||||||
Clear mark
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="canVote">
|
<template v-else-if="canVote">
|
||||||
|
@ -97,9 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div v-else-if="!player">
|
<div v-else-if="!player">Please claim a seat to vote.</div>
|
||||||
Please claim a seat to vote.
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<transition name="blur">
|
<transition name="blur">
|
||||||
<div
|
<div
|
||||||
|
@ -183,7 +178,8 @@ export default {
|
||||||
...voters.slice(nomination + 1),
|
...voters.slice(nomination + 1),
|
||||||
...voters.slice(0, nomination + 1),
|
...voters.slice(0, nomination + 1),
|
||||||
];
|
];
|
||||||
return (this.session.lockedVote
|
return (
|
||||||
|
this.session.lockedVote
|
||||||
? reorder.slice(0, this.session.lockedVote - 1)
|
? reorder.slice(0, this.session.lockedVote - 1)
|
||||||
: reorder
|
: reorder
|
||||||
).filter((n) => !!n);
|
).filter((n) => !!n);
|
||||||
|
|
|
@ -10,17 +10,22 @@
|
||||||
@click.stop=""
|
@click.stop=""
|
||||||
>
|
>
|
||||||
<div class="top-right-buttons">
|
<div class="top-right-buttons">
|
||||||
<font-awesome-icon
|
<div
|
||||||
@click="isMaximized = !isMaximized"
|
@click="isMaximized = !isMaximized"
|
||||||
class="top-right-button"
|
style="display: inline-block"
|
||||||
:icon="['fas', isMaximized ? 'window-minimize' : 'window-maximize']"
|
>
|
||||||
/>
|
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
@click="close"
|
|
||||||
class="top-right-button"
|
class="top-right-button"
|
||||||
icon="times-circle"
|
:icon="[
|
||||||
|
'fas',
|
||||||
|
isMaximized ? 'window-minimize' : 'window-maximize',
|
||||||
|
]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div @click="close" style="display: inline-block">
|
||||||
|
<font-awesome-icon class="top-right-button" icon="times-circle" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="slot">
|
<div class="slot">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,16 +36,17 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
emits: ["close"],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
isMaximized: false
|
isMaximized: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close() {
|
close() {
|
||||||
this.$emit("close");
|
this.$emit("close");
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
@close="toggleModal('nightOrder')"
|
@close="toggleModal('nightOrder')"
|
||||||
v-if="modals.nightOrder && roles.size"
|
v-if="modals.nightOrder && roles.size"
|
||||||
>
|
>
|
||||||
|
<div @click="toggleModal('reference')">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
@click="toggleModal('reference')"
|
|
||||||
icon="address-card"
|
icon="address-card"
|
||||||
class="toggle"
|
class="toggle"
|
||||||
title="Show Character Reference"
|
title="Show Character Reference"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
Night Order
|
Night Order
|
||||||
<font-awesome-icon icon="cloud-moon" />
|
<font-awesome-icon icon="cloud-moon" />
|
||||||
|
@ -47,7 +49,7 @@
|
||||||
: require('../../assets/icons/' +
|
: require('../../assets/icons/' +
|
||||||
(role.imageAlt || role.id) +
|
(role.imageAlt || role.id) +
|
||||||
'.png')
|
'.png')
|
||||||
})`
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span class="reminder" v-if="role.firstNightReminder">
|
<span class="reminder" v-if="role.firstNightReminder">
|
||||||
|
@ -72,7 +74,7 @@
|
||||||
: require('../../assets/icons/' +
|
: require('../../assets/icons/' +
|
||||||
(role.imageAlt || role.id) +
|
(role.imageAlt || role.id) +
|
||||||
'.png')
|
'.png')
|
||||||
})`
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span class="name">
|
<span class="name">
|
||||||
|
@ -104,7 +106,7 @@ import { mapMutations, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
rolesFirstNight: function () {
|
rolesFirstNight: function () {
|
||||||
|
@ -117,33 +119,33 @@ export default {
|
||||||
name: "Minion info",
|
name: "Minion info",
|
||||||
firstNight: 5,
|
firstNight: 5,
|
||||||
team: "minion",
|
team: "minion",
|
||||||
players: this.players.filter(p => p.role.team === "minion"),
|
players: this.players.filter((p) => p.role.team === "minion"),
|
||||||
firstNightReminder:
|
firstNightReminder:
|
||||||
"• If more than one Minion, they all make eye contact with each other. " +
|
"• If more than one Minion, they all make eye contact with each other. " +
|
||||||
"• Show the “This is the Demon” card. Point to the Demon."
|
"• Show the “This is the Demon” card. Point to the Demon.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "evil",
|
id: "evil",
|
||||||
name: "Demon info & bluffs",
|
name: "Demon info & bluffs",
|
||||||
firstNight: 8,
|
firstNight: 8,
|
||||||
team: "demon",
|
team: "demon",
|
||||||
players: this.players.filter(p => p.role.team === "demon"),
|
players: this.players.filter((p) => p.role.team === "demon"),
|
||||||
firstNightReminder:
|
firstNightReminder:
|
||||||
"• Show the “These are your minions” card. Point to each Minion. " +
|
"• Show the “These are your minions” card. Point to each Minion. " +
|
||||||
"• Show the “These characters are not in play” card. Show 3 character tokens of good " +
|
"• Show the “These characters are not in play” card. Show 3 character tokens of good " +
|
||||||
"characters not in play."
|
"characters not in play.",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach((role) => {
|
||||||
const players = this.players.filter(p => p.role.id === role.id);
|
const players = this.players.filter((p) => p.role.id === role.id);
|
||||||
if (role.firstNight && (role.team !== "traveler" || players.length)) {
|
if (role.firstNight && (role.team !== "traveler" || players.length)) {
|
||||||
rolesFirstNight.push(Object.assign({ players }, role));
|
rolesFirstNight.push(Object.assign({ players }, role));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.fabled
|
this.fabled
|
||||||
.filter(({ firstNight }) => firstNight)
|
.filter(({ firstNight }) => firstNight)
|
||||||
.forEach(fabled => {
|
.forEach((fabled) => {
|
||||||
rolesFirstNight.push(Object.assign({ players: [] }, fabled));
|
rolesFirstNight.push(Object.assign({ players: [] }, fabled));
|
||||||
});
|
});
|
||||||
rolesFirstNight.sort((a, b) => a.firstNight - b.firstNight);
|
rolesFirstNight.sort((a, b) => a.firstNight - b.firstNight);
|
||||||
|
@ -151,26 +153,26 @@ export default {
|
||||||
},
|
},
|
||||||
rolesOtherNight: function () {
|
rolesOtherNight: function () {
|
||||||
const rolesOtherNight = [];
|
const rolesOtherNight = [];
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach((role) => {
|
||||||
const players = this.players.filter(p => p.role.id === role.id);
|
const players = this.players.filter((p) => p.role.id === role.id);
|
||||||
if (role.otherNight && (role.team !== "traveler" || players.length)) {
|
if (role.otherNight && (role.team !== "traveler" || players.length)) {
|
||||||
rolesOtherNight.push(Object.assign({ players }, role));
|
rolesOtherNight.push(Object.assign({ players }, role));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.fabled
|
this.fabled
|
||||||
.filter(({ otherNight }) => otherNight)
|
.filter(({ otherNight }) => otherNight)
|
||||||
.forEach(fabled => {
|
.forEach((fabled) => {
|
||||||
rolesOtherNight.push(Object.assign({ players: [] }, fabled));
|
rolesOtherNight.push(Object.assign({ players: [] }, fabled));
|
||||||
});
|
});
|
||||||
rolesOtherNight.sort((a, b) => a.otherNight - b.otherNight);
|
rolesOtherNight.sort((a, b) => a.otherNight - b.otherNight);
|
||||||
return rolesOtherNight;
|
return rolesOtherNight;
|
||||||
},
|
},
|
||||||
...mapState(["roles", "modals", "edition", "grimoire"]),
|
...mapState(["roles", "modals", "edition", "grimoire"]),
|
||||||
...mapState("players", ["players", "fabled"])
|
...mapState("players", ["players", "fabled"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,14 @@
|
||||||
@close="toggleModal('reference')"
|
@close="toggleModal('reference')"
|
||||||
v-if="modals.reference && roles.size"
|
v-if="modals.reference && roles.size"
|
||||||
>
|
>
|
||||||
|
<div @click="toggleModal('nightOrder')">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
@click="toggleModal('nightOrder')"
|
|
||||||
icon="cloud-moon"
|
icon="cloud-moon"
|
||||||
class="toggle"
|
class="toggle"
|
||||||
title="Show Night Order"
|
title="Show Night Order"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>
|
<h3>
|
||||||
Character Reference
|
Character Reference
|
||||||
<font-awesome-icon icon="address-card" />
|
<font-awesome-icon icon="address-card" />
|
||||||
|
@ -35,7 +37,7 @@
|
||||||
: require('../../assets/icons/' +
|
: require('../../assets/icons/' +
|
||||||
(role.imageAlt || role.id) +
|
(role.imageAlt || role.id) +
|
||||||
'.png')
|
'.png')
|
||||||
})`
|
})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<div class="role">
|
<div class="role">
|
||||||
|
@ -62,7 +64,7 @@
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${require('../../assets/icons/' +
|
backgroundImage: `url(${require('../../assets/icons/' +
|
||||||
jinx.first.id +
|
jinx.first.id +
|
||||||
'.png')})`
|
'.png')})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
|
@ -70,7 +72,7 @@
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url(${require('../../assets/icons/' +
|
backgroundImage: `url(${require('../../assets/icons/' +
|
||||||
jinx.second.id +
|
jinx.second.id +
|
||||||
'.png')})`
|
'.png')})`,
|
||||||
}"
|
}"
|
||||||
></span>
|
></span>
|
||||||
<div class="role">
|
<div class="role">
|
||||||
|
@ -93,7 +95,7 @@ import { mapMutations, mapState } from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
/**
|
/**
|
||||||
|
@ -102,14 +104,14 @@ export default {
|
||||||
*/
|
*/
|
||||||
jinxed: function () {
|
jinxed: function () {
|
||||||
const jinxed = [];
|
const jinxed = [];
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach((role) => {
|
||||||
if (this.jinxes.get(role.id)) {
|
if (this.jinxes.get(role.id)) {
|
||||||
this.jinxes.get(role.id).forEach((reason, second) => {
|
this.jinxes.get(role.id).forEach((reason, second) => {
|
||||||
if (this.roles.get(second)) {
|
if (this.roles.get(second)) {
|
||||||
jinxed.push({
|
jinxed.push({
|
||||||
first: role,
|
first: role,
|
||||||
second: this.roles.get(second),
|
second: this.roles.get(second),
|
||||||
reason
|
reason,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -119,7 +121,7 @@ export default {
|
||||||
},
|
},
|
||||||
rolesGrouped: function () {
|
rolesGrouped: function () {
|
||||||
const rolesGrouped = {};
|
const rolesGrouped = {};
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach((role) => {
|
||||||
if (!rolesGrouped[role.team]) {
|
if (!rolesGrouped[role.team]) {
|
||||||
rolesGrouped[role.team] = [];
|
rolesGrouped[role.team] = [];
|
||||||
}
|
}
|
||||||
|
@ -141,11 +143,11 @@ export default {
|
||||||
return players;
|
return players;
|
||||||
},
|
},
|
||||||
...mapState(["roles", "modals", "edition", "grimoire", "jinxes"]),
|
...mapState(["roles", "modals", "edition", "grimoire", "jinxes"]),
|
||||||
...mapState("players", ["players"])
|
...mapState("players", ["players"]),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,13 @@
|
||||||
@close="toggleModal('roles')"
|
@close="toggleModal('roles')"
|
||||||
>
|
>
|
||||||
<h3>Select the characters for {{ nonTravelers }} players:</h3>
|
<h3>Select the characters for {{ nonTravelers }} players:</h3>
|
||||||
|
|
||||||
<ul class="tokens" v-for="(teamRoles, team) in roleSelection" :key="team">
|
<ul class="tokens" v-for="(teamRoles, team) in roleSelection" :key="team">
|
||||||
<li class="count" :class="[team]">
|
<li class="count" :class="[team]">
|
||||||
{{ teamRoles.reduce((a, { selected }) => a + selected, 0) }} /
|
{{ teamRoles.reduce((a, { selected }) => a + selected, 0) }} /
|
||||||
{{ game[nonTravelers - 5][team] }}
|
{{ game[nonTravelers - 5][team] }}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
v-for="role in teamRoles"
|
v-for="role in teamRoles"
|
||||||
:class="[role.team, role.selected ? 'selected' : '']"
|
:class="[role.team, role.selected ? 'selected' : '']"
|
||||||
|
@ -17,40 +19,51 @@
|
||||||
@click="role.selected = role.selected ? 0 : 1"
|
@click="role.selected = role.selected ? 0 : 1"
|
||||||
>
|
>
|
||||||
<Token :role="role" />
|
<Token :role="role" />
|
||||||
|
|
||||||
<font-awesome-icon icon="exclamation-triangle" v-if="role.setup" />
|
<font-awesome-icon icon="exclamation-triangle" v-if="role.setup" />
|
||||||
|
|
||||||
<div class="buttons" v-if="allowMultiple">
|
<div class="buttons" v-if="allowMultiple">
|
||||||
<font-awesome-icon
|
<div @click.stop="role.selected--">
|
||||||
icon="minus-circle"
|
<font-awesome-icon icon="minus-circle" />
|
||||||
@click.stop="role.selected--"
|
</div>
|
||||||
/>
|
|
||||||
<span>{{ role.selected > 1 ? "x" + role.selected : "" }}</span>
|
<span>{{ role.selected > 1 ? "x" + role.selected : "" }}</span>
|
||||||
<font-awesome-icon icon="plus-circle" @click.stop="role.selected++" />
|
|
||||||
|
<div @click.stop="role.selected++">
|
||||||
|
<font-awesome-icon icon="plus-circle" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="warning" v-if="hasSelectedSetupRoles">
|
<div class="warning" v-if="hasSelectedSetupRoles">
|
||||||
<font-awesome-icon icon="exclamation-triangle" />
|
<font-awesome-icon icon="exclamation-triangle" />
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
Warning: there are characters selected that modify the game setup! The
|
Warning: there are characters selected that modify the game setup! The
|
||||||
randomizer does not account for these characters.
|
randomizer does not account for these characters.
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label class="multiple" :class="{ checked: allowMultiple }">
|
<label class="multiple" :class="{ checked: allowMultiple }">
|
||||||
<font-awesome-icon :icon="allowMultiple ? 'check-square' : 'square'" />
|
<font-awesome-icon :icon="allowMultiple ? 'check-square' : 'square'" />
|
||||||
|
|
||||||
<input type="checkbox" name="allow-multiple" v-model="allowMultiple" />
|
<input type="checkbox" name="allow-multiple" v-model="allowMultiple" />
|
||||||
Allow duplicate characters
|
Allow duplicate characters
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<div
|
<div
|
||||||
class="button"
|
class="button"
|
||||||
@click="assignRoles"
|
@click="assignRoles"
|
||||||
:class="{
|
:class="{
|
||||||
disabled: selectedRoles > nonTravelers || !selectedRoles
|
disabled: selectedRoles > nonTravelers || !selectedRoles,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="people-arrows" />
|
<font-awesome-icon icon="people-arrows" />
|
||||||
Assign {{ selectedRoles }} characters randomly
|
Assign {{ selectedRoles }} characters randomly
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="button" @click="selectRandomRoles">
|
<div class="button" @click="selectRandomRoles">
|
||||||
<font-awesome-icon icon="random" />
|
<font-awesome-icon icon="random" />
|
||||||
Shuffle characters
|
Shuffle characters
|
||||||
|
@ -65,53 +78,55 @@ import gameJSON from "./../../game";
|
||||||
import Token from "./../Token";
|
import Token from "./../Token";
|
||||||
import { mapGetters, mapMutations, mapState } from "vuex";
|
import { mapGetters, mapMutations, mapState } from "vuex";
|
||||||
|
|
||||||
const randomElement = arr => arr[Math.floor(Math.random() * arr.length)];
|
const randomElement = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Token,
|
Token,
|
||||||
Modal
|
Modal,
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
roleSelection: {},
|
roleSelection: {},
|
||||||
game: gameJSON,
|
game: gameJSON,
|
||||||
allowMultiple: false
|
allowMultiple: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
selectedRoles: function () {
|
selectedRoles: function () {
|
||||||
return Object.values(this.roleSelection)
|
return Object.values(this.roleSelection)
|
||||||
.map(roles => roles.reduce((a, { selected }) => a + selected, 0))
|
.map((roles) => roles.reduce((a, { selected }) => a + selected, 0))
|
||||||
.reduce((a, b) => a + b, 0);
|
.reduce((a, b) => a + b, 0);
|
||||||
},
|
},
|
||||||
hasSelectedSetupRoles: function () {
|
hasSelectedSetupRoles: function () {
|
||||||
return Object.values(this.roleSelection).some(roles =>
|
return Object.values(this.roleSelection).some((roles) =>
|
||||||
roles.some(role => role.selected && role.setup)
|
roles.some((role) => role.selected && role.setup)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
...mapState(["roles", "modals"]),
|
...mapState(["roles", "modals"]),
|
||||||
...mapState("players", ["players"]),
|
...mapState("players", ["players"]),
|
||||||
...mapGetters({ nonTravelers: "players/nonTravelers" })
|
...mapGetters({ nonTravelers: "players/nonTravelers" }),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
selectRandomRoles() {
|
selectRandomRoles() {
|
||||||
this.roleSelection = {};
|
this.roleSelection = {};
|
||||||
this.roles.forEach(role => {
|
this.roles.forEach((role) => {
|
||||||
if (!this.roleSelection[role.team]) {
|
if (!this.roleSelection[role.team]) {
|
||||||
this.$set(this.roleSelection, role.team, []);
|
this.roleSelection[role.team] = [];
|
||||||
|
//this.$set(this.roleSelection, role.team, []);
|
||||||
}
|
}
|
||||||
this.roleSelection[role.team].push(role);
|
this.roleSelection[role.team].push(role);
|
||||||
this.$set(role, "selected", 0);
|
role["selected"] = 0;
|
||||||
|
//this.$set(role, "selected", 0);
|
||||||
});
|
});
|
||||||
delete this.roleSelection["traveler"];
|
delete this.roleSelection["traveler"];
|
||||||
const playerCount = Math.max(5, this.nonTravelers);
|
const playerCount = Math.max(5, this.nonTravelers);
|
||||||
const composition = this.game[playerCount - 5];
|
const composition = this.game[playerCount - 5];
|
||||||
Object.keys(composition).forEach(team => {
|
Object.keys(composition).forEach((team) => {
|
||||||
for (let x = 0; x < composition[team]; x++) {
|
for (let x = 0; x < composition[team]; x++) {
|
||||||
if (this.roleSelection[team]) {
|
if (this.roleSelection[team]) {
|
||||||
const available = this.roleSelection[team].filter(
|
const available = this.roleSelection[team].filter(
|
||||||
role => !role.selected
|
(role) => !role.selected
|
||||||
);
|
);
|
||||||
if (available.length) {
|
if (available.length) {
|
||||||
randomElement(available).selected = 1;
|
randomElement(available).selected = 1;
|
||||||
|
@ -124,30 +139,30 @@ export default {
|
||||||
if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
|
if (this.selectedRoles <= this.nonTravelers && this.selectedRoles) {
|
||||||
// generate list of selected roles and randomize it
|
// generate list of selected roles and randomize it
|
||||||
const roles = Object.values(this.roleSelection)
|
const roles = Object.values(this.roleSelection)
|
||||||
.map(roles =>
|
.map((roles) =>
|
||||||
roles
|
roles
|
||||||
// duplicate roles selected more than once and filter unselected
|
// duplicate roles selected more than once and filter unselected
|
||||||
.reduce((a, r) => [...a, ...Array(r.selected).fill(r)], [])
|
.reduce((a, r) => [...a, ...Array(r.selected).fill(r)], [])
|
||||||
)
|
)
|
||||||
// flatten into a single array
|
// flatten into a single array
|
||||||
.reduce((a, b) => [...a, ...b], [])
|
.reduce((a, b) => [...a, ...b], [])
|
||||||
.map(a => [Math.random(), a])
|
.map((a) => [Math.random(), a])
|
||||||
.sort((a, b) => a[0] - b[0])
|
.sort((a, b) => a[0] - b[0])
|
||||||
.map(a => a[1]);
|
.map((a) => a[1]);
|
||||||
this.players.forEach(player => {
|
this.players.forEach((player) => {
|
||||||
if (player.role.team !== "traveler" && roles.length) {
|
if (player.role.team !== "traveler" && roles.length) {
|
||||||
const value = roles.pop();
|
const value = roles.pop();
|
||||||
this.$store.commit("players/update", {
|
this.$store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value
|
value,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.$store.commit("toggleModal", "roles");
|
this.$store.commit("toggleModal", "roles");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...mapMutations(["toggleModal"])
|
...mapMutations(["toggleModal"]),
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
if (!Object.keys(this.roleSelection).length) {
|
if (!Object.keys(this.roleSelection).length) {
|
||||||
|
@ -157,8 +172,8 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
roles() {
|
roles() {
|
||||||
this.selectRandomRoles();
|
this.selectRandomRoles();
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -178,7 +193,7 @@ ul.tokens {
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.fa-exclamation-triangle {
|
.fa-triangle-exclamation {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -201,7 +216,7 @@ ul.tokens {
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
.fa-exclamation-triangle {
|
.fa-triangle-exclamation {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: red;
|
color: red;
|
||||||
filter: drop-shadow(0 0 3px black) drop-shadow(0 0 3px black);
|
filter: drop-shadow(0 0 3px black) drop-shadow(0 0 3px black);
|
||||||
|
|
|
@ -4,13 +4,14 @@
|
||||||
v-if="modals.voteHistory && (session.voteHistory || !session.isSpectator)"
|
v-if="modals.voteHistory && (session.voteHistory || !session.isSpectator)"
|
||||||
@close="toggleModal('voteHistory')"
|
@close="toggleModal('voteHistory')"
|
||||||
>
|
>
|
||||||
|
<div @click="clearVoteHistory">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
@click="clearVoteHistory"
|
|
||||||
icon="trash-alt"
|
icon="trash-alt"
|
||||||
class="clear"
|
class="clear"
|
||||||
title="Clear vote history"
|
title="Clear vote history"
|
||||||
v-if="session.isSpectator"
|
v-if="session.isSpectator"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>Vote history</h3>
|
<h3>Vote history</h3>
|
||||||
|
|
||||||
|
@ -58,16 +59,8 @@
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(vote, index) in session.voteHistory" :key="index">
|
<tr v-for="(vote, index) in session.voteHistory" :key="index">
|
||||||
<td>
|
<td>
|
||||||
{{
|
{{ vote.timestamp.getHours().toString().padStart(2, "0") }}:{{
|
||||||
vote.timestamp
|
vote.timestamp.getMinutes().toString().padStart(2, "0")
|
||||||
.getHours()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0")
|
|
||||||
}}:{{
|
|
||||||
vote.timestamp
|
|
||||||
.getMinutes()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, "0")
|
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ vote.nominator }}</td>
|
<td>{{ vote.nominator }}</td>
|
||||||
|
|
20
src/main.js
20
src/main.js
|
@ -1,5 +1,5 @@
|
||||||
import Vue from "vue";
|
import { createApp } from "vue";
|
||||||
import App from "./App";
|
import App from "./App.vue";
|
||||||
import store from "./store";
|
import store from "./store";
|
||||||
import { library } from "@fortawesome/fontawesome-svg-core";
|
import { library } from "@fortawesome/fontawesome-svg-core";
|
||||||
import { fas } from "@fortawesome/free-solid-svg-icons";
|
import { fas } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
@ -52,17 +52,15 @@ const faIcons = [
|
||||||
"VolumeMute",
|
"VolumeMute",
|
||||||
"VoteYea",
|
"VoteYea",
|
||||||
"WindowMaximize",
|
"WindowMaximize",
|
||||||
"WindowMinimize"
|
"WindowMinimize",
|
||||||
];
|
];
|
||||||
const fabIcons = ["Github", "Discord"];
|
const fabIcons = ["Github", "Discord"];
|
||||||
library.add(
|
library.add(
|
||||||
...faIcons.map(i => fas["fa" + i]),
|
...faIcons.map((i) => fas["fa" + i]),
|
||||||
...fabIcons.map(i => fab["fa" + i])
|
...fabIcons.map((i) => fab["fa" + i])
|
||||||
);
|
);
|
||||||
Vue.component("font-awesome-icon", FontAwesomeIcon);
|
|
||||||
Vue.config.productionTip = false;
|
|
||||||
|
|
||||||
new Vue({
|
createApp(App)
|
||||||
render: h => h(App),
|
.component("font-awesome-icon", FontAwesomeIcon)
|
||||||
store
|
.use(store)
|
||||||
}).$mount("#app");
|
.mount("#app");
|
||||||
|
|
|
@ -1,5 +1,20 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// if the app is supposed to run on Github Pages in a subfolder, use the following config:
|
publicPath: process.env.NODE_ENV === "production" ? "/" : "/",
|
||||||
// publicPath: process.env.NODE_ENV === "production" ? "/townsquare/" : "/"
|
chainWebpack: (config) => {
|
||||||
publicPath: process.env.NODE_ENV === "production" ? "/" : "/"
|
config.resolve.alias.set("vue", "@vue/compat");
|
||||||
|
|
||||||
|
config.module
|
||||||
|
.rule("vue")
|
||||||
|
.use("vue-loader")
|
||||||
|
.tap((options) => {
|
||||||
|
return {
|
||||||
|
...options,
|
||||||
|
compilerOptions: {
|
||||||
|
compatConfig: {
|
||||||
|
MODE: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue