Merge branch 'master' into voting

# Conflicts:
#	src/components/Menu.vue
This commit is contained in:
Steffen 2020-06-04 21:57:18 +02:00
commit 863c2137a6
No known key found for this signature in database
GPG Key ID: 764D74E98267DFC6
10 changed files with 226 additions and 82 deletions

4
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,4 @@
# These are supported funding model platforms
github: bra1n
custom: https://www.paypal.me/bra1n

View File

@ -30,3 +30,8 @@ It is supposed to aid storytellers and allow them to quickly set up and capture
* All other images and icons are copyright to their respective owners * All other images and icons are copyright to their respective owners
This project and its website are provided free of charge and not affiliated with The Pandemonium Institute in any way. This project and its website are provided free of charge and not affiliated with The Pandemonium Institute in any way.
## Donations
This project will always be available free of charge, since I love building cool things and playing Blood on the Clocktower. If you still want to support me with a donation, you can do that here:
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/bra1n)

8
package-lock.json generated
View File

@ -40,6 +40,14 @@
"@fortawesome/fontawesome-common-types": "^0.2.28" "@fortawesome/fontawesome-common-types": "^0.2.28"
} }
}, },
"@fortawesome/free-brands-svg-icons": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.13.0.tgz",
"integrity": "sha512-/6xXiJFCMEQxqxXbL0FPJpwq5Cv6MRrjsbJEmH/t5vOvB4dILDpnY0f7zZSlA8+TG7jwlt12miF/yZpZkykucA==",
"requires": {
"@fortawesome/fontawesome-common-types": "^0.2.28"
}
},
"@fortawesome/free-solid-svg-icons": { "@fortawesome/free-solid-svg-icons": {
"version": "5.13.0", "version": "5.13.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz",

View File

@ -11,6 +11,7 @@
"main": "App.vue", "main": "App.vue",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.28", "@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-brands-svg-icons": "^5.13.0",
"@fortawesome/free-solid-svg-icons": "^5.13.0", "@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/vue-fontawesome": "^0.1.9", "@fortawesome/vue-fontawesome": "^0.1.9",
"@vue/cli-service": "^4.3.1", "@vue/cli-service": "^4.3.1",

View File

@ -7,17 +7,24 @@ const server = https.createServer({
key: fs.readFileSync("key.pem") key: fs.readFileSync("key.pem")
}); });
const wss = new WebSocket.Server({ const wss = new WebSocket.Server({
server, ...(process.env.NODE_ENV === "development" ? { port: 8081 } : { server }),
// port: 8081,
verifyClient: info => verifyClient: info =>
!!info.origin.match(/^https?:\/\/(bra1n\.github\.io|localhost)/i) !!info.origin.match(/^https?:\/\/(bra1n\.github\.io|localhost)/i)
}); });
function noop() {}
function heartbeat() {
this.isAlive = true;
}
wss.on("connection", function connection(ws, req) { wss.on("connection", function connection(ws, req) {
ws.channel = req.url ws.channel = req.url
.split("/") .split("/")
.pop() .pop()
.toLocaleLowerCase(); .toLocaleLowerCase();
ws.isAlive = true;
ws.on("pong", heartbeat);
ws.on("message", function incoming(data) { ws.on("message", function incoming(data) {
if (!data.match(/^\["ping/i)) { if (!data.match(/^\["ping/i)) {
console.log(ws.channel, wss.clients.size, data); console.log(ws.channel, wss.clients.size, data);
@ -34,4 +41,18 @@ wss.on("connection", function connection(ws, req) {
}); });
}); });
server.listen(8080); const interval = setInterval(function ping() {
wss.clients.forEach(function each(ws) {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping(noop);
});
}, 30000);
wss.on("close", function close() {
clearInterval(interval);
});
if (process.env.NODE_ENV !== "development") {
server.listen(8080);
}

View File

@ -62,6 +62,12 @@ export default {
case "a": case "a":
this.$refs.menu.addPlayer(); this.$refs.menu.addPlayer();
break; break;
case "h":
this.$refs.menu.hostSession();
break;
case "j":
this.$refs.menu.joinSession();
break;
case "r": case "r":
this.$store.commit("toggleModal", "reference"); this.$store.commit("toggleModal", "reference");
break; break;
@ -73,8 +79,8 @@ export default {
if (this.session.isSpectator) return; if (this.session.isSpectator) return;
this.$store.commit("toggleModal", "roles"); this.$store.commit("toggleModal", "roles");
break; break;
case "Escape": case "escape":
this.$store.commit("toggleMenu"); this.$store.commit("toggleModal");
} }
} }
} }

View File

@ -24,11 +24,25 @@
<div class="menu" v-bind:class="{ open: grimoire.isMenuOpen }"> <div class="menu" v-bind:class="{ open: grimoire.isMenuOpen }">
<font-awesome-icon icon="cog" @click="toggleMenu" /> <font-awesome-icon icon="cog" @click="toggleMenu" />
<ul> <ul>
<!-- Grimoire --> <li class="tabs" :class="tab">
<li class="headline"> <font-awesome-icon icon="book-open" @click="tab = 'grimoire'" />
<font-awesome-icon icon="book-open" /> <font-awesome-icon icon="broadcast-tower" @click="tab = 'session'" />
Grimoire <font-awesome-icon
icon="users"
v-if="!session.isSpectator"
@click="tab = 'players'"
/>
<font-awesome-icon
icon="theater-masks"
v-if="!session.isSpectator"
@click="tab = 'characters'"
/>
<font-awesome-icon icon="question" @click="tab = 'help'" />
</li> </li>
<template v-if="tab === 'grimoire'">
<!-- Grimoire -->
<li class="headline">Grimoire</li>
<li @click="toggleGrimoire" v-if="players.length"> <li @click="toggleGrimoire" v-if="players.length">
<em>[G]</em> <em>[G]</em>
<template v-if="!grimoire.isPublic">Hide</template> <template v-if="!grimoire.isPublic">Hide</template>
@ -37,30 +51,42 @@
<li @click="toggleNightOrder" v-if="players.length"> <li @click="toggleNightOrder" v-if="players.length">
<em <em
><font-awesome-icon ><font-awesome-icon
:icon="['fas', grimoire.isNightOrder ? 'check-square' : 'square']" :icon="[
'fas',
grimoire.isNightOrder ? 'check-square' : 'square'
]"
/></em> /></em>
Night order Night order
</li> </li>
<li v-if="players.length"> <li v-if="players.length">
<em> <em>
<font-awesome-icon @click="updateZoom(-0.1)" icon="search-minus" /> <font-awesome-icon
@click="updateZoom(-0.1)"
icon="search-minus"
/>
{{ Math.round(grimoire.zoom * 100) }}% {{ Math.round(grimoire.zoom * 100) }}%
<font-awesome-icon @click="updateZoom(0.1)" icon="search-plus" /> <font-awesome-icon @click="updateZoom(0.1)" icon="search-plus" />
</em> </em>
Zoom Zoom
</li> </li>
<li @click="setBackground"> <li @click="setBackground">
<em><font-awesome-icon icon="image"/></em>
Background image Background image
</li> </li>
</template>
<template v-if="tab === 'session'">
<li class="headline" v-if="session.sessionId">
{{ session.isSpectator ? "Playing" : "Hosting" }}
</li>
<li class="headline" v-else>
Live Session
</li>
<li @click="hostSession" v-if="!session.sessionId"> <li @click="hostSession" v-if="!session.sessionId">
Host Live Session <em>[H]</em> Host (Storyteller)
</li> </li>
<li @click="joinSession" v-if="!session.sessionId"> <li @click="joinSession" v-if="!session.sessionId">
Join Live Session <em>[J]</em> Join (Player)
</li>
<li class="headline" v-if="session.sessionId">
<font-awesome-icon icon="broadcast-tower" />
{{ session.isSpectator ? "Playing" : "Hosting" }}
</li> </li>
<li v-if="session.sessionId" @click="copySessionUrl"> <li v-if="session.sessionId" @click="copySessionUrl">
<em><font-awesome-icon icon="copy"/></em> <em><font-awesome-icon icon="copy"/></em>
@ -70,34 +96,27 @@
<em>{{ session.sessionId }}</em> <em>{{ session.sessionId }}</em>
Leave Session Leave Session
</li> </li>
</template>
<template v-if="!session.isSpectator"> <template v-if="tab === 'players' && !session.isSpectator">
<!-- Users --> <!-- Users -->
<li class="headline"> <li class="headline">Players</li>
<font-awesome-icon icon="users" />
Players
</li>
<li @click="addPlayer" v-if="players.length < 20"> <li @click="addPlayer" v-if="players.length < 20">
<em>[A]</em> Add <em>[A]</em> Add
</li> </li>
<li @click="randomizeSeatings" v-if="players.length > 2"> <li @click="randomizeSeatings" v-if="players.length > 2">
<em>[R]</em> Randomize <em><font-awesome-icon icon="dice"/></em>
Randomize
</li> </li>
<li @click="clearPlayers" v-if="players.length"> <li @click="clearPlayers" v-if="players.length">
<em><font-awesome-icon icon="trash-alt"/></em>
Remove all Remove all
</li> </li>
</template> </template>
<template v-if="tab === 'characters' && !session.isSpectator">
<!-- Characters --> <!-- Characters -->
<li class="headline"> <li class="headline">Characters</li>
<font-awesome-icon icon="theater-masks" />
Characters
</li>
<li @click="toggleModal('reference')">
<em>[R]</em>
Reference Sheet
</li>
<template v-if="!session.isSpectator">
<li @click="toggleModal('edition')"> <li @click="toggleModal('edition')">
<em>[E]</em> <em>[E]</em>
Select Edition Select Edition
@ -106,10 +125,32 @@
<em>[C]</em> <em>[C]</em>
Choose & Assign Choose & Assign
</li> </li>
</template>
<li @click="clearRoles" v-if="players.length"> <li @click="clearRoles" v-if="players.length">
<em><font-awesome-icon icon="trash-alt"/></em>
Remove all Remove all
</li> </li>
</template>
<template v-if="tab === 'help'">
<!-- Help -->
<li class="headline">Help</li>
<li @click="toggleModal('reference')">
<em>[R]</em>
Reference Sheet
</li>
<li>
<a href="https://discord.gg/tkWDny6" target="_blank">
<em><font-awesome-icon :icon="['fab', 'discord']"/></em>
Join Discord
</a>
</li>
<li>
<a href="https://github.com/bra1n/townsquare" target="_blank">
<em><font-awesome-icon :icon="['fab', 'github']"/></em>
Source code
</a>
</li>
</template>
</ul> </ul>
</div> </div>
</div> </div>
@ -127,6 +168,11 @@ export default {
...mapState(["grimoire", "session"]), ...mapState(["grimoire", "session"]),
...mapState("players", ["players"]) ...mapState("players", ["players"])
}, },
data() {
return {
tab: "grimoire"
};
},
methods: { methods: {
takeScreenshot(dimensions = {}) { takeScreenshot(dimensions = {}) {
this.$store.commit("updateScreenshot"); this.$store.commit("updateScreenshot");
@ -261,7 +307,7 @@ export default {
margin-left: 10px; margin-left: 10px;
} }
.session { span.session {
color: $demon; color: $demon;
&.spectator { &.spectator {
color: $townsfolk; color: $townsfolk;
@ -270,8 +316,8 @@ export default {
} }
.menu { .menu {
width: 210px; width: 220px;
transform-origin: 190px 22px; transform-origin: 200px 22px;
transition: transform 500ms cubic-bezier(0.68, -0.55, 0.27, 1.55); transition: transform 500ms cubic-bezier(0.68, -0.55, 0.27, 1.55);
transform: rotate(-90deg); transform: rotate(-90deg);
position: absolute; position: absolute;
@ -294,6 +340,14 @@ export default {
padding: 5px 5px 15px; padding: 5px 5px 15px;
} }
a {
color: white;
text-decoration: none;
&:hover {
color: red;
}
}
ul { ul {
display: flex; display: flex;
list-style-type: none; list-style-type: none;
@ -306,16 +360,48 @@ export default {
border-radius: 10px 0 10px 10px; border-radius: 10px 0 10px 10px;
li { li {
padding: 2px 10px; padding: 2px 5px;
color: white; color: white;
text-align: left; text-align: left;
background: rgba(0, 0, 0, 0.7); background: rgba(0, 0, 0, 0.7);
&.tabs {
display: flex;
padding: 0;
svg {
flex-grow: 1;
flex-shrink: 0;
height: 35px;
border-bottom: 3px solid black;
border-right: 3px solid black;
padding: 5px 0;
cursor: pointer;
transition: color 250ms;
&:hover {
color: red;
}
&:last-child {
border-right: 0;
}
}
&.grimoire .fa-book-open,
&.players .fa-users,
&.characters .fa-theater-masks,
&.session .fa-broadcast-tower,
&.help .fa-question {
background: linear-gradient(
to bottom,
$townsfolk 0%,
rgba(0, 0, 0, 0.5) 100%
);
}
}
&:last-child { &:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
&:not(.headline):hover { &:not(.headline):not(.tabs):hover {
cursor: pointer; cursor: pointer;
color: red; color: red;
} }
@ -332,7 +418,7 @@ export default {
.headline { .headline {
font-family: PiratesBay, sans-serif; font-family: PiratesBay, sans-serif;
letter-spacing: 1px; letter-spacing: 1px;
padding: 5px 10px; padding: 0 10px;
text-align: center; text-align: center;
background: linear-gradient( background: linear-gradient(
to right, to right,

View File

@ -86,6 +86,10 @@ export default {
[ [
"Frankenstein's Mayor by Ted", "Frankenstein's Mayor by Ted",
"https://gist.githubusercontent.com/bra1n/32c52b422cc01b934a4291eeb81dbcee/raw/3ca5a043c41141ac40667dc15097deb327263268/Frankensteins_Mayor_by_Ted.json" "https://gist.githubusercontent.com/bra1n/32c52b422cc01b934a4291eeb81dbcee/raw/3ca5a043c41141ac40667dc15097deb327263268/Frankensteins_Mayor_by_Ted.json"
],
[
"Vigormortis High School",
"https://gist.githubusercontent.com/bra1n/1f65bd4a999524719d5dabe98c3c2d27/raw/f28d3268846c182b2078888122003c6f95c6b2cf/VigormortisHighSchool.json"
] ]
] ]
}; };

View File

@ -3,6 +3,7 @@ import App from "./App";
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";
import { fab } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
const faIcons = [ const faIcons = [
@ -13,12 +14,15 @@ const faIcons = [
"CheckSquare", "CheckSquare",
"Cog", "Cog",
"Copy", "Copy",
"Dice",
"ExchangeAlt", "ExchangeAlt",
"FileUpload", "FileUpload",
"HandPointRight", "HandPointRight",
"Heartbeat", "Heartbeat",
"Image",
"Link", "Link",
"PeopleArrows", "PeopleArrows",
"Question",
"Random", "Random",
"RedoAlt", "RedoAlt",
"SearchMinus", "SearchMinus",
@ -28,6 +32,7 @@ const faIcons = [
"TheaterMasks", "TheaterMasks",
"Times", "Times",
"TimesCircle", "TimesCircle",
"TrashAlt",
"Undo", "Undo",
"User", "User",
"UserEdit", "UserEdit",
@ -35,7 +40,11 @@ const faIcons = [
"Users", "Users",
"VoteYea" "VoteYea"
]; ];
library.add(...faIcons.map(i => fas["fa" + i])); const fabIcons = ["Github", "Discord"];
library.add(
...faIcons.map(i => fas["fa" + i]),
...fabIcons.map(i => fab["fa" + i])
);
Vue.component("font-awesome-icon", FontAwesomeIcon); Vue.component("font-awesome-icon", FontAwesomeIcon);
Vue.config.productionTip = false; Vue.config.productionTip = false;

View File

@ -79,13 +79,13 @@ export default new Vuex.Store({
} }
}, },
toggleModal({ modals }, name) { toggleModal({ modals }, name) {
if (name) {
modals[name] = !modals[name]; modals[name] = !modals[name];
if (modals[name]) { }
for (let modal in modals) { for (let modal in modals) {
if (modal === name) continue; if (modal === name) continue;
modals[modal] = false; modals[modal] = false;
} }
}
}, },
updateScreenshot({ grimoire }, status) { updateScreenshot({ grimoire }, status) {
if (status !== true && status !== false) { if (status !== true && status !== false) {