mirror of https://github.com/bra1n/townsquare.git
Merge branch 'master' into voting
# Conflicts: # src/components/Menu.vue
This commit is contained in:
commit
863c2137a6
|
@ -0,0 +1,4 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: bra1n
|
||||
custom: https://www.paypal.me/bra1n
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) {
|
|||
});
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
10
src/App.vue
10
src/App.vue
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,11 +24,25 @@
|
|||
<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 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="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>
|
||||
|
@ -37,30 +51,42 @@
|
|||
<li @click="toggleNightOrder" v-if="players.length">
|
||||
<em
|
||||
><font-awesome-icon
|
||||
:icon="['fas', grimoire.isNightOrder ? 'check-square' : 'square']"
|
||||
: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" />
|
||||
<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">
|
||||
Host Live Session
|
||||
<em>[H]</em> Host (Storyteller)
|
||||
</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" }}
|
||||
<em>[J]</em> Join (Player)
|
||||
</li>
|
||||
<li v-if="session.sessionId" @click="copySessionUrl">
|
||||
<em><font-awesome-icon icon="copy"/></em>
|
||||
|
@ -70,34 +96,27 @@
|
|||
<em>{{ session.sessionId }}</em>
|
||||
Leave Session
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<template v-if="!session.isSpectator">
|
||||
<template v-if="tab === 'players' && !session.isSpectator">
|
||||
<!-- Users -->
|
||||
<li class="headline">
|
||||
<font-awesome-icon icon="users" />
|
||||
Players
|
||||
</li>
|
||||
<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>
|
||||
|
||||
<template v-if="tab === 'characters' && !session.isSpectator">
|
||||
<!-- 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">
|
||||
<li class="headline">Characters</li>
|
||||
<li @click="toggleModal('edition')">
|
||||
<em>[E]</em>
|
||||
Select Edition
|
||||
|
@ -106,10 +125,32 @@
|
|||
<em>[C]</em>
|
||||
Choose & Assign
|
||||
</li>
|
||||
</template>
|
||||
<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>
|
||||
</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,
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
]
|
||||
};
|
||||
|
|
11
src/main.js
11
src/main.js
|
@ -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;
|
||||
|
||||
|
|
|
@ -79,13 +79,13 @@ export default new Vuex.Store({
|
|||
}
|
||||
},
|
||||
toggleModal({ modals }, name) {
|
||||
if (name) {
|
||||
modals[name] = !modals[name];
|
||||
if (modals[name]) {
|
||||
}
|
||||
for (let modal in modals) {
|
||||
if (modal === name) continue;
|
||||
modals[modal] = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
updateScreenshot({ grimoire }, status) {
|
||||
if (status !== true && status !== false) {
|
||||
|
|
Loading…
Reference in New Issue