fix dice decoding, token editor can create clientside tokens!

This commit is contained in:
Iris Lightshard 2023-07-06 02:20:30 -06:00
parent 8dfa99c8bc
commit f4513a28f7
Signed by: nilix
GPG key ID: 3B7FBC22144E6398
7 changed files with 185 additions and 15 deletions

View file

@ -7,6 +7,15 @@ const createTokenForm = document.getElementById("createTokenForm");
const tokenWrapper = document.getElementById("adminWrapper_tokens");
const newTableName = document.getElementById("newTableName");
const newTablePass = document.getElementById("newTablePass");
const tokenSpriteDropdown = document.getElementById("token_combobox");
const tokenName = document.getElementById("token_name");
const tokenWidth = document.getElementById("token_width");
const tokenHeight = document.getElementById("token_height");
const tokenCX = document.getElementById("token_cx");
const tokenCY = document.getElementById("token_cy");
const previewZone = document.getElementById("tokenPreview_zone");
const tokenAspect = document.getElementById("tokenKeepAspect");
const aspectLockLabel = document.getElementById("aspectLockLabel");
async function getTable(name, pass) {
try {
@ -50,7 +59,7 @@ async function getTable(name, pass) {
}
adminZone.innerHTML = infoHtml;
let tokenListHTML = "<input id='token_img_upload' type='file'/><button onclick='uploadTokenImg()'>Upload Token</button><br/>";
let tokenListHTML = "<input id='token_img_upload' type='file'/><button onclick='uploadTokenImg()'>Upload Sprite</button><br/>";
if (tokenImgs.ok) {
tokenListHTML += "<label>Available Sprites</label>";
const tokens = await tokenImgs.json();
@ -60,15 +69,19 @@ async function getTable(name, pass) {
tokenListHTML += `<li>${parts[parts.length - 1]} <a href="${t}" target="_blank">view</a> <button onclick="deleteImg('${t}')">Delete</button></li>\n`
}
tokenListHTML += "</ul>";
fillSpriteDropdown(tokens);
} else {
tokenListHTML += "<label>Sprites couldn't be retrieved</label>"
}
spriteZone.innerHTML = tokenListHTML;
tokenWrapper.style.display = "inline";
// also, we have to fill and toggle the tokens window
} else {
console.log(res.status);
}
@ -77,6 +90,144 @@ async function getTable(name, pass) {
}
}
function fillSpriteDropdown(tokens) {
let options = "<option value=''>select</option>";
for (const t of tokens) {
const parts = t.split("/");
const o = `<option value="${t}">${parts[parts.length - 1]}</option>\n`;
options += o;
}
tokenSpriteDropdown.innerHTML = options;
}
function previewSprite(source) {
if (source) {
switch (source.id) {
case "token_combobox":
reinitializeSpritePreview();
break;
case "token_cx":
case "token_cy":
drawTokenOrigin();
break;
default:
scaleSpritePreview(source);
console.log("default case");
}
}
}
function toggleAspectLock() {
if (tokenKeepAspect.checked) {
aspectLockLabel.innerHTML = "&#128274;"
} else {
aspectLockLabel.innerHTML = "&#128275;"
}
}
function scaleSpritePreview(source) {
if (mapImg && mapImg._image) {
console.log(mapImg);
const scaleFactor = mapImg._image.clientWidth / mapImg._image.naturalWidth;
const keepAspect = tokenAspect.checked;
const img = previewZone.children[0];
if (img) {
if (!keepAspect || !source) {
img.width = Number(tokenWidth.value) * scaleFactor;
img.height = Number(tokenHeight.value) * scaleFactor;
} else {
const currentAspect = img.width/img.height;
switch (source.id) {
case "token_width":
img.width = Number(tokenWidth.value) * scaleFactor;
img.height = (img.clientWidth / img.naturalWidth) * img.naturalHeight;
tokenHeight.value = Number(tokenWidth.value)/currentAspect;
break;
case "token_height":
img.height = Number(tokenHeight.value) * scaleFactor;
img.width = (img.clientHeight / img.naturalHeight) * img.naturalWidth;
tokenWidth.value = currentAspect * Number(tokenHeight.value);
break;
}
}
tokenCX.value = Number(tokenWidth.value)/2;
tokenCY.value = Number(tokenHeight.value)/2;
drawTokenOrigin();
}
}
}
function drawTokenOrigin() {
const img = previewZone.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");
originImg.src="/table/origin.png";
originImg.style.position = "absolute";
originImg.style.left = (origin.x - 2) + "px";
originImg.style.top = (origin.y - 2) + "px";
if (previewZone.children.length > 1) {
previewZone.replaceChild(originImg, previewZone.children[1]);
} else {
previewZone.appendChild(originImg);
}
}
function reinitializeSpritePreview() {
const img = document.createElement("img");
img.src = tokenSpriteDropdown[tokenSpriteDropdown.selectedIndex].value;
const tokenNameParts = tokenSpriteDropdown[tokenSpriteDropdown.selectedIndex].text.split(".");
tokenNameParts.pop();
tokenName.value = tokenNameParts.join(".");
img.onload = () => {
const w = img.naturalWidth;
const h = img.naturalHeight;
tokenWidth.value = w;
tokenHeight.value = h;
scaleSpritePreview();
}
if (previewZone.children.length) {
previewZone.replaceChild(img, previewZone.children[0]);
} else {
previewZone.appendChild(img);
}
}
function createToken() {
const w = Number(tokenWidth.value);
const h = Number(tokenHeight.value);
const oX = Number(tokenCX.value);
const oY = Number(tokenCY.value);
const img = tokenSpriteDropdown[tokenSpriteDropdown.selectedIndex].value;
const name = tokenName.value;
console.log("creating token");
if (!isNaN(w) && !isNaN(h) && !isNaN(oX) && !isNaN(oY) && img && name) {
console.log("all green");
const self = {
sz: [w, h],
m: L.marker(getCascadingPos(), {
icon: L.icon({
iconUrl: img,
iconSize: [w,h],
}),
title: name,
draggable: true,
autoPan: true
}),
};
tokens.push(self);
self.m.addTo(map);
resizeMarkers();
}
}
function publishAuxMsg() {
const txtArea = document.getElementById("auxMsgZone");
if (txtArea != null) {
@ -223,8 +374,8 @@ async function doLogin() {
if (adminUsrInput && adminPassInput) {
adminToken = await getAdminToken(adminUsrInput.value, adminPassInput.value);
if (adminToken) {
adminWrapper.style.display="inline";
getTables();
adminWrapper.style.display="inline";
adminZone.style.display = "block";
} else {
setErr("Incorrect credentials");

View file

@ -89,14 +89,15 @@
<details id="admin_token_win" class="ui_win admin_win"><summary>tokens</summary>
<button onclick="setTokenCreateFormVisible(true)">New Token</button>
<form onsubmit="return false" id="createTokenForm">
<label>Sprite<select id="token_combobox"></select></label><br/>
<label>Name<input id="newToken_name"/></label><br/>
<label>Width<input type="number" id="newToken_width" min="1" max="9999"/></label><br/>
<label>Height<input type="number" id="newToken_height" min="1" max="9999"/></label><br/>
<label>cX<input type="number" id="newToken_cx" min="0" max="9999"/></label><br/>
<label>cY<input type="number" id="newToken_cy" min="0" max="9999"/></label><br/>
<label>Sprite<select id="token_combobox" onchange="previewSprite(this)"></select></label><br/>
<label>Name<input id="token_name"/></label><br/>
<label>Width<input type="number" id="token_width" min="1" max="9999" onchange="previewSprite(this)"/></label><label id="aspectLockLabel" for="tokenKeepAspect">&#128274;</label><input type="checkbox" checked id="tokenKeepAspect" onchange="toggleAspectLock()"/><br/>
<label>Height<input type="number" id="token_height" min="1" max="9999" onchange="previewSprite(this)"/></label><br/>
<label>cX<input type="number" id="token_cx" min="0" max="9999" onchange="previewSprite(this)"/></label><br/>
<label>cY<input type="number" id="token_cy" min="0" max="9999" onchange="previewSprite(this)"/></label><br/>
<div id="tokenPreview_zone"></div>
<button type="submit" onlcick="createToken()">Create</button>
<button type="submit" onclick="createToken()">Create</button>
<button onclick="setTokenCreateFormVisible(false)">Cancel</button>
</form>
<div id="tokenZone"></div>

View file

@ -1,18 +1,19 @@
let map = null;
let mapImg = null;
let tokens = [];
const worldBounds = [[180, -180],[-180, 180]];
function initializeMap(mapImgUrl) {
if (!map) {
map = L.map('map', { minZoom: 0, maxZoom: 4, crs: L.CRS.Simple });
map.on("zoomend", resizeMarkers);
map.on("zoomend", ()=>{resizeMarkers();scaleSpritePreview();});
}
if (mapImg) {
mapImg.removeFrom(map);
}
mapImg = L.imageOverlay(mapImgUrl, [[-180, 180],[180, -180]]);
mapImg = L.imageOverlay(mapImgUrl, worldBounds);
mapImg.addTo(map);
map.setMaxBounds([[-180,180],[180,-180]]);
map.setMaxBounds(worldBounds);
map.setView([0,0], 2);
while (tokens.some(t=>t)) {
tokens[0].m.removeFrom(map);
@ -30,6 +31,14 @@ function resizeMarkers() {
});
}
function getCascadingPos() {
const topLeft = worldBounds[0];
const n = tokens.length;
topLeft[1] += (n+1)*5;
topLeft[0] -= (n+1)*5;
return topLeft;
}
function addToken(token) {
const self = { sz: token.sz, m: L.marker(token.pos, {
icon: L.icon({

BIN
static/origin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

View file

@ -19,6 +19,7 @@ function fmtLeading(n) {
}
function formatDice(r) {
console.log(r);
const date = new Date(r.timestamp)
const p = document.createElement("p");
const month = date.getMonth() + 1;
@ -51,7 +52,6 @@ function logDice(dice, many) {
function setAuxMsg(msg) {
const auxDiv = document.getElementById("aux");
if (auxDiv) {
console.log("eeee");
auxDiv.innerText = msg;
}
}
@ -111,7 +111,6 @@ function dial() {
});
conn.addEventListener("open", e => {
// TODO: add message to let user know they are at the table
console.info("socket connected");
tabletop = document.getElementById("tabletop");
if (tabletop) {
tabletop.style.display = "block";
@ -132,6 +131,9 @@ function dial() {
table = data;
makeUpToDate(table);
} else {
if (data.diceRoll) {
data.diceRoll.roll = Uint8Array.from(atob(data.diceRoll.roll), c => c.charCodeAt(0));
}
makeUpToDate(data);
}

View file

@ -169,4 +169,12 @@ nav {
.leaflet-container {
background: transparent;
}
#tokenKeepAspect {
display: none;
}
#tokenPreview_zone {
position: relative;
}

View file

@ -30,7 +30,6 @@ function loadName() {
if (username) {
const cookies = document.cookie.split(";")
cookies.forEach(c=>{
console.log(c);
if (c.trim().startsWith("username=")) {
username.value = c.trim().split("=")[1];
}