diff --git a/README.md b/README.md index c013a18..533321a 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@ -- virtual tabletop for distributed cooperative storytelling -- +![Screenshot of Felt showing a hexgrid map on which a party of 4 humans and an alien dog thing facing off against two giant centipedes. Various UI windows are overlayed, some of them collapsed, showing the dice roller with dice log, and admin windows showing map and token management interfaces.](./screenshot.jpg) + ## about -Felt is a lightweight webapp written in Go and vanilla Javascript which provides an agnostic virtual tabletop for battle maps, visual puzzles, or any other situation you may need a shared map in a tabletop RPG over voice/video chat. +Felt is a lightweight (~210KB frontend!) webapp written in Go and vanilla Javascript which provides an agnostic virtual tabletop for battle maps, visual puzzles, or any other situation you may need a shared map in a tabletop RPG over voice/video chat. ## usage @@ -20,7 +22,7 @@ The `status` panel is updated when the admin changes the table's status, and can The `token select` panel provides a list of every existing token at the table. Clicking the name of the token shows a to-scale preview at the top of the list (with a button to dismiss it), which is resized according to the zoom level of the map. The button to the right of each token's name can place the token on or remove it from the map. -Any user can move any token on the map by dragging it around. The map is pannable and zoomable as well. +Any user can move any token on the map by dragging it around. The map is pannable and zoomable as well. The UI theme is changeable from the drawer at bottom-left of the application, and it persists to `localStorage`. ### admin @@ -48,8 +50,8 @@ Creating or destroying tokens as well as changing the map causes updates to be s Requirements: -- go -- docker +- go 1.19 +- docker (for the containerized database) 1. Clone this repository and `cd` into it. 2. `go mod tidy` diff --git a/screenshot.jpg b/screenshot.jpg new file mode 100644 index 0000000..48a5c56 Binary files /dev/null and b/screenshot.jpg differ diff --git a/static/admin.js b/static/admin.js index dd32617..85a411b 100644 --- a/static/admin.js +++ b/static/admin.js @@ -65,7 +65,6 @@ async function loadAdmin(name, pass) { console.log(res.status); } } catch (err) { - console.dir(err) setErr(`${err.name}: ${err.message}`); } } @@ -162,26 +161,26 @@ function drawTokenOrigin() { if (dropdown && tokenWidth && tokenHeight && tokenCX && tokenCY && preview && dropdown.selectedIndex >= 0) { - tokenCX.value = Math.floor(Number(tokenCX.value)) - tokenCY.value = Math.floor(Number(tokenCY.value)) + tokenCX.value = Math.floor(Number(tokenCX.value)) + tokenCY.value = Math.floor(Number(tokenCY.value)) - const img = preview.children[0]; - const x = Number(tokenWidth.value) / Number(tokenCX.value); - const y = Number(tokenHeight.value) / Number(tokenCY.value); + const img = preview.children[0]; + const x = Number(tokenWidth.value) / Number(tokenCX.value); + const y = Number(tokenHeight.value) / Number(tokenCY.value); - const origin = {x: img.width/x, y: img.height/y}; - const originImg = document.createElement("img"); + const origin = {x: img.width/x, y: img.height/y}; + const originImg = document.createElement("img"); - originImg.src="/table/origin.png"; - originImg.style.position = "absolute"; - originImg.style.left = (origin.x - 2) + "px"; - originImg.style.top = (origin.y - 2) + "px"; + originImg.src="/table/origin.png"; + originImg.style.position = "absolute"; + originImg.style.left = (origin.x - 2) + "px"; + originImg.style.top = (origin.y - 2) + "px"; - if (preview.children.length > 1) { - preview.replaceChild(originImg, preview.children[1]); - } else { - preview.appendChild(originImg); - } + if (preview.children.length > 1) { + preview.replaceChild(originImg, preview.children[1]); + } else { + preview.appendChild(originImg); + } } } @@ -252,7 +251,9 @@ function createToken() { return; } setErr("All token fields are required"); - } catch {} + } catch (err) { + setErr(`${err.name}: ${err.message}`); + } } function destroyToken(id) { @@ -270,7 +271,7 @@ function previewExistingToken(id) { try { const existing = tokens.find(t=>t.t.id == id); const dropdown = $("sprite_dropdown"); - if (existing) { + if (existing && dropdown) { $("token_width").value = existing.t.w; $("token_height").value = existing.t.h; $("token_cx").value = existing.t.oX; @@ -285,7 +286,7 @@ function previewExistingToken(id) { reinitializeSpritePreview(true); } } catch (err) { - console.log(err); + setErr(`${err.name}: ${err.message}`); } } @@ -486,7 +487,9 @@ function setTableCreateFormVisible(v) { $("new_table_name").value = ""; $("new_table_pass").value = ""; } - } catch {} + } catch (err) { + setErr(`${err.name}: ${err.message}`); + } } function setTokenCreateFormVisible(v) { @@ -502,7 +505,9 @@ function setTokenCreateFormVisible(v) { $("token_creation_form").style.display = v ? "block" : "none"; $("token_list").style.display = v ? "none" : "block"; $("token_admin_preview").innerHTML = ""; - } catch {} + } catch (err) { + setErr(`${err.name}: ${err.message}`); + } } async function createTable() { @@ -538,8 +543,8 @@ async function createTable() { } else { setErr("Error creating table"); } - } catch { - setErr("Error creating table"); + } catch (err) { + setErr(`${err.name}: ${err.message}`); } } } \ No newline at end of file diff --git a/static/index.html b/static/index.html index d662dc2..e1a3467 100644 --- a/static/index.html +++ b/static/index.html @@ -6,7 +6,7 @@ - +