enforce subprotocol/tableKey to alphanumeric and underscore, refine UI
This commit is contained in:
parent
0d3b4fb5ae
commit
43d85c2abc
7 changed files with 132 additions and 78 deletions
|
@ -16,6 +16,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
func apiGetTableList(next http.Handler, udb auth.UserStore) http.Handler {
|
func apiGetTableList(next http.Handler, udb auth.UserStore) http.Handler {
|
||||||
|
@ -80,6 +81,13 @@ func apiCreateTable(next http.Handler, udb auth.UserStore, dbAdapter mongodb.DbA
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r := regexp.MustCompile("^[a-zA-Z0-9_]+$")
|
||||||
|
if !r.MatchString(tableKey.Name) || !r.MatchString(tableKey.Passcode) {
|
||||||
|
w.WriteHeader(422)
|
||||||
|
next.ServeHTTP(w, req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// table name is primary key so w edon't need to check
|
// table name is primary key so w edon't need to check
|
||||||
err = dbAdapter.CreateTable(tableKey)
|
err = dbAdapter.CreateTable(tableKey)
|
||||||
|
|
||||||
|
|
|
@ -127,7 +127,9 @@ async function destroyTable() {
|
||||||
});
|
});
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
conn.close(1000);
|
conn.close(1000);
|
||||||
|
initializeMap("");
|
||||||
getTables();
|
getTables();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
setErr(await res.json());
|
setErr(await res.json());
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,8 @@
|
||||||
</form>
|
</form>
|
||||||
</details><br/>
|
</details><br/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="tabletop" style="display:none;">
|
<div id="tabletop" style="display:none;">
|
||||||
<details class="ui_win"><summary>dice</summary>
|
<details id="dice_win" class="ui_win"><summary>dice</summary>
|
||||||
<select id="num_dice">
|
<select id="num_dice">
|
||||||
<option>1</option>
|
<option>1</option>
|
||||||
<option>2</option>
|
<option>2</option>
|
||||||
|
|
|
@ -14,6 +14,10 @@ function initializeMap(mapImgUrl) {
|
||||||
mapImg.addTo(map);
|
mapImg.addTo(map);
|
||||||
map.setMaxBounds([[-180,180],[180,-180]]);
|
map.setMaxBounds([[-180,180],[180,-180]]);
|
||||||
map.setView([0,0], 2);
|
map.setView([0,0], 2);
|
||||||
|
while (tokens.some(t=>t)) {
|
||||||
|
tokens[0].m.removeFrom(map);
|
||||||
|
tokens.shift();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// this works but assumes the map is square (reasonable limitation I think)
|
// this works but assumes the map is square (reasonable limitation I think)
|
||||||
|
|
|
@ -14,10 +14,19 @@ function showTableModal(show) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fmtLeading(n) {
|
||||||
|
return n < 10 ? "0" + n : String(n);
|
||||||
|
}
|
||||||
|
|
||||||
function formatDice(r) {
|
function formatDice(r) {
|
||||||
const date = new Date(r.timestamp)
|
const date = new Date(r.timestamp)
|
||||||
const p = document.createElement("p");
|
const p = document.createElement("p");
|
||||||
p.innerHTML = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()} ${r.player} rolled ${r.roll.length}d${r.faces} ${(r.note ? "(" + r.note + ")" : "")}<br>[${r.roll}] (total ${r.roll.reduce((a,c)=>a+c,0)})`;
|
const month = date.getMonth() + 1;
|
||||||
|
const day = date.getDate();
|
||||||
|
const hours = date.getHours();
|
||||||
|
const minutes = date.getMinutes();
|
||||||
|
const seconds = date.getSeconds();
|
||||||
|
p.innerHTML = `${date.getFullYear()}-${fmtLeading(month)}-${fmtLeading(day)} ${fmtLeading(hours)}:${fmtLeading(minutes)}:${fmtLeading(seconds)} ${r.player} rolled ${r.roll.length}d${r.faces} ${(r.note ? "(" + r.note + ")" : "")}<br>[${r.roll}] (total ${r.roll.reduce((a,c)=>a+c,0)})`;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,11 +76,17 @@ function setMapImg(url) {
|
||||||
initializeMap(url);
|
initializeMap(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTableKeyValid(name, passcode) {
|
||||||
|
const r = /^[a-zA-Z0-9_]+$/;
|
||||||
|
return r.test(name) && r.test(passcode);
|
||||||
|
}
|
||||||
|
|
||||||
function dial() {
|
function dial() {
|
||||||
// get tableKey from UI
|
// get tableKey from UI
|
||||||
const tblNameInput = document.getElementById("input_table_name");
|
const tblNameInput = document.getElementById("input_table_name");
|
||||||
const tblPassInput = document.getElementById("input_table_pass");
|
const tblPassInput = document.getElementById("input_table_pass");
|
||||||
if (tblNameInput && tblPassInput && tblNameInput.value && tblPassInput.value) {
|
if (tblNameInput && tblPassInput && tblNameInput.value && tblPassInput.value) {
|
||||||
|
if (isTableKeyValid(tblNameInput.value, tblPassInput.value)) {
|
||||||
tableKey.name = tblNameInput.value;
|
tableKey.name = tblNameInput.value;
|
||||||
tableKey.passcode = tblPassInput.value;
|
tableKey.passcode = tblPassInput.value;
|
||||||
|
|
||||||
|
@ -80,8 +95,11 @@ function dial() {
|
||||||
}
|
}
|
||||||
conn = new WebSocket(`ws://${location.host}/subscribe`, `${tableKey.name}.${tableKey.passcode}`);
|
conn = new WebSocket(`ws://${location.host}/subscribe`, `${tableKey.name}.${tableKey.passcode}`);
|
||||||
conn.addEventListener("close", e => {
|
conn.addEventListener("close", e => {
|
||||||
if (e.code > 1001) {
|
if (e.code == 1006) {
|
||||||
|
setErr("Table not found - check the name and passcode are correct");
|
||||||
|
} else if (e.code > 1001) {
|
||||||
// TODO: add message to let user know they are reconnecting
|
// TODO: add message to let user know they are reconnecting
|
||||||
|
setErr("Websocket error: trying to reconnect");
|
||||||
setTimeout(dial, 1000)
|
setTimeout(dial, 1000)
|
||||||
} else {
|
} else {
|
||||||
tabletop = document.getElementById("tabletop");
|
tabletop = document.getElementById("tabletop");
|
||||||
|
@ -119,6 +137,11 @@ function dial() {
|
||||||
|
|
||||||
console.log(data);
|
console.log(data);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
setErr("Table name and passcode can only be alphanumeric and underscores");
|
||||||
|
}
|
||||||
|
}else {
|
||||||
|
setErr("Table name and passcode required");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
:root {
|
||||||
|
--bg_color: rgba(0,0,0,0.7);
|
||||||
|
--fg_color: #ccc;
|
||||||
|
--main_color: #1f9b92;
|
||||||
|
--sub_color: #002b36;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -6,12 +13,12 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
* { scrollbar-color:#1f9b92 #000;}
|
* { scrollbar-color:var(--main_color) var(--sub_color); }
|
||||||
*::-webkit-scrollbar { width:6px;height:6px; }
|
*::-webkit-scrollbar { width:6px;height:6px; }
|
||||||
*::-webkit-scrollbar-track { background:#000; }
|
*::-webkit-scrollbar-track { background: var(--sub_color);}
|
||||||
*::-webkit-scrollbar-thumb { background-color:#1f9b92;border-radius:0;border:none; }
|
*::-webkit-scrollbar-thumb { background:var(--main_color);border-radius:0;border:none; }
|
||||||
*::-webkit-scrollbar-corner { background:#000; }
|
*::-webkit-scrollbar-corner { background:var(--sub_color); }
|
||||||
*::selection { background-color:#1f9b92;color:#000;text-decoration:none;text-shadow:none; }
|
*::selection { background-color:var(--main_color);color:var(--bg_color);text-decoration:none;text-shadow:none; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: url('./bg.png');
|
background: url('./bg.png');
|
||||||
|
@ -22,15 +29,18 @@ label {
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input, select {
|
input, select, textarea {
|
||||||
background: #002b36;
|
background: var(--sub_color);
|
||||||
color: #93a1a1;
|
color: var(--fg_color);
|
||||||
border: solid 1px gray;
|
border: solid 1px transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
input , select {
|
||||||
margin-right: 1ch;
|
margin-right: 1ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:active, input:focus, select:active, select:focus {
|
input:active, input:focus, select:active, select:focus, textarea:focus {
|
||||||
border: solid 1px cyan;
|
border: solid 1px var(--main_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -41,15 +51,15 @@ ul {
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 0.5ch;
|
padding: 0.5ch;
|
||||||
background: #000;
|
background: transparent;
|
||||||
color: #fff;
|
color: var(--fg_color);
|
||||||
border: solid 2px lightseagreen;
|
border: solid 2px var(--main_color);
|
||||||
margin-right: 1ch;
|
margin-right: 1ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
color: #000;
|
color: var(--bg_color);
|
||||||
background: lightseagreen;
|
background: var(--main_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#errWrapper {
|
#errWrapper {
|
||||||
|
@ -72,8 +82,8 @@ button:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
#dice_log {
|
#dice_log {
|
||||||
background: #002b36;
|
background: var(--sub_color);
|
||||||
color: #93a1a1;
|
color: var(--fg_color);
|
||||||
height: 10em;
|
height: 10em;
|
||||||
max-height: 10em;
|
max-height: 10em;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -85,7 +95,7 @@ button:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
#dice_log p:not(:last-child) {
|
#dice_log p:not(:last-child) {
|
||||||
border-bottom: solid 1px gray;
|
border-bottom: solid 1px var(--fg_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#aux {
|
#aux {
|
||||||
|
@ -94,14 +104,12 @@ button:hover {
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
font-size: 125%;
|
font-size: 125%;
|
||||||
background: #002b36;
|
background: var(--sub_color);
|
||||||
color: #93a1a1;
|
color: var(--fg_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#auxMsgZone {
|
#auxMsgZone {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: #93a1a1;
|
|
||||||
background: #002b36;
|
|
||||||
padding:0.2em;
|
padding:0.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,18 +120,18 @@ pre {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 2em;
|
margin: 2em;
|
||||||
background: rgba(0,0,0,0.7);
|
background: var(--bg_color);
|
||||||
color: #eee;
|
color: var(--fg_color);
|
||||||
display: inline;
|
display: inline;
|
||||||
height: min-content;
|
height: min-content;
|
||||||
z-index:1;
|
z-index:1;
|
||||||
padding: 0.25em;
|
padding: 0.25em;
|
||||||
border: 2px solid dimgray;;
|
border: 2px solid transparent;
|
||||||
max-width: 80ch;
|
max-width: min(60ch, 80vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui_win:hover, .ui_win:active {
|
.ui_win:focus-within {
|
||||||
border: 2px solid #1f9b92;
|
border: 2px solid var(--main_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui_win * {
|
.ui_win * {
|
||||||
|
@ -135,7 +143,7 @@ pre {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui_win a:hover, ui_win a:active {
|
.ui_win a:hover, ui_win a:active {
|
||||||
color: #ff;
|
color: var(--fg_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
#admin_section {
|
#admin_section {
|
||||||
|
|
|
@ -38,4 +38,15 @@ function loadName() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupDiceAutoScroll() {
|
||||||
|
const diceWin = document.getElementById("dice_win");
|
||||||
|
diceWin.addEventListener("toggle", e => {
|
||||||
|
if (diceWin.open) {
|
||||||
|
const diceLog = document.getElementById("dice_log");
|
||||||
|
diceLog.children[diceLog.children.length - 1].scrollIntoView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDiceAutoScroll();
|
||||||
loadName();
|
loadName();
|
Loading…
Reference in a new issue