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
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/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": {
"version": "5.13.0",
"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",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-brands-svg-icons": "^5.13.0",
"@fortawesome/free-solid-svg-icons": "^5.13.0",
"@fortawesome/vue-fontawesome": "^0.1.9",
"@vue/cli-service": "^4.3.1",

View File

@ -7,17 +7,24 @@ const server = https.createServer({
key: fs.readFileSync("key.pem")
});
const wss = new WebSocket.Server({
server,
// port: 8081,
...(process.env.NODE_ENV === "development" ? { port: 8081 } : { server }),
verifyClient: info =>
!!info.origin.match(/^https?:\/\/(bra1n\.github\.io|localhost)/i)
});
function noop() {}
function heartbeat() {
this.isAlive = true;
}
wss.on("connection", function connection(ws, req) {
ws.channel = req.url
.split("/")
.pop()
.toLocaleLowerCase();
ws.isAlive = true;
ws.on("pong", heartbeat);
ws.on("message", function incoming(data) {
if (!data.match(/^\["ping/i)) {
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":
this.$refs.menu.addPlayer();
break;
case "h":
this.$refs.menu.hostSession();
break;
case "j":
this.$refs.menu.joinSession();
break;
case "r":
this.$store.commit("toggleModal", "reference");
break;
@ -73,8 +79,8 @@ export default {
if (this.session.isSpectator) return;
this.$store.commit("toggleModal", "roles");
break;
case "Escape":
this.$store.commit("toggleMenu");
case "escape":
this.$store.commit("toggleModal");
}
}
}

View File

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

View File

@ -86,6 +86,10 @@ export default {
[
"Frankenstein's Mayor by Ted",
"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 { library } from "@fortawesome/fontawesome-svg-core";
import { fas } from "@fortawesome/free-solid-svg-icons";
import { fab } from "@fortawesome/free-brands-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
const faIcons = [
@ -13,12 +14,15 @@ const faIcons = [
"CheckSquare",
"Cog",
"Copy",
"Dice",
"ExchangeAlt",
"FileUpload",
"HandPointRight",
"Heartbeat",
"Image",
"Link",
"PeopleArrows",
"Question",
"Random",
"RedoAlt",
"SearchMinus",
@ -28,6 +32,7 @@ const faIcons = [
"TheaterMasks",
"Times",
"TimesCircle",
"TrashAlt",
"Undo",
"User",
"UserEdit",
@ -35,7 +40,11 @@ const faIcons = [
"Users",
"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.config.productionTip = false;

View File

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